Native 2D Barcodes support yet for Cloud?

This has come up in past posts, but I didn’t find anything recent. Has Epicor come up with a good native option for cloud customers to use 2D barcodes in SSRS yet? I know 3rd party tools exist but require to run on the report server.

Just curious what the most recent update is on this and what options exist for 2026.

Thanks,
-B

1 Like

I am looking for the post that mentions it but I believe this is coming with the transition to Bold Reports.

There are 3rd party solutions for SaaS we have been printing QR codes since we went to cloud and have had 0 issues so far.

(Countdown to something breaking)

2 Likes

What solution are you using to enable it for SaaS?

1 Like

Yeah we are looking at ID automation. That seems to be the best option. I posted this just to make sure my search was correct and didn’t show any updates from Epicor to support them natively before pursuing the 3rd party option.

2 Likes

Just a spitball at a way to natively implement now,

2D barcode spec is well documented.

You can pass base64 data into ssrs on the fly via CallContextHeader.BpmData, but does have the header size limitations. I don’t remember what formats SSRS rendering engine supports, but a QR code being a black and white image with relatively simple geometry should be compact in filesize at the right file format.

I’m imagining a function that gets called in a BPM Pre on SubmitToAgent that generates the applicable qr codes, slaps them into CallContextClient, then utilized in the SSRS report.

I could probably take a look at cooking that up if anyone’s interested.

It would have to render as a Data Matrix type, not a QR code.

1 Like

I suppose there would be no reason not to accept an additional input for symbology and various other specifications like guard bar size, etc.

Just curious why the Data Matrix preference?

In two unique customer scenarios involving a % sign and a Zebra scanner, I’ve seen issues with a Bartender generated Data Matrix that when switching to QR suddenly worked. No good explanation as to why, but it has skewed my preference since then.

Data Matrix is by default more information dense than a QR, so I would think this may be an artifact of printer DPI/head cleanliness/age?

2 Likes

Evil IO golf solution: A QR/Data Matrix grid boils down to a few dozen bytes worth of points. Send a tiny blob with each bit representing a white/black point, mangle an SSRS table into grid shape, and map specific bits to each cell’s conditional background color.

/ducks

3 Likes

It is a requirement of some customers.

This seemed like a fun challenge, so, done.



Piggybacked a function off zxing, which epicor has included in SaaS on server side assemblies…

Use the function in a pre bpm on .SubmitToAgent for your report of choice. Put the base64png output into CallContextBpmData in a string column. Add CallContextBpmData to your report as a new datasource (I have another post here with a how-to on this)

Edit: Corrected to CallContextBpmData and link to post: SSRS - Feeding report style number/description into RDL - #8 by GabeFranco

This will give you availability to the generated 2d barcode within your report. I’m doing absolutely no validation here so if you don’t get an output barcode take a look at the log output in msg. You’ve probably fed in some data that the selected barcode type does not support.

Possible values for Symbology:

QR
MICROQR
DATAMATRIX
CODE128
CODE39
AZTEC
PDF417
EAN13
EAN8
UPCA
CODABAR
ITF

Old & Nasty
//References: zxing.dll
//Signature:
//Input: symbology (string), data (string), scale (decimal)
//Output: msg (string), base64png (string)
//Usings:
//using System.IO;
//using System.IO.Compression;
//using ZXing;
//using ZXing.Common;

