Skip to main content
Skip table of contents

Render separations to frame buffers

📌 Overview

Mako 7.3.0 adds a new method, IJawsRenderer::renderSeparationsToFrameBuffers() to render your choice of separations, directly to frame buffers. Essentially a clone of renderSeparations(), it differs in a number of ways.

  • The user provides a vector of frame buffer info objects that describes the frame buffers that are to be populated

  • The user can specify whether they want 16-bit results in host-endian or default (big endian - as used for PDF, PS, etc.)

Otherwise, the arguments are the same, and it will behave in the same way, down to producing exactly the same pixel results. The usage, apart from the above (and a reordering of some arguments in order to be safe) is as per renderSeparations().

ℹ️ Key Concepts

  1. Argument reordering: Although renderSeparations() can work with some arguments having defaults, this API cannot. For example, it’s no longer possible to default width, height or colour space; these must be explicitly specified so that Mako can check that the buffer allocated matches the requirement exactly. As some of the parameters are no longer defaulted, some arguments are reordered compared to renderSeparations() but this should not be too confusing.

  2. The specification of buffers is designed to be as flexible as possible. The user specifies a buffer for each and every separation that will be rendered, up front. The class IJawsRenderer::CFrameBufferInfo provides the description of the buffer. For each CFrameBufferInfo the user specifies:

    • A pointer to the memory where the first pixel is to be written

    • A stride describing how many bytes to move in order to get to the next scanline in the buffer

    • A pixel stride that describes how many bytes to move in order to get to the next pixel in the buffer. This may be 0, in which case the pixels will be packed together with one sample in the separation immediately following the previous in memory.

  3. The pixel stride, if present, must be large enough. That is, it must be larger (in absolute value) than the storage size of a sample. Mako places no such restriction on the row stride, just in case the user has a creative requirement that needs it. Strides may be negative, allowing upside down or right to left rendering. This also allows separated results to be written into a single buffer. By allocating a single CMYK interleaved buffer for the resulting image, you could configure the CFrameBufferInfo vector such that (8 bit example here):

    • The Cyan buffer info points to the start of the single buffer, and uses a pixel stride of 4.

    • The Magenta buffer info points to the second byte in the single buffer, and also uses a pixel stride of 4.

    • The Yellow buffer info points to the third byte in the single buffer, and also uses a pixel stride of 4.

    • The Black buffer info points to the fourth byte in the single buffer, and also uses a pixel stride of 4.

It’s also possible to use a different order for the components, insert a pad (say for RGBX results) or whatever else you need. The actual rendering is the same as renderSeparations(), it's just a different output format. This also means that options like antialiasing will work without issue. The other major change is that it supports an arbitrary number of separations. This allows us to support 4096 separations as per renderSeparations(), even while rendering to frame buffers.

⌨️ Sample Code

See sample code below for a comparison of renderSeparations() and renderSeparationsToFrameBuffers. The same image is rendered to tiff files using the two different methods for you to see how to achieve the same output with the new API. You can also find the code here: RenderSeparationsToFrameBuffersComparison.cpp, RenderSeparationsToFrameBuffersComparison.cs.

C++

C#

CPP
const auto fixedPage = page->edit();
        
// Set image dimensions + colorspace
double resolution = 576.0;
const auto bounds = FRect(0, 0, page->getWidth(), page->getHeight());
auto pixelWidth = static_cast<uint32_t>(lround(bounds.dX / 96.0 * resolution));
auto pixelHeight = static_cast<uint32_t>(lround(bounds.dY / 96.0 * resolution));
const auto depth = 8;
auto testspace = IDOMColorSpaceDeviceCMYK::create(mako);

// Create spot color lists
const auto inks = IRendererTransform::findInks(mako, fixedPage);
CU8StringVect componentNames;
CSpotColorNames ignoreSpotColorNames;
CSpotColorNames retainSpotColorNames;

for (auto i = 0; i < testspace->getNumComponents(); i++)
{
    componentNames.append(testspace->getColorantName(i));
}

for (const auto& ink : inks)
{
    auto inkName = ink.inkName;
    if (spotsToIgnore.contains(inkName))
    {
        ignoreSpotColorNames.append(inkName);
    }
    else if (spotsToRetain.contains(inkName))
    {
        retainSpotColorNames.append(ink.inkName);
        componentNames.append(ink.inkName);
    }
    else 
        continue; // If a spot color is not specified in either list it will be represented in the process separations by default, so we do not need to do anything
}

