Making
of the SGI Image Plugin
Why this
plugin?
In our studio, we use Silicon Graphics (SGI) workstations to do visual effects work
for movies and television. Consequently, a lot of images are generated and stored in the
SGI image file format (referred hereafter as SGI images). Personal Computers (PCs) are
fast replacing our SGI workstations as a cost-effective alternative. But our film scanners
and recorders are still connected to SGI workstations and process only SGI images. We too
want to work with SGI images because we are comfortable with it and have pulled off so
many successful projects with it. On the PC front, Photoshop is a great program for
analyzing images. But Photoshop does not read or write SGI images. So our artists convert
the SGI images into TIFF images, load them into Photoshop, manipulate them, save them in
TIFF format and convert them back to SGI format. The only problem with this approach apart
from the time spent on two way conversion is the amount of disk space required to store
the temporary TIFF images. A frame scanned at film resolution (2048w x 1736h) takes 10 MB
of disk space if the image data is stored verbatim. A typical shot may have (at least) 100
frames amounting to 1 GB of disk space. The temporary TIFF files with image compression
turned on take (at least) 250 MB. Manipulated TIFF files will take another 250 MB (because
you don't want to overwrite the original, just in case you wanted to start all over).
Conversion back to SGI uncompressed images will take another 1 GB. Therefore, there is a
waste of about 500 MB due to lack of direct support for SGI images in Photoshop. All this
for a shot with a typical minimum of frames. Imagine a bigger shot of say 2000 frames.
That will amount to 10 GB of disk space wastage. This is enough requirement to develop a
plugin that would read and write SGI images directly in Photoshop.
Adobe
Photoshop Plugin Architecture:
There are two things that need to be defined first. One is the term plugin
host. A plugin host is responsible for loading plugin modules and
calling them. Photoshop is a plugin host. The second term is the plugin
itself. A plugin for Photoshop is a code module that extends the features of
Photoshop without modifying the program itself. To support SGI images inside
Photoshop, we need to write what Photoshop calls a Format Plugin. Such a
plugin can be accessed by the user from a pop-up menu in Open ..., Save As ... and Save A
Copy... dialogs.
A Format plugin
module, defines a function called ENTRYPOINT (in Windows 95/NT). As the name
suggests, ENTRYPOINT is the first function Photoshop calls when the user runs your
plugin. The following are the parameters for this function
Parameter Type |
Parameter Name |
short |
selector |
void * |
formatParamBlock |
long * |
pluginData |
short * |
result |
The selector value indicates the type of
operation requested by Photoshop for the plugin to perform. The following table
lists the values selector can take and what the plugin should do in each case.
Selector Value |
What the Plugin should do |
0 |
Display an About box. |
formatSelectorFilterFile |
Indicate whether file selected by the user can be read. |
formatSelectorReadPrepare |
Allocate memory for reading and processing the file. |
formatSelectorReadStart |
Read any file header information |
formatSelectorReadContinue |
Process and return the next portion of the image data until
the file ends or some other error occurs. |
formatSelectorReadFinish |
Clean up the allocated resources |
formatSelectorOptionsPrepare |
Set the memory management strategy |
formatSelectorOptionsStart |
Present dialog to user to get any file specific information
needed when file is first created. |
formatSelectorOptionsContinue |
This selector may be skipped |
formatSelectorOptionsFinish |
Clean up, if necessary |
formatSelectorEstimatePrepare |
Set the memory management strategy |
formatSelectorEstimateStart |
Calculate the disk space needed to save the image in memory |
formatSelectorEstimateContinue |
This selector may be skipped |
formatSelectorEstimateFinish |
Clean up, if necessary |
formatSelectorWritePrepare |
Allocate memory for writing and processing the file. |
formatSelectorWriteStart |
Write any file header information |
formatSelectorWriteContinue |
Write into file the next portion of the image data until the
the complete image is saved. |
formatSelectorWriteFinish |
Clean up the allocated resources. |
Essentially the code in ENTRYPOINT transfers control to the
corresponding handler for a given selector value. If the plugin fails to handle a
particular request, it sets an error value in result. If this
error value is positive, the plugin has handled the error situation and Photoshop does not
worry about it. If the error value is negative, Photoshop displays its standard
error dialog and terminates the plugin. The plugin will in other normal cases set
the value of result equal to noErr.
Through formatParamBlock,
the plugin passes information back and forth to Photoshop. The formatParamBlock is a
huge structure containing a lot of parameters of which we will see an important few :
formatParamBlock Parameter |
Function |
imageSize |
Represents the size of the image. imageSize.h specifies
the width and imageSize.v specifies the height of the image. |
imageMode |
Represents the color mode of the image. We support only
RGB (with an alpha channel) mode in our plugin. Therefore in our plugin imageMode
will have the value pluginModeRGBColor |
depth |
depth is the number of bits allocated for every color
component in an image. A depth of 8 bits means red, green, blue and alpha components
of a pixel have 8 bits allocated for each one of them. In such a case, a pixel is
represented by 32 bits. In our plugin, we will support images that have a depth of 8
bits. |
planes |
The number of planes is equal to the number of components
making every pixel of the image. If the image has red, green and blue components,
the image has 3 planes. If it has an alpha component too, it has 4 planes. Our
plugin can read and write images having any number of planes. But Photoshop seems to work
unpredictably when the number of planes exceeds 16. And other programs reading our
file may not recognize the image if it has more than 4 planes. |
theRect |
Represents the rectangular region of the image that is being
processed by the read / write routines of the plugin. |
data |
data is the actual image data. When the plugin finishes
reading the entire image from disk, it must set this field to NULL. The plugin is
also responsible for freeing up any memory data may point to. |
loPlane and hiPlane |
These parameters represent the number of planes represented by
data. For example if loPlane is 0 and hiPlane is 2, then data has red, green and
blue components for every pixel in the rectangular region defined in theRect. |
colBytes |
colBytes is either 1, 3 or 4. If colBytes is 1, the
image is read one component at a time. That is the red component of all pixels is
read first, followed by green, blue and alpha (if present) components. If colBytes
is 3, the red, green, blue components of every pixel are read simultaneously. An
alpha component is also read if colBytes equals 4. |
rowBytes |
rowBytes is the number of bytes required to make up a row of
the component. It is equal to colBytes * width of theRect being
returned. |
dataFork |
Through the dataFork, the plugin reads and writes files and
performs other I/O functions. |
minDataBytes and maxDataBytes |
Through these parameters the plugin specifies minimum and
maximum disk space requirements to Photoshop for saving the image in memory.
The plugin does this in the estimate portion of the write sequence. |
pluginData is a pointer to a chunk of global
data the plugin maintains. This usually includes the header information of the
image.
Implementation:
We will develop
our plugin by modifying an example format plugin called SimpleFormat supplied with the
Photoshop Plugin Software Development Kit. A SimpleFormat image contains an image
header followed by image data. The image data has red channel of all pixels listed
first, followed by green, blue and alpha (if present) channels. The data is stored
verbatim. Even an SGI image contains a header that is followed by image data if it
is stored verbatim. The SGI header differs from the SimpleFormat header but the
structure of the verbatim image data is the same. Thus we can reuse the code
section in the SimpleFormat plugin that reads its image data. However, we need to
replace code that reads the SimpleFormat header with one that reads the SGI
header. If the image data in an SGI image is run length encoded (RLE) (ie)
compressed, we need to handle that situation too. We have decided to write the image
data verbatim, so we can reuse the code that writes the image data in the SimpleFormat
plugin. However, we need to make sure that the SGI header gets written and not the
SimpleFormat header.
The meat of the
SimpleFormat plugin is in two files SimpleFormat.c and SimpleFormat.h. In
SimpleFormat.h, we find the definition of the SimpleFormat image header. We modify
it to represent the SGI image header instead. The structure of the SGI image
header is displayed in the following table:
Type |
Size |
Name |
Description |
short |
2 bytes |
magic |
Identifies the SGI image. It must
be 474 |
char |
1 byte |
storage |
Identifies whether the image data is
stored verbatim or is Run Length Encoded (RLE) ) (ie) compressed. If storage equals
0, the image data is stored verbatim. If its 1, the image data is
compressed. |
char |
1 byte |
bpc |
Number of bytes per component per
pixel. The majority of images use 1 byte per component. We call these images
as 8 bit images. Some images are 16 bit, that is, they use 2 bytes per
component. The only valid values for this field are 1 or 2. |
short |
2 bytes |
dimension |
Number of dimensions in the image
data. The valid values for this field are 1, 2 or 3. If its 1, the image data
has one component with one scan line. The width of the scan line is equal to xsize
(described below). If its 2, the image data has one component with a number of scan
lines. The width and height of the image is equal to xsize and ysize (described
below) If it is 3, the image data has a number of components with zsize (described
below) specifying their exact number. xsize and ysize specify the width and height
of the image. |
short |
2 bytes |
xsize |
The width of the image. |
short |
2 bytes |
ysize |
The height of the image. |
short |
2 bytes |
zsize |
The number of components in the image. |
long |
4 bytes |
PIXMIN |
minimum component value. |
long |
4 bytes |
PIXMAX |
maximum component value. |
char |
4 bytes |
dummy |
Ignored. |
char |
80 bytes |
name |
The name of the image. |
long |
4 bytes |
ColorMap |
Our plugin ignores this parameter. |
char |
404 bytes |
dummy |
Ignored. |
In SimpleFormat.c, the function ENTRYPOINT is defined.
Based on the selector parameter value, it transfers control to an handler. The
following table lists the range of selector values supported and names of their
corresponding handlers.
Selector Value |
Handler Name |
formatSelectorFilterFile |
DoFilterFile |
formatSelectorReadPrepare |
DoReadPrepare |
formatSelectorReadStart |
DoReadStart |
formatSelectorReadContinue |
DoReadContinue |
formatSelectorReadFinish |
DoReadFinish |
formatSelectorOptionsPrepare |
DoOptionsPrepare |
formatSelectorOptionsStart |
DoOptionsStart |
formatSelectorOptionsContinue |
DoOptionsContinue |
formatSelectorOptionsFinish |
DoOptionsFinish |
formatSelectorEstimatePrepare |
DoEstimatePrepare |
formatSelectorEstimateStart |
DoEstimateStart |
formatSelectorEstimateContinue |
DoEstimateContinue |
formatSelectorEstimateFinish |
DoEstimateFinish |
formatSelectorWritePrepare |
DoWritePrepare |
formatSelectorWriteStart |
DoWriteStart |
formatSelectorWriteContinue |
DoWriteContinue |
formatSelectorWriteFinish |
DoWriteFinish |
Reading the SGI image:
When the user selects a particular image file for loading, Photoshop calls DoFilterFile
to know if our plugin can handle the file. In DoFilterFile, we check if the first
two bytes of the file evaluate to 474. If yes, the selected file is an SGI
image and we express our desire to handle it. Otherwise, we set an error value in
the result parameter.
When Photoshop
finds that our plugin is willing to handle the file, it calls DoReadPrepare.
We now have the option of specifying the amount of memory we require to do the job.
In our plugin, we will allocate memory as we want it and hence set the field of required
memory to zero.
The plugin is
now ready to read the image file. Photoshop calls DoReadStart
to begin the process. We read the image header in DoReadStart and set some important
parameters of the formatParamBlock structure. We set imageMode to pluginModeRGBColor,
height of the image in imageSize.v and the width in imageSize.h
and the number of color components of the image in planes. SGI
images may store only the alpha channel. But Photoshop cannot accept a value less
than 3 in the planes parameter when the imageMode parameter is set to
pluginModeRGBColor. Therefore, we fool Photoshop by setting the planes value to 4,
dont load anything into red, green and blue channels (thus leaving them empty) and load
the alpha channel in channel #4.
We read the
image data component by component, one scan line at a time in DoReadContinue.
As we know, SGI images can have their data stored verbatim or be compressed using the RLE
scheme. If the data is stored verbatim it is processed in DoVerbatimReadContinue
else it goes to DoRLEReadContinue. The code in
DoVerbatimReadContinue is the same as the code that reads the image data in the
SimpleFormat plugin. We will now discuss how the compressed image data is parsed in
DoRLEReadContinue.
When the image
data is compressed, two tables follow the image header. These tables are of type
long and their length is equal to the product of the number of components and the number
of scan lines in the image. For example, If the image has red, green and blue
components and has 500 scan lines, then these tables will have 1500 entries each.
The first compressed red scan line will begin in the file at value of table entry 0, the
first compressed green scan line will begin at the value of table entry 500 and the first
compressed blue scan line will begin at the value of table entry 1000. The
corresponding entries in the second table will give the size of the compressed scan lines
in bytes. To parse a given scan line, we position the file pointer to
the location suggested by the appropriate first table entry. We read the compressed
scan line into a buffer. The amount of bytes to read is got from the corresponding
second table entry. We begin by reading a byte from this buffer. We construct
a runCount from the first 7 bits (bit 0 through bit 6) and a runCountFlag from
bit 7. If the runCountFlag is 0, we read an another byte from the buffer and
copy it runCount number of times into the data parameter of formatParamBlock. If the
runCountFlag is 1, we copy runCount number of bytes from the buffer to data. We
repeat this procedure until all bytes are consumed from the buffer. The result is
the scan line.
Writing the
SGI image:
When the user decides to save the image in memory in SGI format, Photoshop initiates the
write sequence of the plugin. The write sequence has the following sections:
We ignore the Options section. In DoEstimatePrepare
(Estimate section), we inform Photoshop how much disk space we want to save the file
successfully. Photoshop checks whether the requested space exists on the disk and if
not terminates the plugin. In DoWriteStart (Write
section), we write the image header and the verbatim image data to disk.
Resources:
The user invokes our plugin by choosing the option "SGI" from the popups
that appear in Open..., Save As..., and Save A Copy dialogs. To get the text
"SGI" to show up in those popups we need to edit the resource identifiers
in SimpleFormat.r. The following table lists the resource identifiers that our
plugin sets.
Resource Identifier |
Our Value |
plugInName |
SGI |
plugInCopyrightYear |
1998 |
plugInDescription |
Author : Jagannathan Sampath, EFX R + D
Reads and Writes SGI image files
Copyright (c) 1998 EFX. All Rights Reserved. |
vendorName |
EFX |
plugInAETEComment |
SGI Image File Reader / Writer |
plugInSuiteID |
sdk4 |
pluginClassID |
sgiI |
ReadExtensions |
{ { 'rgb ', 'sgi ' } |
WriteExtensions |
{ { 'rgb ' } |
FilteredExtensions |
{ { 'rgb ', 'sgi ' } |
The plugInName along with the extensions appear in the
popups. We also use the plugInName and pluginDescription in the about box displayed
when DoAbout is called.
Conclusion:
As you know, this plugin reads only 8 bit SGI images at this time. Though Photoshop
supports conversion of 8 bit images into 16 bit images, all its image manipulation tools
work only on 8 bit images. Thus there is no point in reading into Photoshop a 16 bit
image. We will wait for the day when Photoshop can finally give us 16 bit image
manipulation tools. Until then, good bye.
References:
|
The Adobe Photoshop
Plugin API Guide | Release 4.0.1
|
|
The SGI Image File
Format Specification by Paul Haeberli of SGI.
|
|