msg = "";
base64png = null;
var log = new Action<string>(txt => msg += $"[{txt}] § ");
log("Pipeline Initialized");
try
{
Func<uint, byte[]> GetBigEndianBytes = value => new byte[]
{
(byte)((value >> 24) & 255u),
(byte)((value >> 16) & 255u),
(byte)((value >> 8) & 255u),
(byte)(value & 255u)
};
log("GetBigEndianBytes Created");
Func<byte[], uint> ComputeCrc32 = bytes =>
{
uint crc = 4294967295u;
if (bytes == null || bytes.Length == 0)
{
return crc ^ 4294967295u;
}
for (int index = 0; index < bytes.Length; index++)
{
crc ^= bytes[index];
for (int bit = 0; bit < 8; bit++)
{
bool applyPolynomial = (crc & 1u) == 1u;
crc >>= 1;
if (applyPolynomial)
{
crc ^= 3988292384u;
}
}
}
return crc ^ 4294967295u;
};
log("ComputeCrc32 Created");
Func<byte[], uint> ComputeAdler32 = buffer =>
{
const uint modulus = 65521u;
uint sumA = 1u;
uint sumB = 0u;
if (buffer == null || buffer.Length == 0)
{
return 1u;
}
for (int index = 0; index < buffer.Length; index++)
{
sumA = (sumA + buffer[index]) % modulus;
sumB = (sumB + sumA) % modulus;
}
return (sumB << 16) | sumA;
};
log("ComputeAdler32 Created");
Action<MemoryStream, byte[], byte[]> WritePngChunk = (stream, chunkType, chunkData) =>
{
if (stream == null)
{
throw new ArgumentNullException("stream");
}
if (chunkType == null || chunkType.Length != 4)
{
throw new ArgumentException("PNG chunk types must be exactly four bytes long.", "chunkType");
}
byte[] safeChunkData = chunkData ?? new byte[0];
byte[] payloadLengthBytes = GetBigEndianBytes((uint)safeChunkData.Length);
stream.Write(payloadLengthBytes, 0, payloadLengthBytes.Length);
stream.Write(chunkType, 0, chunkType.Length);
if (safeChunkData.Length > 0)
{
stream.Write(safeChunkData, 0, safeChunkData.Length);
}
byte[] crcInput = new byte[chunkType.Length + safeChunkData.Length];
Buffer.BlockCopy(chunkType, 0, crcInput, 0, chunkType.Length);
if (safeChunkData.Length > 0)
{
Buffer.BlockCopy(safeChunkData, 0, crcInput, chunkType.Length, safeChunkData.Length);
}
byte[] crcBytes = GetBigEndianBytes(ComputeCrc32(crcInput));
stream.Write(crcBytes, 0, crcBytes.Length);
};
log("WritePngChunk Created");
Func<byte[], byte[]> CompressToZlib = buffer =>
{
byte[] safeBuffer = buffer ?? new byte[0];
byte[] deflateBytes = null;
using (MemoryStream compressedStream = new MemoryStream())
{
using (DeflateStream deflateStream = new DeflateStream(compressedStream, CompressionMode.Compress, true))
{
deflateStream.Write(safeBuffer, 0, safeBuffer.Length);
}
deflateBytes = compressedStream.ToArray();
}
byte[] zlibPayload = new byte[2 + deflateBytes.Length + 4];
zlibPayload[0] = 120;
zlibPayload[1] = 156;
if (deflateBytes.Length > 0)
{
Buffer.BlockCopy(deflateBytes, 0, zlibPayload, 2, deflateBytes.Length);
}
byte[] adlerBytes = GetBigEndianBytes(ComputeAdler32(safeBuffer));
Buffer.BlockCopy(adlerBytes, 0, zlibPayload, 2 + deflateBytes.Length, adlerBytes.Length);
return zlibPayload;
};
log("CompressToZlib Created");
Func<Dictionary<string, BarcodeFormat>> GetSymbologyFormats = () => new Dictionary<string, BarcodeFormat>
{
{ "QR",         BarcodeFormat.QR_CODE },
{ "MICROQR",    BarcodeFormat.QR_CODE },
{ "DATAMATRIX", BarcodeFormat.DATA_MATRIX },
{ "CODE128",    BarcodeFormat.CODE_128 },
{ "CODE39",     BarcodeFormat.CODE_39 },
{ "AZTEC",      BarcodeFormat.AZTEC },
{ "PDF417",     BarcodeFormat.PDF_417 },
{ "EAN13",      BarcodeFormat.EAN_13 },
{ "EAN8",       BarcodeFormat.EAN_8 },
{ "UPCA",       BarcodeFormat.UPC_A },
{ "CODABAR",    BarcodeFormat.CODABAR },
{ "ITF",        BarcodeFormat.ITF }
};
log("GetSymbologyFormats Created");
log($"Original Inputs -> Symbology: {symbology}, DataLength: {(data != null ? data.Length.ToString() : "null")}, Scale: {scale}");
symbology = string.IsNullOrEmpty(symbology) ? "QR" : symbology;
data = string.IsNullOrEmpty(data) ? "https://example.com" : data;
scale = scale <= 0m ? 1.0m : scale;
log($"Defaulted Inputs -> Symbology: {symbology}, DataLength: {data.Length}, Scale: {scale}");
log("Validating Inputs");
if (string.IsNullOrWhiteSpace(data))
{
throw new ArgumentException("The data string to encode cannot be null, empty, or whitespace.", "data");
}
if (string.IsNullOrWhiteSpace(symbology))
{
throw new ArgumentException("The symbology definition must be provided.", "symbology");
}
log("Inputs Validated Successfully");
log("Resolving Symbology");
string normalizedSymbology = symbology.Trim().ToUpperInvariant();
var symbologyFormats = GetSymbologyFormats();
BarcodeFormat format;
if (!symbologyFormats.TryGetValue(normalizedSymbology, out format))
{
throw new ArgumentException(
"Unsupported symbology: '" + symbology + "'. Supported values: " +
string.Join(", ", symbologyFormats.Keys) + ".",
"symbology"
);
}
log($"Resolved Symbology: {normalizedSymbology} to Format: {format}");
log("Configuring EncodingOptions");
var encodingOptions = new EncodingOptions
{
Width = 1,
Height = 1,
Margin = 0,
PureBarcode = true
};
log($"EncodingOptions set - Width: {encodingOptions.Width}, Height: {encodingOptions.Height}, Margin: {encodingOptions.Margin}, PureBarcode: {encodingOptions.PureBarcode}");
if (normalizedSymbology == "MICROQR")
{
log("Applying MICROQR Version Hint");
encodingOptions.Hints[ZXing.EncodeHintType.QR_VERSION] = 1;
}
log("Initializing MultiFormatWriter");
var writer = new ZXing.MultiFormatWriter();
log("Calling MultiFormatWriter.encode...");
BitMatrix bitMatrix = writer.encode(data, format, encodingOptions.Width, encodingOptions.Height, encodingOptions.Hints);
if (bitMatrix == null)
{
throw new InvalidOperationException("ZXing MultiFormatWriter.encode returned null BitMatrix.");
}
log($"BitMatrix Generated - Width: {bitMatrix.Width}, Height: {bitMatrix.Height}");
log("Setting up Raster Buffer Rendering");
int scaleFactor = Math.Max(1, (int)Math.Ceiling(scale));
int scaledWidth = bitMatrix.Width * scaleFactor;
int scaledHeight = bitMatrix.Height * scaleFactor;
int bytesPerPixel = 4;
int rgbaStride = scaledWidth * bytesPerPixel;
log($"Calculated Raster Bounds -> Width: {scaledWidth}, Height: {scaledHeight}, ScaleFactor: {scaleFactor}");
byte[] rgba = new byte[scaledWidth * scaledHeight * bytesPerPixel];
log($"Allocated RGBA Buffer -> Length: {rgba.Length} bytes");
for (int pixelOffset = 0; pixelOffset < rgba.Length; pixelOffset += bytesPerPixel)
{
rgba[pixelOffset] = 255;
rgba[pixelOffset + 1] = 255;
rgba[pixelOffset + 2] = 255;
rgba[pixelOffset + 3] = 255;
}
log("RGBA Buffer Cleared to White");
int paintedModules = 0;
for (int matrixY = 0; matrixY < bitMatrix.Height; matrixY++)
{
for (int matrixX = 0; matrixX < bitMatrix.Width; matrixX++)
{
if (!bitMatrix[matrixX, matrixY])
{
continue;
}
paintedModules++;
for (int offsetY = 0; offsetY < scaleFactor; offsetY++)
{
int pixelY = (matrixY * scaleFactor) + offsetY;
int rowOffset = pixelY * rgbaStride;
for (int offsetX = 0; offsetX < scaleFactor; offsetX++)
{
int pixelX = (matrixX * scaleFactor) + offsetX;
int modulePixelOffset = rowOffset + (pixelX * bytesPerPixel);
rgba[modulePixelOffset] = 0;
rgba[modulePixelOffset + 1] = 0;
rgba[modulePixelOffset + 2] = 0;
rgba[modulePixelOffset + 3] = 255;
}
}
}
}
log($"Raster Buffer Rendered - Painted {paintedModules} black modules");
log("Packing PNG Scanlines");
int scanlineLength = 1 + rgbaStride;
byte[] scanlines = new byte[scaledHeight * scanlineLength];
for (int row = 0; row < scaledHeight; row++)
{
int scanlineOffset = row * scanlineLength;
scanlines[scanlineOffset] = 0;
Buffer.BlockCopy(rgba, row * rgbaStride, scanlines, scanlineOffset + 1, rgbaStride);
}
log($"PNG Scanlines Packed -> Total Length: {scanlines.Length} bytes");
log("Beginning PNG Export to Base64");
byte[] pngSignature = new byte[] { 137, 80, 78, 71, 13, 10, 26, 10 };
byte[] ihdrType = new byte[] { 73, 72, 68, 82 };
byte[] idatType = new byte[] { 73, 68, 65, 84 };
byte[] iendType = new byte[] { 73, 69, 78, 68 };
byte[] ihdrData = new byte[13];
byte[] widthBytes = GetBigEndianBytes((uint)scaledWidth);
byte[] heightBytes = GetBigEndianBytes((uint)scaledHeight);
Buffer.BlockCopy(widthBytes, 0, ihdrData, 0, widthBytes.Length);
Buffer.BlockCopy(heightBytes, 0, ihdrData, 4, heightBytes.Length);
ihdrData[8] = 8;
ihdrData[9] = 6;
ihdrData[10] = 0;
ihdrData[11] = 0;
ihdrData[12] = 0;
log("Compressing Scanlines via Zlib");
byte[] compressedScanlines = CompressToZlib(scanlines);
log($"Scanlines Compressed -> Zlib Payload Length: {compressedScanlines.Length} bytes");
using (MemoryStream pngStream = new MemoryStream())
{
pngStream.Write(pngSignature, 0, pngSignature.Length);
WritePngChunk(pngStream, ihdrType, ihdrData);
log("IHDR Chunk Written");
WritePngChunk(pngStream, idatType, compressedScanlines);
log("IDAT Chunk Written");
WritePngChunk(pngStream, iendType, new byte[0]);
log("IEND Chunk Written");
base64png = Convert.ToBase64String(pngStream.ToArray());
}
log("PNG Encoded to Base64 String");
log($"DataLength: {data.Length}");
log($"Scale: {scale} (Factor: {scaleFactor})");
log($"MatrixSize: {bitMatrix.Width}x{bitMatrix.Height}");
log($"ImageSize: {scaledWidth}x{scaledHeight}");
log($"Base64Length: {base64png.Length}");
log("Pipeline Completed Successfully");
}
catch (Exception ex)
{
msg += $"\r\n[FATAL ERROR] {ex.Message} \r\nStackTrace: {ex.StackTrace} § ";
log("Pipeline Execution Failed");
if (ex.InnerException != null)
{
msg += $"\r\n[INNER EXCEPTION] {ex.InnerException.Message} \r\nStackTrace: {ex.InnerException.StackTrace} § ";
}
}
// References: zxing.dll
// Signature:
// Input:  symbology (string), data (string), scale (decimal)
// Output: msg (string), base64png (string)
// Usings:
// using System;
// using System.Collections.Generic;
// using System.IO;
// using System.IO.Compression;
// using ZXing;
// using ZXing.Common;

