You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ESC/POS位图字节数组转图像及收据OCR实现咨询

解决方案:还原ESC/POS 24点双密度图像为Bitmap

Absolutely you can convert the ESC/POS image byte array back to a usable image for OCR—your all-black output happens because you’re directly copying the ESC/POS-specific bit-encoded data into a 24-bit RGB Bitmap, which doesn’t match the format of the printer’s image data. Let’s break down how to fix this.

First: Understand ESC/POS 24-bit Double-Density Image Format

The ESC * 0x21 (24-bit double-density) command isn’t for 24-bit color—it’s for monochrome (black/white) images with a vertical resolution of 24 pixels per data block. Here’s how the data is structured:

  • The command starts with 0x1B 0x2A 0x21 nL nH, where nL + nH*256 is the number of bytes per horizontal "strip" (each byte represents 8 horizontal pixels).
  • The image data that follows is 3× the horizontal byte count:
    • First chunk: Bytes for vertical rows 0–7 (each bit in a byte = 1 pixel; 1 = black/printed, 0 = white/unprinted)
    • Second chunk: Bytes for vertical rows 8–15
    • Third chunk: Bytes for vertical rows 16–23
  • Bits in each byte are ordered from highest (bit7) to lowest (bit0), mapping to left-to-right horizontal pixels.

Fix: Convert ESC/POS Data to Bitmap

Below is a C# implementation that correctly parses the ESC/POS image data and converts it to a Bitmap (optimized for OCR with a 1-bit indexed format, which you can convert to 24-bit RGB if needed).

Step 1: Convert ESC/POS Image Data to Monochrome Bitmap

using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Collections.Generic;

public static Bitmap ConvertEscPos24BitImage(byte[] imageData, int bytesPerHorizontalStrip)
{
    int pixelWidth = bytesPerHorizontalStrip * 8;
    int pixelHeight = 24; // Fixed for 24-bit ESC/POS mode

    // Create a 1-bit indexed Bitmap (ideal for OCR, small file size)
    Bitmap bitmap = new Bitmap(pixelWidth, pixelHeight, PixelFormat.Format1bppIndexed);
    
    // Set palette: index 0 = white (unprinted), index 1 = black (printed)
    ColorPalette palette = bitmap.Palette;
    palette.Entries[0] = Color.White;
    palette.Entries[1] = Color.Black;
    bitmap.Palette = palette;

    BitmapData bitmapData = bitmap.LockBits(
        new Rectangle(0, 0, pixelWidth, pixelHeight),
        ImageLockMode.WriteOnly,
        PixelFormat.Format1bppIndexed
    );

    int stride = bitmapData.Stride; // Bitmap row length (aligned to 4 bytes)
    IntPtr scanPointer = bitmapData.Scan0;

    // Process each of the 3 vertical planes (8 rows per plane)
    for (int plane = 0; plane < 3; plane++)
    {
        int planeStartIndex = plane * bytesPerHorizontalStrip;
        // Process each row in the current plane
        for (int rowInPlane = 0; rowInPlane < 8; rowInPlane++)
        {
            int bitmapRow = plane * 8 + rowInPlane;
            byte[] rowBuffer = new byte[stride];

            // Convert each ESC/POS byte to bitmap pixels
            for (int byteIndex = 0; byteIndex < bytesPerHorizontalStrip; byteIndex++)
            {
                byte escPosByte = imageData[planeStartIndex + byteIndex];
                // Map each bit in the byte to a horizontal pixel
                for (int bitPosition = 0; bitPosition < 8; bitPosition++)
                {
                    int pixelX = byteIndex * 8 + bitPosition;
                    if (pixelX >= pixelWidth) continue;

                    // Check if the bit is set (1 = black pixel)
                    bool isBlack = (escPosByte & (1 << (7 - bitPosition))) != 0;
                    if (isBlack)
                    {
                        int bufferByte = pixelX / 8;
                        int bufferBit = 7 - (pixelX % 8);
                        rowBuffer[bufferByte] |= (byte)(1 << bufferBit);
                    }
                }
            }

            // Copy the processed row to the Bitmap
            Marshal.Copy(rowBuffer, 0, scanPointer + bitmapRow * stride, stride);
        }
    }

    bitmap.UnlockBits(bitmapData);
    return bitmap;
}

Step 2: Convert to 24-bit RGB (If Needed)

If you need a 24-bit RGB image instead of 1-bit indexed, use this helper:

public static Bitmap ConvertTo24bppRgb(Bitmap sourceBitmap)
{
    Bitmap rgbBitmap = new Bitmap(sourceBitmap.Width, sourceBitmap.Height, PixelFormat.Format24bppRgb);
    using (Graphics graphics = Graphics.FromImage(rgbBitmap))
    {
        graphics.Clear(Color.White); // Set white background
        graphics.DrawImage(sourceBitmap, 0, 0);
    }
    return rgbBitmap;
}

Step 3: Concatenate Multiple Image Blocks

Since your receipt is split by 0x0A (LF), you’ll need to stitch multiple image blocks into a full receipt:

public static Bitmap StitchReceiptImages(List<Bitmap> imageBlocks)
{
    if (imageBlocks == null || imageBlocks.Count == 0) return null;

    int totalWidth = imageBlocks.Max(block => block.Width);
    int totalHeight = imageBlocks.Sum(block => block.Height);

    Bitmap fullReceipt = new Bitmap(totalWidth, totalHeight, PixelFormat.Format24bppRgb);
    using (Graphics graphics = Graphics.FromImage(fullReceipt))
    {
        graphics.Clear(Color.White);
        int currentY = 0;
        foreach (Bitmap block in imageBlocks)
        {
            graphics.DrawImage(block, 0, currentY);
            currentY += block.Height;
            block.Dispose(); // Clean up to avoid memory leaks
        }
    }
    return fullReceipt;
}

Why Your Original Code Failed

Your code directly copied the ESC/POS bit-encoded data into a 24-bit RGB Bitmap. This doesn’t work because:

  1. ESC/POS data is monochrome bit data, not 3-byte RGB pixels.
  2. 24-bit RGB Bitmaps use BGR byte order (not RGB), so even if you had color data, your copy order was wrong.
  3. The Bitmap’s stride (row length) is aligned to 4 bytes, which your code didn’t account for.

Usage Example

// Example: Parse ESC/POS command parameters
int nL = 0x08; // From your captured data
int nH = 0x00;
int bytesPerStrip = nL + (nH << 8);

// Extract the image data (3 * bytesPerStrip bytes from the command)
byte[] escPosImageData = ...; // Your captured image byte array

// Convert to Bitmap
Bitmap imageBlock = ConvertEscPos24BitImage(escPosImageData, bytesPerStrip);
Bitmap rgbBlock = ConvertTo24bppRgb(imageBlock);

// Collect all blocks and stitch into full receipt
List<Bitmap> receiptBlocks = new List<Bitmap>();
receiptBlocks.Add(rgbBlock);
// Add other blocks from your parsed data...

Bitmap fullReceipt = StitchReceiptImages(receiptBlocks);
fullReceipt.Save("full_receipt.png", ImageFormat.Png);

Extra Tips for OCR

  • Use the 1-bit indexed Bitmap directly for OCR—it’s cleaner and faster for text recognition.
  • Account for printer line spacing: if your receipt has blank lines between blocks, add empty rows to the stitched image (e.g., 30 pixels per line for standard POS printers).

内容的提问来源于stack exchange,提问作者XeeChaun

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 07:00:28