// Render using renderSeparations()
auto render = IJawsRenderer::create(mako);

CEDLVector<IDOMImagePtr> images = render->renderSeparations(
    fixedPage, 
    depth,
    testspace,
    /*halftone=*/ true,
    bounds,
    pixelWidth,
    pixelHeight,
    retainSpotColorNames,
    /*optional content*/ IOptionalContentPtr(),
    eOCEView,
    CU8StringVect(), // extra components (none)
    /*useOverprintPreview*/ false,
    /*blackPointCompensation*/ 0, 
    ignoreSpotColorNames
);

// Prepare frame buffers
auto numComponents = componentNames.size();

std::vector<std::vector<uint8_t>> buffers(numComponents);
auto frameBuffers = IJawsRenderer::CFrameBufferInfoVect(numComponents);

for (uint8_t bufferIndex = 0; bufferIndex < numComponents; ++bufferIndex)
{
    // One byte per pixel (depth = 8, single component per plane)
    buffers[bufferIndex].resize(pixelHeight * pixelWidth);
    frameBuffers[bufferIndex].buffer = buffers[bufferIndex].data();
    frameBuffers[bufferIndex].rowStride = static_cast<int>(pixelWidth);
    frameBuffers[bufferIndex].pixelStride = 0;
}

// Render using renderSeparationsToFrameBuffers()
render->renderSeparationsToFrameBuffers(
    fixedPage,
    depth,
    /*hostEndian=*/ true,
    pixelWidth,
    pixelHeight,
    testspace,
    frameBuffers,
    0,
    bounds,
    retainSpotColorNames,
    IOptionalContentPtr(),
    eOCEView,
    CU8StringVect(), // extra components (none)
    /*alphGeneration*/ false,
    /*bandMemorySize*/ 0,
    ignoreSpotColorNames
);

// write the outputs to tiff files to compare
for (uint32 j = 0; j < numComponents; j++)
{
    // From renderSeparations() images
    EDLSysString tiffFileName = std::format(
        "{0}_regular_{1}.tif",
        inputFile.substr(0, inputFile.size() - 4),
        componentNames[j]).c_str();
    IDOMTIFFImage::encode(
        mako,
        images[j],
        IOutputStream::createToFile(mako, tiffFileName)
      );

    // From frame buffers
    IImageFrameWriterPtr frame;
    tiffFileName = std::format(
        "{0}_frameBuffer_{1}.tif",
        inputFile.substr(0, inputFile.size() - 4),
        componentNames[j]).c_str();
    (void)IDOMTIFFImage::createWriterAndImage(
        mako,
        frame,
        IDOMColorSpaceDeviceGray::create(mako),
        pixelWidth,
        pixelHeight, 
        depth,
        96.0,
        96.0,
        IDOMTIFFImage::eTCAuto,
        IDOMTIFFImage::eTPNone,
        eIECNone, 
        false,
        IInputStream::createFromFile(mako, tiffFileName),
        IOutputStream::createToFile(mako, tiffFileName)
    );
  
    for (uint32 y = 0; y < pixelHeight; y++)
    {
        frame->writeScanLine(&buffers[j][y * pixelWidth * depth / 8]);
    }
    frame->flushData();
}
C#
using var fixedPage = page.getContent();

// Set image dimensions + colorspace
double resolution = 576.0;
var bounds = new FRect(0, 0, page.getWidth(), page.getHeight());
uint pixelWidth = (uint)Math.Round(bounds.dX / 96.0 * resolution);
uint pixelHeight = (uint)Math.Round(bounds.dY / 96.0 * resolution);
const int depth = 8;
var testspace = IDOMColorSpaceDeviceCMYK.create(mako);

// Create spot color lists
var inks = IRendererTransform.findInks(mako, fixedPage);
var componentNames = new List<string>();
var ignoreSpotColorNames = new CEDLVectString();
var retainSpotColorNames = new CEDLVectString();

for (int i = 0; i < testspace.getNumComponents(); i++)
{
    componentNames.Add(testspace.getColorantName((byte)i));
}