msg = "";
base64png = null;

var log = new Action<string>(txt => msg += $"[{txt}] § ");
log("Pipeline Initialized");

try
{
    Func<uint, byte[]> GetBigEndianBytes = value => new byte[]
    {
        (byte)((value >> 24) & 255u),
        (byte)((value >> 16) & 255u),
        (byte)((value >> 8)  & 255u),
        (byte)(value & 255u)
    };
    log("GetBigEndianBytes Created");

    Func<byte[], uint> ComputeCrc32 = bytes =>
    {
        uint crc = 4294967295u;

        if (bytes == null || bytes.Length == 0)
        {
            return crc ^ 4294967295u;
        }

        for (int index = 0; index < bytes.Length; index++)
        {
            crc ^= bytes[index];

            for (int bit = 0; bit < 8; bit++)
            {
                bool applyPolynomial = (crc & 1u) == 1u;
                crc >>= 1;

                if (applyPolynomial)
                {
                    crc ^= 3988292384u;
                }
            }
        }

        return crc ^ 4294967295u;
    };
    log("ComputeCrc32 Created");

    Func<byte[], uint> ComputeAdler32 = buffer =>
    {
        const uint modulus = 65521u;
        uint sumA = 1u;
        uint sumB = 0u;

        if (buffer == null || buffer.Length == 0)
        {
            return 1u;
        }

        for (int index = 0; index < buffer.Length; index++)
        {
            sumA = (sumA + buffer[index]) % modulus;
            sumB = (sumB + sumA) % modulus;
        }

        return (sumB << 16) | sumA;
    };
    log("ComputeAdler32 Created");

    Action<MemoryStream, byte[], byte[]> WritePngChunk = (stream, chunkType, chunkData) =>
    {
        if (stream == null)
        {
            throw new ArgumentNullException("stream");
        }

        if (chunkType == null || chunkType.Length != 4)
        {
            throw new ArgumentException(
                "PNG chunk types must be exactly four bytes long.",
                "chunkType"
            );
        }

        byte[] safeChunkData = chunkData ?? new byte[0];
        byte[] payloadLengthBytes = GetBigEndianBytes((uint)safeChunkData.Length);

        stream.Write(payloadLengthBytes, 0, payloadLengthBytes.Length);
        stream.Write(chunkType, 0, chunkType.Length);

        if (safeChunkData.Length > 0)
        {
            stream.Write(safeChunkData, 0, safeChunkData.Length);
        }

        byte[] crcInput = new byte[chunkType.Length + safeChunkData.Length];
        Buffer.BlockCopy(chunkType, 0, crcInput, 0, chunkType.Length);

        if (safeChunkData.Length > 0)
        {
            Buffer.BlockCopy(safeChunkData, 0, crcInput, chunkType.Length, safeChunkData.Length);
        }

        byte[] crcBytes = GetBigEndianBytes(ComputeCrc32(crcInput));
        stream.Write(crcBytes, 0, crcBytes.Length);
    };
    log("WritePngChunk Created");

    Func<byte[], byte[]> CompressToZlib = buffer =>
    {
        byte[] safeBuffer = buffer ?? new byte[0];
        byte[] deflateBytes = null;

        using (MemoryStream compressedStream = new MemoryStream())
        {
            using (DeflateStream deflateStream = new DeflateStream(
                compressedStream,
                CompressionMode.Compress,
                true))
            {
                deflateStream.Write(safeBuffer, 0, safeBuffer.Length);
            }

            deflateBytes = compressedStream.ToArray();
        }

        byte[] zlibPayload = new byte[2 + deflateBytes.Length + 4];
        zlibPayload[0] = 120;
        zlibPayload[1] = 156;

        if (deflateBytes.Length > 0)
        {
            Buffer.BlockCopy(deflateBytes, 0, zlibPayload, 2, deflateBytes.Length);
        }

        byte[] adlerBytes = GetBigEndianBytes(ComputeAdler32(safeBuffer));
        Buffer.BlockCopy(adlerBytes, 0, zlibPayload, 2 + deflateBytes.Length, adlerBytes.Length);

        return zlibPayload;
    };
    log("CompressToZlib Created");

    Func<Dictionary<string, BarcodeFormat>> GetSymbologyFormats = () =>
        new Dictionary<string, BarcodeFormat>
        {
            { "QR",         BarcodeFormat.QR_CODE },
            { "MICROQR",    BarcodeFormat.QR_CODE },
            { "DATAMATRIX", BarcodeFormat.DATA_MATRIX },
            { "CODE128",    BarcodeFormat.CODE_128 },
            { "CODE39",     BarcodeFormat.CODE_39 },
            { "AZTEC",      BarcodeFormat.AZTEC },
            { "PDF417",     BarcodeFormat.PDF_417 },
            { "EAN13",      BarcodeFormat.EAN_13 },
            { "EAN8",       BarcodeFormat.EAN_8 },
            { "UPCA",       BarcodeFormat.UPC_A },
            { "CODABAR",    BarcodeFormat.CODABAR },
            { "ITF",        BarcodeFormat.ITF }
        };
    log("GetSymbologyFormats Created");

    log(
        $"Original Inputs -> Symbology: {symbology}, " +
        $"DataLength: {(data != null ? data.Length.ToString() : "null")}, " +
        $"Scale: {scale}"
    );

    symbology = string.IsNullOrEmpty(symbology) ? "QR" : symbology;
    data      = string.IsNullOrEmpty(data) ? "https://example.com" : data;
    scale     = scale <= 0m ? 1.0m : scale;

    log(
        $"Defaulted Inputs -> Symbology: {symbology}, " +
        $"DataLength: {data.Length}, " +
        $"Scale: {scale}"
    );

    log("Validating Inputs");

    if (string.IsNullOrWhiteSpace(data))
    {
        throw new ArgumentException(
            "The data string to encode cannot be null, empty, or whitespace.",
            "data"
        );
    }

    if (string.IsNullOrWhiteSpace(symbology))
    {
        throw new ArgumentException(
            "The symbology definition must be provided.",
            "symbology"
        );
    }

    log("Inputs Validated Successfully");
    log("Resolving Symbology");

    string normalizedSymbology = symbology.Trim().ToUpperInvariant();
    var symbologyFormats = GetSymbologyFormats();

    BarcodeFormat format;
    if (!symbologyFormats.TryGetValue(normalizedSymbology, out format))
    {
        throw new ArgumentException(
            "Unsupported symbology: '" + symbology + "'. Supported values: " +
            string.Join(", ", symbologyFormats.Keys) + ".",
            "symbology"
        );
    }

    log($"Resolved Symbology: {normalizedSymbology} to Format: {format}");
    log("Configuring EncodingOptions");

    var encodingOptions = new EncodingOptions
    {
        Width = 1,
        Height = 1,
        Margin = 0,
        PureBarcode = true
    };

    log(
        $"EncodingOptions set - Width: {encodingOptions.Width}, " +
        $"Height: {encodingOptions.Height}, " +
        $"Margin: {encodingOptions.Margin}, " +
        $"PureBarcode: {encodingOptions.PureBarcode}"
    );

    if (normalizedSymbology == "MICROQR")
    {
        log("Applying MICROQR Version Hint");
        encodingOptions.Hints[ZXing.EncodeHintType.QR_VERSION] = 1;
    }

    log("Initializing MultiFormatWriter");

    var writer = new ZXing.MultiFormatWriter();

    log("Calling MultiFormatWriter.encode...");

    BitMatrix bitMatrix = writer.encode(
        data,
        format,
        encodingOptions.Width,
        encodingOptions.Height,
        encodingOptions.Hints
    );

    if (bitMatrix == null)
    {
        throw new InvalidOperationException(
            "ZXing MultiFormatWriter.encode returned null BitMatrix."
        );
    }

    log($"BitMatrix Generated - Width: {bitMatrix.Width}, Height: {bitMatrix.Height}");
    log("Setting up Raster Buffer Rendering");

    int scaleFactor = Math.Max(1, (int)Math.Ceiling(scale));
    int scaledWidth = bitMatrix.Width * scaleFactor;
    int scaledHeight = bitMatrix.Height * scaleFactor;
    int bytesPerPixel = 4;
    int rgbaStride = scaledWidth * bytesPerPixel;

    log(
        $"Calculated Raster Bounds -> Width: {scaledWidth}, " +
        $"Height: {scaledHeight}, " +
        $"ScaleFactor: {scaleFactor}"
    );

    byte[] rgba = new byte[scaledWidth * scaledHeight * bytesPerPixel];
    log($"Allocated RGBA Buffer -> Length: {rgba.Length} bytes");

    for (int pixelOffset = 0; pixelOffset < rgba.Length; pixelOffset += bytesPerPixel)
    {
        rgba[pixelOffset] = 255;
        rgba[pixelOffset + 1] = 255;
        rgba[pixelOffset + 2] = 255;
        rgba[pixelOffset + 3] = 255;
    }

    log("RGBA Buffer Cleared to White");

    int paintedModules = 0;

    for (int matrixY = 0; matrixY < bitMatrix.Height; matrixY++)
    {
        for (int matrixX = 0; matrixX < bitMatrix.Width; matrixX++)
        {
            if (!bitMatrix[matrixX, matrixY])
            {
                continue;
            }

            paintedModules++;

            for (int offsetY = 0; offsetY < scaleFactor; offsetY++)
            {
                int pixelY = (matrixY * scaleFactor) + offsetY;
                int rowOffset = pixelY * rgbaStride;

                for (int offsetX = 0; offsetX < scaleFactor; offsetX++)
                {
                    int pixelX = (matrixX * scaleFactor) + offsetX;
                    int modulePixelOffset = rowOffset + (pixelX * bytesPerPixel);

                    rgba[modulePixelOffset] = 0;
                    rgba[modulePixelOffset + 1] = 0;
                    rgba[modulePixelOffset + 2] = 0;
                    rgba[modulePixelOffset + 3] = 255;
                }
            }
        }
    }

    log($"Raster Buffer Rendered - Painted {paintedModules} black modules");
    log("Packing PNG Scanlines");

    int scanlineLength = 1 + rgbaStride;
    byte[] scanlines = new byte[scaledHeight * scanlineLength];

    for (int row = 0; row < scaledHeight; row++)
    {
        int scanlineOffset = row * scanlineLength;
        scanlines[scanlineOffset] = 0;

        Buffer.BlockCopy(
            rgba,
            row * rgbaStride,
            scanlines,
            scanlineOffset + 1,
            rgbaStride
        );
    }

    log($"PNG Scanlines Packed -> Total Length: {scanlines.Length} bytes");
    log("Beginning PNG Export to Base64");

    byte[] pngSignature = new byte[] { 137, 80, 78, 71, 13, 10, 26, 10 };
    byte[] ihdrType     = new byte[] { 73, 72, 68, 82 };
    byte[] idatType     = new byte[] { 73, 68, 65, 84 };
    byte[] iendType     = new byte[] { 73, 69, 78, 68 };

    byte[] ihdrData = new byte[13];
    byte[] widthBytes = GetBigEndianBytes((uint)scaledWidth);
    byte[] heightBytes = GetBigEndianBytes((uint)scaledHeight);

    Buffer.BlockCopy(widthBytes, 0, ihdrData, 0, widthBytes.Length);
    Buffer.BlockCopy(heightBytes, 0, ihdrData, 4, heightBytes.Length);

    ihdrData[8]  = 8;
    ihdrData[9]  = 6;
    ihdrData[10] = 0;
    ihdrData[11] = 0;
    ihdrData[12] = 0;

    log("Compressing Scanlines via Zlib");

    byte[] compressedScanlines = CompressToZlib(scanlines);
    log($"Scanlines Compressed -> Zlib Payload Length: {compressedScanlines.Length} bytes");

    using (MemoryStream pngStream = new MemoryStream())
    {
        pngStream.Write(pngSignature, 0, pngSignature.Length);

        WritePngChunk(pngStream, ihdrType, ihdrData);
        log("IHDR Chunk Written");

        WritePngChunk(pngStream, idatType, compressedScanlines);
        log("IDAT Chunk Written");

        WritePngChunk(pngStream, iendType, new byte[0]);
        log("IEND Chunk Written");

        base64png = Convert.ToBase64String(pngStream.ToArray());
    }

    log("PNG Encoded to Base64 String");
    log($"DataLength: {data.Length}");
    log($"Scale: {scale} (Factor: {scaleFactor})");
    log($"MatrixSize: {bitMatrix.Width}x{bitMatrix.Height}");
    log($"ImageSize: {scaledWidth}x{scaledHeight}");
    log($"Base64Length: {base64png.Length}");
    log("Pipeline Completed Successfully");
}
catch (Exception ex)
{
    msg += $"\r\n[FATAL ERROR] {ex.Message} \r\nStackTrace: {ex.StackTrace} § ";
    log("Pipeline Execution Failed");

    if (ex.InnerException != null)
    {
        msg += $"\r\n[INNER EXCEPTION] {ex.InnerException.Message} " +
               $"\r\nStackTrace: {ex.InnerException.StackTrace} § ";
    }
}

