Creating raster images from scratch

Andrew Markovich

February 09, 2017

The OdGiRasterImage interface gives the ability to implement raster image creation from various types of source data in a few steps, and the resulting raster image will be accepted by all image processing functionality, similar to a raster image loaded from file source. In this article, we will create a gradient image from scratch. We will use the four corner pixel colors that were extracted using the steps previously described in Part 1 of this series.

Create a class inherited from the OdGiRasterImage interface and add some class members to store image parameters:

class GeneratedRasterImage : public OdGiRasterImage
{
    protected:
        OdUInt32 m_pixW, m_pixH;
        PixelFormatInfo m_pf;
        ODCOLORREF m_cornerColors[4];

Implement all required abstract pure-virtual methods that are defined in the OdGiRasterImage interface:

public:
    virtual OdUInt32 pixelWidth() const { return m_pixW; }
    virtual OdUInt32 pixelHeight() const { return m_pixH; }
    virtual OdUInt32 colorDepth() const { return 24; }
    virtual OdUInt32 numColors() const { return 0; }
    virtual ODCOLORREF color(OdUInt32 /*colorIndex*/) const { return 0; }
    virtual OdUInt32 paletteDataSize() const { return 0; }
    virtual void paletteData(OdUInt8* /*bytes*/) const { }
    virtual PixelFormatInfo pixelFormat() const { return m_pf; }
    virtual OdUInt32 scanLinesAlignment() const { return 4; }

Here we simply return the image width, height and pixel format which will be set during raster image construction. Use 24-bits per pixel color depth for the image. Palette-related methods are required for indexed images only (with color depths less than or equal to 8-bits per pixel), so simply stub them.

Add image construction methods which will be required to initialize data inside the GeneratedRasterImage class:

GeneratedRasterImage() : m_pixW(1), m_pixH(1) {}
void configureImage(OdUInt32 nWidth, OdUInt32 nHeight, const PixelFormatInfo &pf, const ODCOLORREF *pColors)
{
    m_pixW = nWidth; m_pixH = nHeight; m_pf = pf;
    for (int i = 0; i < 4; i++)
        m_cornerColors[i] = pColors[i];
}

Finally implement the scanLines image methods:

virtual const OdUInt8* scanLines() const { return NULL; }
virtual void scanLines(OdUInt8* scnLines, OdUInt32 firstScanline, OdUInt32 numLines = 1) const
{
    OdUInt32 scanLen = scanLineSize();
    for (OdUInt32 i = firstScanline; i < firstScanline + numLines; i++)
    {
        OdUInt8 *pScanLine = scnLines + ((i - firstScanline) * scanLen);
        for (OdUInt32 j = 0; j < m_pixW; j++, pScanLine += 3)
            computePixel(j, i, pScanLine);
    }
}

We don’t store pixel data inside our generated raster image class, so the direct scanlines accessor method will always return null in our case. The copy-based scanLines method simply calls the internal computePixel method for each pixel. The computePixel method uses four input colors to interpolate a gradient inside the generated image surface and puts the computed gradient colors into an output pixels buffer:

void computePixel(OdUInt32 x, OdUInt32 y, OdUInt8 *pOutput) const
{
    // Interpolate colors by X-axis
    const double interpolateX = double(x) / (m_pixW - 1);
    const ODCOLORREF bottom = ODRGB((1.0 - interpolateX) * ODGETRED(m_cornerColors[0]) + interpolateX * ODGETRED(m_cornerColors[1]),
          (1.0 - interpolateX) * ODGETGREEN(m_cornerColors[0]) + interpolateX * ODGETGREEN(m_cornerColors[1]),
          (1.0 - interpolateX) * ODGETBLUE(m_cornerColors[0]) + interpolateX * ODGETBLUE(m_cornerColors[1]));
    const ODCOLORREF top = ODRGB((1.0 - interpolateX) * ODGETRED(m_cornerColors[2])   + interpolateX * ODGETRED(m_cornerColors[3]),
          (1.0 - interpolateX) * ODGETGREEN(m_cornerColors[2]) + interpolateX * ODGETGREEN(m_cornerColors[3]),
          (1.0 - interpolateX) * ODGETBLUE(m_cornerColors[2]) + interpolateX * ODGETBLUE(m_cornerColors[3]));
    // Interpolate colors in Y-axis
    const double interpolateY = double(y) / (m_pixH - 1);
    const ODCOLORREF color = ODRGB((1.0 - interpolateY) * ODGETRED(bottom) + interpolateY * ODGETRED(top),
          (1.0 - interpolateY) * ODGETGREEN(bottom) + interpolateY * ODGETGREEN(top),
          (1.0 - interpolateY) * ODGETBLUE(bottom)  + interpolateY * ODGETBLUE(top));
    pOutput[0] = ODGETRED(color); pOutput[1] = ODGETGREEN(color); pOutput[2] = ODGETBLUE(color);
}

The example image class implementation is complete. Now create an instance of the GeneratedRasterImage class, initialize it and store the output raster image for file saving or further experiments:

OdGiRasterImagePtr pOutputImage;
{
    OdSmartPtr<GeneratedRasterImage> pImage = OdRxObjectImpl<GeneratedRasterImage>::createObject();
    pImage->configureImage(256, 256, pInputImage->pixelFormat(), inputColors);
    pOutputImage = pImage;
}

Now we can store our generated raster image using code from “Saving raster images in a file” in Part 1 of this series, and look at the resulting file: