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
Images on a PDF page are represented in the Mako DOM as IDOMImageobjects, making them easy to work with
The first step to adding an image to a PDF page is to create it as an IDOMImage
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.
Advanced Usage
Extracting a sub-section of an image
The first 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 (The C# example should be followed for creating similar code for Java or Python). 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:
Customized image handling
Although the built-in TIFF input class (IDOMTiffImage) can handle multi-channel TIFFs, until Mako 8.1, did not interpret the custom PHOTOSHOP tags in the TIFF that provide the information such as the colorant name.
This means the code example described below is less relevant, but it still serves as an example of integrating a third-party image library with Mako.
The following sample implements a custom TIFF reader that checks for the presence of such tags. If they are present, it uses this information to create an IDOMImage backed by a DeviceN color with the additional channels.
⌨️ Code Examples
In the table below you can find functions for extracting a subsection of an image in C++ and C#. For the full implementation, as well as a customized image handling example project, see MakoPartialImage.cpp, MakoPartialImage.cs and MakoImage2PDF.cpp.
C++
C#
CPP
IDOMImagePtr getPartialImage(const IJawsMakoPtr& mako, IDOMImagePtr image, const FRect& subImageRect)
{
// Get some details of the original image
auto imageFrame = image->getImageFrame(mako);
auto bps = imageFrame->getBPS();
if (bps < 8)
{
image = IDOMFilteredImage::create(mako, image, IDOMImageBitScalerFilter::create(mako, 8));
imageFrame = image->getImageFrame(mako);
bps = 8;
}
else if (bps != 8 && bps != 16)
{
image = IDOMFilteredImage::create(mako, image, IDOMImageBitScalerFilter::create(mako, 16));
imageFrame = image->getImageFrame(mako);
bps = 16;
}
const auto colorSpace = imageFrame->getColorSpace();
const auto stride = imageFrame->getRawBytesPerRow();
const auto bpp = imageFrame->getNumChannels() * bps / 8;
// Check the requested area is within the bounds of the original
const auto originalImageRect = new FRect(0.0, 0.0, imageFrame->getWidth(), imageFrame->getHeight());
if (!originalImageRect->containsRect(subImageRect))
return {};
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;
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);
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;
}
C#
static IDOMImage GetPartialImage(IJawsMako mako, IDOMImage image, FRect subImageRect)
{
// Get some details of the original image
var imageFrame = image.getImageFrame(mako);
var bps = imageFrame.getBPS();
if (bps < 8)
{
image = IDOMFilteredImage.create(mako, image, IDOMImageBitScalerFilter.create(mako, 8));
imageFrame = image.getImageFrame(mako);
bps = 8;
}
else if (bps != 8 && bps != 16)
{
image = IDOMFilteredImage.create(mako, image, IDOMImageBitScalerFilter.create(mako, 16));
imageFrame = image.getImageFrame(mako);
bps = 16;
}
var colorSpace = imageFrame.getColorSpace();
var stride = imageFrame.getRawBytesPerRow();
var bpp = imageFrame.getNumChannels() * bps / 8;
// Check the requested area is within the bounds of the original
var originalImageRect = new FRect(0.0, 0.0, imageFrame.getWidth(), imageFrame.getHeight());
if (!originalImageRect.containsRect(subImageRect))
return IDOMImage.Null();
var (reader, writer) = mako.getTempStore().createTemporaryReaderWriterTuple();
IInputStream inStream = IInputStream.createFromLz4Compressed(mako, reader);
IOutputStream outStream = IOutputStream.createToLz4Compressed(mako, 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;
imageAndWriter.domImage = null;
imageAndWriter.frameWriter = null;
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;
}
☑️ 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.