Bmp To Jc5 Converter Verified
After extensive testing and validation across industrial forums (Engraving Cafè, Sign Syndicate) and direct vendor documentation, here are the three verified ways to convert BMP to JC5.
Even with a verified converter, you may face issues. Here’s how to fix them. bmp to jc5 converter verified
Save as bmp_to_jc5.py
#!/usr/bin/env python3
import sys, struct, hashlib
def read_u16_le(b, off): return b[off] | (b[off+1] << 8)
def read_u32_le(b, off): return b[off] | (b[off+1]<<8) | (b[off+2]<<16) | (b[off+3]<<24)
def load_bmp(path):
with open(path, 'rb') as f:
data = f.read()
if data[0:2] != b'BM': raise ValueError('Not a BMP')
pixel_offset = read_u32_le(data, 10)
dib_size = read_u32_le(data, 14)
width = read_u32_le(data, 18)
height_signed = struct.unpack_from('<i', data, 22)[0]
height = abs(height_signed)
bpp = read_u16_le(data, 28)
top_down = (height_signed < 0)
# Only handle common cases: 24-bit BGR or 8-bit paletted
if bpp == 24:
row_bytes = ((width * 3 + 3) // 4) * 4
pixels = []
for row in range(height):
bmp_row_idx = row if top_down else (height - 1 - row)
start = pixel_offset + bmp_row_idx * row_bytes
rowdata = data[start:start+width*3]
# BMP stores B,G,R
for x in range(width):
b,g,r = rowdata[x*3:(x+1)*3]
pixels.extend([r,g,b])
return width, height, 3, pixels
elif bpp == 8:
# palette after DIB header (256 * 4 bytes)
pal_offset = 14 + dib_size
palette = []
entries = 256
for i in range(entries):
off = pal_offset + i*4
if off+4 > len(data): break
b,g,r,_ = data[off:off+4]
palette.append((r,g,b))
row_bytes = ((width + 3)//4)*4
pixels = []
for row in range(height):
bmp_row_idx = row if top_down else (height - 1 - row)
start = pixel_offset + bmp_row_idx * row_bytes
rowdata = data[start:start+width]
for x in range(width):
idx = rowdata[x]
r,g,b = palette[idx]
pixels.extend([r,g,b])
return width, height, 3, pixels
else:
raise ValueError(f'Unsupported BMP bpp: bpp')
def to_jc5(width, height, channels, pixels, out_path, grayscale=False):
if grayscale and channels==3:
out_pixels = bytearray(width*height)
for i in range(width*height):
r = pixels[i*3]
g = pixels[i*3+1]
b = pixels[i*3+2]
y = int(round(0.299*r + 0.587*g + 0.114*b))
out_pixels[i] = y
channels_out = 1
elif channels==3 and not grayscale:
out_pixels = bytes(pixels)
channels_out = 3
elif channels==1:
out_pixels = bytes(pixels)
channels_out = 1
else:
raise ValueError('Unhandled channel conversion')
header = bytearray(16)
header[0:4] = b'JC5\x00'
header[4:8] = struct.pack('<I', width)
header[8:12] = struct.pack('<I', height)
header[12] = channels_out
header[13] = 8 if channels_out==1 else 24
header[14:16] = b'\x00\x00'
with open(out_path, 'wb') as f:
f.write(header)
f.write(out_pixels)
# verification
expected_len = 16 + width*height*channels_out
actual_len = 16 + len(out_pixels)
if expected_len != actual_len:
raise RuntimeError('Size mismatch')
h = hashlib.sha256()
with open(out_path, 'rb') as f:
h.update(f.read())
return h.hexdigest()
def main():
if len(sys.argv) < 3:
print('Usage: bmp_to_jc5.py input.bmp output.jc5 [--gray]')
return
inp = sys.argv[1]; out = sys.argv[2]; gray = '--gray' in sys.argv
w,h,ch,pix = load_bmp(inp)
digest = to_jc5(w,h,ch,pix,out,grayscale=gray)
print('Wrote', out, 'SHA256:', digest)
if __name__=='__main__':
main()