Processing images with IDOMImage
📌 Overview
Mako's IDOMImage class offers a convenient, format-independent tool set for handling image data.
ℹ️ Key Concepts
An
IDOMImagecan be created from an input file stream, with built-in support for several image decoders - JPEG, TIFF, PNG, PSD etc.An
IDOMImagecan be created by rendering PDF page content (in its entirety or a section thereof) with complete control over resolution, colorspace, color management and more
You cannot directly add an IDOMImage to the Mako DOM, you first need to create a node for it such as an IDOMPathNode.
The first step to adding an
IDOMImageto a PDF is to use a method such asIDOMPathNode::createImage()to create a node for it which can then be added to the DOM.An
IDOMImagecan be written to an output file stream, with built-in support for several image encoders - JPEG, TIFF, PNG etc.
💪 Usage
Basic usage
Various methods provide complete access to the image data. There are APIs to return information about an image - width and height in pixels, colorspace, resolution, bits-per sample (e.g. 8- or 16-bit) and more, while others let you set the properties of an image. For example here are the steps to add an image created using IDOMImage to a PDF.
Adding anIDOMImage to a PDF
Images are not a node type in Mako so to add them to a PDF you need to create a node, such as an IDOMPathNode, for the image. There is a dedicated API for this called IDOMPathNode::createImage() which requires both an IDOMImage and also an FRect which defines the desired position and size of the image on the page. Once you have the IDOMPathNode you can then add this to the page using IDOMFixedPage::appendChild(). See below for a code snippet showing how to load an image from a file and add it to a PDF. It also uses the image’s attributes to calculate the values for the FRect.
// Load an image from a file
const auto image = IDOMPNGImage::create(mako, IInputStream::createFromFile(mako, testFilePath + "makologo.png"));
// Get image attributes from the frame
const IImageFramePtr imageFrame = image->getImageFrame(mako);
const double width = imageFrame->getWidth() / imageFrame->getXResolution() * 96.0;
const double height = imageFrame->getHeight() / imageFrame->getYResolution() * 96.0;
// Add the image to a fixedPage
const auto imageNode = IDOMPathNode::createImage(mako, image, FRect(0.0, 0.0, width, height));
fixedPage->appendChild(imageNode);
// Load an image from a file
var image = IDOMPNGImage.create(mako, IInputStream.createFromFile(mako, testFilepath + "makologo.png"));
// Get image attributes
var imageFrame = image.getImageFrame(mako);
var width = imageFrame.getWidth();
var height = imageFrame.getHeight();
// Add the image to a fixedPage
var imageNode = IDOMPathNode.createImage(mako, image, new FRect(0.0, 0.0, width, height));
fixedPage.appendChild(imageNode);
// Load image from file
var image = IDOMPNGImage.create(factory, IInputStream.createFromFile(factory, testFilepath + "makologo.png"));
// Get image attributes
var imageFrame = image.getImageFrame(factory);
var width = imageFrame.getWidth();
var height = imageFrame.getHeight();
// Add image to fixedPage
var imageNode = IDOMPathNode.createImage(factory, image, new FRect(0.0, 0.0, width, height));
fixedPage.appendChild(imageNode);
# Load image from file
image = IDOMPNGImage.create(factory, IInputStream.createFromFile(factory, testFilepath + "makologo.png"))
# Get image attributes
imageFrame = image.getImageFrame(factory)
width = imageFrame.getWidth()
height = imageFrame.getHeight()
# Add image to fixedPage
imageNode = IDOMPathNode.createImage(factory, image, FRect(0.0, 0.0, width, height))
fixedPage.appendChild(imageNode)
# Assign content to a page
page.setContent(fixedPage)
The reverse of this process, extracting images from the Mako DOM, is slightly more complicated. See Mako Image Extract for details of how to do this.
Advanced Usage
Extracting a sub-section of an image
The next example in this KB article shows how to extract a sub-section of an image and copy it to a new image. It demonstrates the use of streams, which are a convenient and memory-efficient way to handle image data. Both C++ and C# examples are provided as the method for addressing scanline data is slightly different due to differences between the two languages. This example also makes use of a bit scaler, a filter that can be applied to an image to regularize color component sample size.
The image used in this example is here:
⌨️ Code Examples
In the table below you can find functions for extracting a subsection of an image in C++, C#, Java and Python. For the full implementation, see Visual Studio Projects/GetPartialImage , Java Projects/GetPartialImage or Python Projects/GetPartialImage .
Creating a writer and image
// Get some details of the original image
auto imageFrame = image->getImageFrame(mako);
auto bps = imageFrame->getBPS();
const auto colorSpace = imageFrame->getColorSpace();
const auto stride = imageFrame->getRawBytesPerRow();
const auto bpp = imageFrame->getNumChannels() * bps / 8;
// Create temp reader/writer
IRAInputStreamPtr reader;
IRAOutputStreamPtr writer;
mako->getTempStore()->createTemporaryReaderWriterPair(reader, writer);
const IInputStreamPtr inStream = IInputStream::createFromLz4Compressed(mako, reader);
const IOutputStreamPtr outStream = IOutputStream::createToLz4Compressed(mako, writer);
IImageFrameWriterPtr frameWriter;
// Create the new raw image and writer
auto subImage = IDOMRawImage::createWriterAndImage(
mako,
frameWriter,
colorSpace,
static_cast<uint32>(subImageRect.dX),
static_cast<uint32>(subImageRect.dY),
bps,
imageFrame->getXResolution(),
imageFrame->getYResolution(),
eImageExtraChannelType::eIECNone,
inStream, outStream);
// Get some details of the original image
var imageFrame = image.getImageFrame(mako);
var bps = imageFrame.getBPS();
var colorSpace = imageFrame.getColorSpace();
var stride = imageFrame.getRawBytesPerRow();
var bpp = imageFrame.getNumChannels() * bps / 8;
// Create temp reader/writer
var (reader, writer) = mako.getTempStore().createTemporaryReaderWriterTuple();
IInputStream inStream = IInputStream.createFromLz4Compressed(mako, reader);
IOutputStream outStream = IOutputStream.createToLz4Compressed(mako, writer);
// Create the new raw image and writer
var imageAndWriter = IDOMRawImage.createWriterAndImage(
mako,
colorSpace,
(uint)subImageRect.dX,
(uint)subImageRect.dY,
bps,
imageFrame.getXResolution(),
imageFrame.getYResolution(),
eImageExtraChannelType.eIECNone,
inStream, outStream);
var subImage = imageAndWriter.domImage;
var frameWriter = imageAndWriter.frameWriter;
// Get some details of the original image
var imageFrame = image.getImageFrame(factory);
var bps = imageFrame.getBPS();
var colorSpace = imageFrame.getColorSpace();
var stride = Math.toIntExact(imageFrame.getRawBytesPerRow());
var bpp = imageFrame.getNumChannels() * bps / 8;
// Create temp reader/writer
var temp = mako.getTempStore().createTemporaryReaderWriter();
var inStream = IInputStream.createFromLz4Compressed(factory, temp.toIInputStream());
var outStream = IOutputStream.createToLz4Compressed(factory, temp.toIOutputStream());
// Create the new raw image and writer
var imageAndWriter = IDOMRawImage.createWriterAndImage(
mako,
colorSpace,
(long) subImageRect.getDX(),
(long) subImageRect.getDY(),
(short) bps,
imageFrame.getXResolution(),
imageFrame.getYResolution(),
eImageExtraChannelType.eIECNone,
inStream,
outStream
);
var subImage = imageAndWriter.getDomImage();
var frameWriter = imageAndWriter.getFrameWriter();
# Get some details of the original image
image_frame = image.getImageFrame(factory)
bps = image_frame.getBPS()
color_space = image_frame.getColorSpace()
stride = image_frame.getRawBytesPerRow()
bpp = image_frame.getNumChannels() * bps // 8
# Create temporary compressed reader/writer pair
temp = jawsMako.getTempStore().createTemporaryReaderWriter()
in_stream = IInputStream.createFromLz4Compressed(factory, temp.toIInputStream())
out_stream = IOutputStream.createToLz4Compressed(factory, temp.toIOutputStream())
# Create the new raw image and writer
image_and_writer = IDOMRawImage.createWriterAndImage(
jawsMako,
color_space,
int(sub_image_rect.dX),
int(sub_image_rect.dY),
bps,
image_frame.getXResolution(),
image_frame.getYResolution(),
eIECNone,
in_stream,
out_stream
)
sub_image = image_and_writer.domImage
frame_writer = image_and_writer.frameWriter
Write the image to a file
// Prepare buffers
CEDLSimpleBuffer rowBuffer(stride);
// Move row pointer down to the first row of the partial image
imageFrame->skipScanLines(static_cast<uint32>(subImageRect.y));
// Copy the portion of the scanlines that we need
for (uint32 i = 0; i < static_cast<uint32>(subImageRect.dY); i++)
{
imageFrame->readScanLine(&rowBuffer[0], rowBuffer.size());
frameWriter->writeScanLine(&rowBuffer[static_cast<uint32>(subImageRect.x) * bpp]);
}
frameWriter->flushData();
return subImage;
imageAndWriter.domImage = null;
imageAndWriter.frameWriter = null;
// Prepare buffers
var rowBuffer = new byte[stride];
var targetRowBuffer = new byte[(uint)subImageRect.dX * bpp];
// Move row pointer down to the first row of the partial image
imageFrame.skipScanLines((uint)subImageRect.y);
// Copy the portion of the scanlines that we need
for (uint i = 0; i < (uint)subImageRect.dY; i++)
{
imageFrame.readScanLine(rowBuffer);
Array.Copy(rowBuffer, (uint)subImageRect.x * bpp, targetRowBuffer, 0, targetRowBuffer.Length);
frameWriter.writeScanLine(targetRowBuffer);
}
frameWriter.flushData();
return subImage;
// Prepare buffers
byte[] rowBuffer = new byte[stride];
byte[] targetRowBuffer = new byte[(int) subImageRect.getDX() * bpp];
// Move row pointer down to the first row of the partial image
imageFrame.skipScanLines((long) subImageRect.getY());
// Copy the portion of the scanlines that we need
for (int i = 0; i < (int) subImageRect.getDY(); i++) {
imageFrame.readScanLine(rowBuffer);
System.arraycopy(rowBuffer, (int) subImageRect.getX() * bpp, targetRowBuffer, 0, targetRowBuffer.length);
frameWriter.writeScanLine(targetRowBuffer);
}
frameWriter.flushData();
return subImage;
# Prepare buffers
row_buffer = bytearray(stride)
target_row_buffer = bytearray(int(sub_image_rect.dX) * bpp)
# Move row pointer down to the first row of the partial image
image_frame.skipScanLines(int(sub_image_rect.y))
# Copy the portion of the scanlines that we need
for _ in range(int(sub_image_rect.dY)):
image_frame.readScanLine(row_buffer, stride)
start = int(sub_image_rect.x) * bpp
target_row_buffer[:] = row_buffer[start:start + len(target_row_buffer)]
frame_writer.writeScanLine(target_row_buffer)
frame_writer.flushData()
return sub_image
☑️ Conclusion
The IDOMImage class in Mako provides a robust and flexible framework for handling image data across various formats. By leveraging its capabilities, users can efficiently create, manipulate, and output images with precise control over resolution, color space, and other properties. The examples provided demonstrate both basic and advanced usage, showcasing the versatility of IDOMImage in different scenarios.
📚 Additional Resources
If you need additional help, see our API documentation for detailed information on class/method usage, or raise a support ticket via our customer portal.