This is an old revision of the document!
Images
Bitmapped images are described in chapter 8.9 Images on page 203 of the PDF specification on 14 pages.
Usage
Sampled or bitmapped images are represented by PDF.ImageXObject objects.
An ImageXObject is created by sending asPDF
to a Smalltalk image object.
The ImageXObject is rendered in the rendering block with: paintXObject:
.
PDF images are always rendered into a unit square (1 by 1 point). Therefore the scaling should be adjusted by changing the current transformation matrix. For this not to affect other graphics, the code should be wrapped by isolatedDo:
.
The complete basic code to put anImage
onto a PDF page is:
page := Page newInBounds: (0 @ 0 corner: 100 @ 100) colorspace: DeviceRGB new render: [:renderer | renderer isolatedDo: [ "scale by the extent for pixels to have unit size" renderer concat: ((Matrix scaling: anImage extent) translatedBy: 10 @ 10). renderer paintXObject: anImage asPDF]].
Object Models
Conceptually, an image is a rectangular array of colored pixels. The array is defined by width and height. Pixels are accessed with zero-based coordinates with the first pixel (0 @ 0
) at the top left corner and the last (w-1 @ h-1
) at the bottom right corner. The pixels are organized by rows.
Pixels consist of bits representing a color. The color of a pixel can be read by
aColorValue := anImage valueAtPoint: columnIndex @ rowIndex.
and set with
anImage valueAtPoint: columnIndex @ rowIndex put: aColorValue.
Technically, images in Smalltalk and PDF differ.
Images in Smalltalk
Image bits: aByteArray width: anInteger height: anInteger bitsPerPixel: anInteger "1, 2, 4, 8, 16, 24 or 32" depth: anInteger palette: aPalette
A Smalltalk image stores the pixels in the variable bits
as a ByteArray. bitsPerPixel
defines how many bits one pixel occupies. This variable is redundant because there is one subclass for each of the permitted values (1, 2, 4, 8, 16, 24 and 32 bits). The rows of the image are stored one after the other in the bits
byte array. The number of bytes in a row is a multiple of 4 bytes (32 bits). That means, that a row with 1 pixel with 1 bit would occupy 4 bytes.
The depth
specifies how many of the bitsPerPixel are actually used for encoding one color. Take, for example a 16 bits per pixel image where the red, green and blue components are encoded with 5 bits. This image would have a depth of 15 and 1 bit per pixel is unused.
The palette
translates between the bits of the pixel to a color. Either, the bits are interpreted directly as RGB value, as in the above example, or the bits are used as an index into a list of colors.
Colors in Smalltalk are always RGB colors or, for masks, coverage values.
Images in PDF
<< /Type /XObject /Subtype /Image /Width anInteger /Height anInteger /BitsPerComponent anInteger % 1, 2, 4, 8 or 16 /ColorSpace aColourSpace >> stream aByteString endstream
In PDF images are defined by the number of bits per color component (1, 2, 4, 8 or 16 bit). The colorspace defines the number of components and their layout for a pixel. Any colorspace permitted in PDF can be used (see Colours in PDF). For images converted from Smalltalk, only /DeviceRGB
(3 components) and /DeviceGray
(1 component) and /Indexed
(1 component) are relevant. The /Indexed
colorspace in PDF can only hold up to 255 colors.
A PDF.ImageXObject
is a stream whose contents is a byte string with the pixel bits. A row ends at the byte boundary. A row with one pixel with 3 components of 1 bits (= 3 bits), uses 1 byte (8 bit) for the row. As any stream, ImageXObjects can be compressed using filters. By default, /FlateDecode
(zip) is used.
The optional attribute /Decode
is used to interpolate the pixel bits. A RGB pixel with 5 bits per component would have a /Decode array to limit the range. Still, 8 bits are used per component so that 3×3=9 bits are unused per pixel.
Masked Images
Useful images often have a mask, allowing only the masked pixels to be drawn. Smalltalk has the class Graphics.OpaqueImage
for this. An OpaqueImage contains a figure
for the image and a shape
for the mask. The mask is a 1 bit image with the same dimension as the figure and a CoveragePalette
which interprets 0
as transparent
(the background is drawn) and 1
as opaque
(the pixel of the figure ist drawn).
A PDF.ImageXObject
has an optional attribute /Mask
which can hold a mask. The mask is a 1 bit /DeviceGray ImageXObject with the optional attribute /ImageMask
set to true and without a /ColorSpace
attribute. Interestingly, the resolution of the mask need not be the same as the resolution of the regular image. Since PDF interprets the bits in the opposite way (“0 shall mark the page with the current colour, and a 1 shall leave the previous contents unchanged”), converted masks have a /Decode array of [1 0]
instead of the default [0 1]
.
When converting an OpaqueImage to PDF with asPDF
, an ImageXObject with a mask is created. Conversely, converting an ImageXObject with a mask with asSmalltalkValue
will produce an OpaqueImage. There are also UI.Icon
objects with a similar layout as OpaqueImage. Icons do understand asPDF
, but the reverse conversion will result in an OpaqueImage (from which an icon can be created easily).
Alpha Blended Images
Images with gradual transparency are Graphics.AlphaCompositedImage
s in Smalltalk. They use a Depth32Image with 1 byte for alpha and 3 bytes for RGB. PDF.ImageXObjects use the optional attribute /SMask
to hold a 8 bit /DeviceGray /SoftMaskImage
for the alpha information.
Implementation
The conversion methods are implemented in the Graphics.Image
hierarchy. To convert a Smalltalk image to PDF with asPDF
, the method writePixelsTo: anImageXObject
transfers the actual pixels from the Smalltalk to the PDF image. The other direction uses the method readPixelsFrom: anImageXObject
which transfers the PDF pixels to the Smalltalk image.
The default behavior is to transfer the pixels one by one. For each pixel, the bits are read from the specified location in the source image bytes and interpreted as color (valueAtPoint:
). This color is then converted to the target bits which are written to the specified location in the target image bytes (valueAtPoint:put:
). While these two pixel accessors are correct and well tested for any kind of image, they are very slow.
This default implementation (readPixelsByPixelFrom: anImageXObject
and writePixelsByPixelTo: anImageXObject
) is the reference for testing and the baseline for performance benchmarks (see the class ImageConversionBenchmarks
in package [PDF Development]
).
Some conversions can be greatly sped up by exploiting the internal byte organization of the image bits and transfering them directly. While this is possible for many useful forms, it is not possible in general (think of a Smalltalk image with a big palette of more than 255 colors). The following conversions are currently optimized:
- Depth1Image for Black and white images and masks
- Depth24Image with 8 bit RGB
- Depth32Image for 8 bit RGB and BGR images taken from the
Screen
(the first byte is always zero) - Depth32Image for 8 bit ARGB and ABGR
Disclaimer
Not covered are the special filters for various kinds of images:
- RunLengthDecode 8 bit monochrome images
- CCITTFaxDecode CCITT encoded 1 bit monochrome images
- JBIG2Decode JBIG2 encoded 1 bit monochrome images
- DCTDecode JPEG encoded 8 bit grayscale or color images
- JPXDecode JPEG2000 encoded grayscale or color images
These are not implemented (yet), so that it is not possible to extract such images from PDF. Nor is it possible to store images in the most efficient way in a PDF. Just the basic FlateDecode filter is used by default to compress images.