ESC/POS位图字节数组转图像及收据OCR实现咨询
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, wherenL + nH*256is 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:
- ESC/POS data is monochrome bit data, not 3-byte RGB pixels.
- 24-bit RGB Bitmaps use BGR byte order (not RGB), so even if you had color data, your copy order was wrong.
- 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