[Pipeline Initialized] § [GetBigEndianBytes Created] § [ComputeCrc32 Created] § [ComputeAdler32 Created] § [WritePngChunk Created] § [CompressToZlib Created] § [GetSymbologyFormats Created] § [Original Inputs → Symbology: DATAMATRIX, DataLength: 34, Scale: 5] § [Defaulted Inputs → Symbology: DATAMATRIX, DataLength: 34, Scale: 5] § [Validating Inputs] § [Inputs Validated Successfully] § [Resolving Symbology] § [Resolved Symbology: DATAMATRIX to Format: DATA_MATRIX] § [Configuring EncodingOptions] § [EncodingOptions set - Width: 1, Height: 1, Margin: 0, PureBarcode: True] § [Initializing MultiFormatWriter] § [Calling MultiFormatWriter.encode…] § [BitMatrix Generated - Width: 22, Height: 22] § [Setting up Raster Buffer Rendering] § [Calculated Raster Bounds → Width: 110, Height: 110, ScaleFactor: 5] § [Allocated RGBA Buffer → Length: 48400 bytes] § [RGBA Buffer Cleared to White] § [Raster Buffer Rendered - Painted 265 black modules] § [Packing PNG Scanlines] § [PNG Scanlines Packed → Total Length: 48510 bytes] § [Beginning PNG Export to Base64] § [Compressing Scanlines via Zlib] § [Scanlines Compressed → Zlib Payload Length: 627 bytes] § [IHDR Chunk Written] § [IDAT Chunk Written] § [IEND Chunk Written] § [PNG Encoded to Base64 String] § [DataLength: 34] § [Scale: 5 (Factor: 5)] § [MatrixSize: 22x22] § [ImageSize: 110x110] § [Base64Length: 912] § [Pipeline Completed Successfully] §

1KB at png encoding seems acceptable for a datamatrix.

EDIT: SSRS Supports VBA, so in theory you could build this out directly in SSRS/VBA as well, which would eliminate the need for BPM/ds modification in SSRS, as you could just call a function from an image expression. But, alas, i’m allergic to VBA.

12 Likes

Mic drop…

2 Likes

Gabe will have you dropping mics till you run out of money to buy another one.

2 Likes

Unfortunately it’s how you have to pass values between groupings to header or footer at times… I’m sure you already know that cause you’ve seen some out of the box Epicor forms that use VB to do it. I find it neat, but messy for sure.

1 Like

Yup. I’ve had to use it for the same reason to get stuff in the header..

Honestly it’s not that bad, I used to do VBA to automate excel so long ago, it just gives me flashbacks. lol

3 Likes

Have you moved to OfficeScript?

We are just going to have to keep mics on a subscription order…

3 Likes

I automate M365 via Carrier Pigeon.

That way, if i’m ever asked to work on a “slide deck” or a “worksheet”, everyone knows i’ll start in 5-7 business days.

5 Likes