foreach (var ink in inks.toVector())
{
    var inkName = ink.getInkName();
    if (spotsToIgnore.Contains(inkName))
    {
        ignoreSpotColorNames.append(inkName);
    }
    else if (spotsToRetain.Contains(inkName))
    {
        retainSpotColorNames.append(ink.getInkName());
        componentNames.Add(ink.getInkName());
    }
    else
        continue; // If a spot color is not specified in either list it will be represented in the process separations by default, so no action needed.
}

// Render using renderSeparations()
var renderer = IJawsRenderer.create(mako);

var images = renderer.renderSeparations(
    fixedPage,
    depth,
    testspace,
    /*halftone=*/ 0,
    bounds,
    pixelWidth,
    pixelHeight,
    retainSpotColorNames,
    /*optional content*/ IOptionalContent.Null(),
    eOptionalContentEvent.eOCEView,
    new CEDLVectString(), // extra components (none)
    /*useOverprintPreview*/ false,
    /*blackPointCompensation*/ 0,
    ignoreSpotColorNames
);

// Prepare frame buffers
int numComponents = componentNames.Count;
int sourceStride = (int)pixelWidth * numComponents;

var buffers = new byte[numComponents][];
var frameBuffers = new CEDLVectCFrameBufferInfo();

for (byte bufferIndex = 0; bufferIndex < numComponents; ++bufferIndex)
{
    // One byte per pixel (depth = 8, single component per plane)
    buffers[bufferIndex] = new byte[pixelHeight * pixelWidth];

    var frameBufferInfo = new IJawsRenderer.CFrameBufferInfo
    {
        bufferOfs = 0,
        rowStride = (int)pixelWidth,
        pixelStride = 0
    };
    frameBuffers.append( frameBufferInfo );
}

// Render using renderSeparationsToFrameBuffers()
renderer.renderSeparationsToFrameBuffers(
    fixedPage,
    depth,
    /*hostEndian=*/ true,
    pixelWidth,
    pixelHeight,
    testspace,
    buffers,
    frameBuffers,
    0,
    bounds,
    new CEDLVectWString(retainSpotColorNames.toArray()),
    IOptionalContent.Null(),
    eOptionalContentEvent.eOCEView,
    new CEDLVectWString(), // extra components (none)
    /*alphGeneration*/ false,
    /*bandMemorySize*/ 0,
    new CEDLVectWString(ignoreSpotColorNames.toArray())
);

// Write the outputs to TIFF files to compare
string stem = Path.Combine(
    Path.GetDirectoryName(inputFile) ?? string.Empty,
    Path.GetFileNameWithoutExtension(inputFile) ?? "output"
);

for (uint j = 0; j < numComponents; j++)
{
    // From renderSeparations() images
    string tiffFileName = $"{stem}_regular_{componentNames[(int)j]}.tif";
    IDOMTIFFImage.encode(
        mako,
        images[j],
        IOutputStream.createToFile(mako, tiffFileName)
    );

    // From frame buffers
    tiffFileName = $"{stem}_frameBuffer_{componentNames[(int)j]}.tif";
    var pair = IDOMTIFFImage.createWriterAndImage(
        mako,
        IDOMColorSpaceDeviceGray.create(mako),
        pixelWidth,
        pixelHeight,
        depth,
        96.0, 96.0,
        IDOMTIFFImage.eTIFFCompression.eTCAuto,
        IDOMTIFFImage.eTIFFPrediction.eTPNone,
        eImageExtraChannelType.eIECNone,
        /*tiled*/ false,
        IInputStream.createFromFile(mako, testFilepath + inputFile),
        IOutputStream.createToFile(mako, tiffFileName)
    );

    IImageFrameWriter frameWriter = pair.frameWriter;

    for (uint y = 0; y < pixelHeight; y++)
    {
        var scanline = new byte[sourceStride];
        Buffer.BlockCopy(buffers[j], (int)(y * pixelWidth), scanline, 0, (int)pixelWidth);
        frameWriter.writeScanLine(scanline);
    }
    frameWriter.flushData();
}

☑️ Conclusion

The IJawsRenderer::renderSeparationsToFrameBuffers() method introduced in Mako 7.3.0 offers a flexible approach to rendering separations directly to frame buffers. By allowing users to specify detailed buffer configurations, including memory pointers and strides, this method provides precise control over the rendering process. The ability to handle an arbitrary number of separations and support for various buffer configurations makes it a powerful tool for advanced rendering needs.

📚 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.

JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.