Created Date: 02 Feb, 2024 17:46
Last Modified Date: 04 Feb, 2024 12:49


Introduction

Mako's IDOMImage class offers a convenient, format-independent tool set for handling image data.

  • An IDOMImage can be created from an input file stream, with built-in support for several image decoders - JPEG, TIFF, PNG, PSD etc. 
  • An IDOMImage can 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 IDOMImage objects, making them easy to work with
  • For example, the first step to adding an image to a PDF page is to create it as an IDOMImage 
  • An IDOMImage can be written to an output file stream, with built-in support for several image encoders - JPEG, TIFF, PNG etc.

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.

The 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 exampe also makes use of a bit scaler, a filter that can be applied to an image to regularize color component sample size.

/* -----------------------------------------------------------------------
 * <copyright file="MakoPartialImage.cpp.cpp" company="Global Graphics Software Ltd">
 *  Copyright (c) 2022-2024 Global Graphics Software Ltd. All rights reserved.
 * </copyright>
 * <summary>
 *  This example is provided on an "as is" basis and without warranty of any kind.
 *  Global Graphics Software Ltd. does not warrant or make any representations
 *  regarding the use or results of use of this example.
 * </summary>
 * -----------------------------------------------------------------------
 */

#include <iostream>

#include <jawsmako/jawsmako.h>
#include <jawsmako/pdfoutput.h>

using namespace JawsMako;
using namespace EDL;

IDOMImagePtr getPartialImage(const IJawsMakoPtr& mako, IDOMImagePtr image, const FRect& subImageRect);

int main()
{
    try
    {
        const auto mako = IJawsMako::create();
        mako->enableAllFeatures(mako);
        const U8String testFilePath = R"(..\..\TestFiles\)";
        const IDOMImagePtr image = 
            IDOMJPEGImage::create(mako, IInputStream::createFromFile(mako, testFilePath + "WEV_086.JPG"));

        const auto partialImage = getPartialImage(mako, image, FRect(230, 230, 400, 250));
        IDOMPNGImage::encode(mako, partialImage, IOutputStream::createToFile(mako, "JustTheKayak.png"));
    }
    catch (IError& e)
    {
        const String errorFormatString = getEDLErrorString(e.getErrorCode());
        std::wcerr << L"Exception thrown: " << e.getErrorDescription(errorFormatString) << std::endl;
        return static_cast<int>(e.getErrorCode());
    }
    catch (std::exception& e)
    {
        std::wcerr << L"std::exception thrown: " << e.what() << std::endl;
        return 1;
    }

    return 0;
}

/// <summary>
 /// Extract part of an image, the area of which is described by an FRect where:
 ///   x,y are the top left corner, in pixels
 ///   dX, dY are the width and height
 /// </summary>
 /// <param name="mako"></param>
 /// <param name="image"></param>
 /// <param name="subImageRect"></param>
 /// <returns>The resulting IDOMImage</returns>
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;
}

CPP



/* -----------------------------------------------------------------------
 * <copyright file="MakoPartialImage.cs" company="Global Graphics Software Ltd">
 *  Copyright (c) 2022-2024 Global Graphics Software Ltd. All rights reserved.
 * </copyright>
 * <summary>
 *  This example is provided on an "as is" basis and without warranty of any kind.
 *  Global Graphics Software Ltd. does not warrant or make any representations
 *  regarding the use or results of use of this example.
 * </summary>
 * -----------------------------------------------------------------------
 */

using JawsMako;

namespace MakoPartialImage;

internal class Program
{
    static void Main(string[] args)
    {
        try
        {
            var mako = IJawsMako.create();
            IJawsMako.enableAllFeatures(mako);
            var testFilePath = @"..\..\..\..\TestFiles\";
            IDOMImage image = IDOMJPEGImage.create(mako,
                IInputStream.createFromFile(mako, testFilePath + "WEV_086.JPG"));

            var partialImage = GetPartialImage(mako, image, new FRect(230, 230, 400, 250));
            IDOMPNGImage.encode(mako, partialImage, IOutputStream.createToFile(mako, "JustTheKayak.png"));
        }
        catch (MakoException e)
        {
            Console.WriteLine($"Mako exception thrown: {e.m_msg}");
        }
        catch (Exception e)
        {
            Console.WriteLine($"Exception thrown: {e}");
        }
    }

    /// <summary>
    /// Extract part of an image, the area of which is described by an FRect where:
    ///   x,y are the top left corner, in pixels
    ///   dX, dY are the width and height
    /// </summary>
    /// <param name="mako"></param>
    /// <param name="image"></param>
    /// <param name="subImageRect"></param>
    /// <returns>The resulting IDOMImage</returns>
    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;
    }
}
C#

The image used in this example is here: