Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

converting images to indexed 2-bit grayscale BMP

First of all, my question is different to How do I convert image to 2-bit per pixel? and unfortunately its solution does not work in my case...

I need to convert images to 2-bit per pixel grayscale BMP format. The sample image has the following properties:

Color Model: RGB
Depth: 4
Is Indexed: 1
Dimension: 800x600
Size: 240,070 bytes (4 bits per pixel but only last 2 bits are used to identify the gray scales as 0/1/2/3 in decimal or 0000/0001/0010/0011 in binary, plus 70 bytes BMP metadata or whatever)

sample

The Hex values of the beginning part of the sample BMP image: head of sample image

The 3s represent white pixels at the beginning of the image. Further down there are some 0s, 1s and 2s representing black, dark gray and light gray: mid-part of sample image

With the command below,

convert pic.png -colorspace gray +matte -depth 2 out.bmp

I can get visually correct 4-level grayscale image, but wrong depth or size per pixel:

Color Model: RGB
Depth: 8 (expect 4)
Dimension: 800x504
Size: 1,209,738 bytes (something like 3 bytes per pixel, plus metadata)
(no mention of indexed colour space)

conversion outcome

Please help...

like image 591
yy502 Avatar asked Mar 04 '16 14:03

yy502


People also ask

How do I convert an image to grayscale?

Change a picture to grayscale or to black-and-whiteRight-click the picture that you want to change, and then click Format Picture on the shortcut menu. Click the Picture tab. Under Image control, in the Color list, click Grayscale or Black and White.

What is difference between grayscale and bitmap?

Images in bitmap mode are called 1‑bit images because they have a bit depth of 1. Uses up to 256 shades of gray. Grayscale images are 8‑bit images. Every pixel in a grayscale image has a brightness value ranging from 0 (black) to 255 (white).


2 Answers

OK, I have written a Python script following Mark's hints (see comments under original question) to manually create a 4-level gray scale BMP with 4bpp. This specific BMP format construction is for the 4.3 inch e-paper display module made by WaveShare. Specs can be found here: http://www.waveshare.com/wiki/4.3inch_e-Paper

Here's how to pipe the original image to my code and save the outcome.

convert in.png -colorspace gray +matte -colors 4 -depth 2 -resize '800x600>' pgm:- | ./4_level_gray_4bpp_BMP_converter.py > out.bmp

Contents of 4_level_gray_4bpp_BMP_converter.py:

#!/usr/bin/env python

"""

### Sample BMP header structure, total = 70 bytes
### !!! little-endian !!!

Bitmap file header 14 bytes
42 4D          "BM"
C6 A9 03 00    FileSize = 240,070       <= dynamic value
00 00          Reserved
00 00          Reserved
46 00 00 00    Offset = 70 = 14+56

DIB header (bitmap information header)
BITMAPV3INFOHEADER 56 bytes
28 00 00 00    Size = 40
20 03 00 00    Width = 800              <= dynamic value
58 02 00 00    Height = 600             <= dynamic value
01 00          Planes = 1
04 00          BitCount = 4
00 00 00 00    compression
00 00 00 00    SizeImage
00 00 00 00    XPerlPerMeter
00 00 00 00    YPerlPerMeter
04 00 00 00    Colours used = 4
00 00 00 00    ColorImportant
00 00 00 00    Colour definition index 0
55 55 55 00    Colour definition index 1
AA AA AA 00    Colour definition index 2
FF FF FF 00    Colour definition index 3

"""

# to insert File Size, Width and Height with hex strings in order
BMP_HEADER = "42 4D %s 00 00 00 00 46 00 00 00 28 00 00 00 %s %s 01 00 04 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 04 00 00 00 00 00 00 00 00 00 00 00 55 55 55 00 AA AA AA 00 FF FF FF 00"
BMP_HEADER_SIZE = 70
BPP = 4
BYTE = 8
ALIGNMENT = 4 # bytes per row

import sys
from re import findall

DIMENTIONS = 1
PIXELS = 3

BLACK     = "0"
DARK_GRAY = "1"
GRAY      = "2"
WHITE     = "3"

# sample data:
# ['P5\n', '610 590\n', '255\n', '<1 byte per pixel for 4 levels of gray>']
# where item 1 is always P5, item 2 is width heigh, item 3 is always 255, items 4 is pixels/colours
data = sys.stdin.readlines()

width = int(data[DIMENTIONS].strip().split(' ')[0])
height = int(data[DIMENTIONS].strip().split(' ')[1])


if not width*height == len(data[PIXELS]):
    print "Error: pixel data (%s bytes) and image size (%dx%d pixels) do not match" % (len(data[PIXELS]),width,height)
    sys.exit()

colours = [] # enumerate 4 gray levels
for p in data[PIXELS]:
    if not p in colours:
        colours.append(p)
        if len(colours) == 4:
            break

# it's possible for the converted pixels to have less than 4 gray levels

colours = sorted(colours) # sort from low to high

# map each colour to e-paper gray indexes
# creates hex string of pixels
# e.g. "0033322222110200....", which is 4 level gray with 4bpp

if len(colours) == 1: # unlikely, but let's have this case here
    pixels = data[PIXELS].replace(colours[0],BLACK)
elif len(colours) == 2: # black & white
    pixels = data[PIXELS].replace(colours[0],BLACK)\
                         .replace(colours[1],WHITE)
elif len(colours) == 3:
    pixels = data[PIXELS].replace(colours[0],DARK_GRAY)\
                         .replace(colours[1],GRAY)\
                         .replace(colours[2],WHITE)
else: # 4 grays as expected
    pixels = data[PIXELS].replace(colours[0],BLACK)\
                         .replace(colours[1],DARK_GRAY)\
                         .replace(colours[2],GRAY)\
                         .replace(colours[3],WHITE)

# BMP pixel array starts from last row to first row
# and must be aligned to 4 bytes or 8 pixels
padding = "F" * ((BYTE/BPP) * ALIGNMENT - width % ((BYTE/BPP) * ALIGNMENT))
aligned_pixels = ''.join([pixels[i:i+width]+padding for i in range(0, len(pixels), width)][::-1])

# convert hex string to represented byte values
def Hex2Bytes(hexStr):
    hexStr = ''.join(hexStr.split(" "))
    bytes = []
    for i in range(0, len(hexStr), 2):
        byte = int(hexStr[i:i+2],16)
        bytes.append(chr(byte))
    return ''.join(bytes)

# convert integer to 4-byte little endian hex string
# e.g. 800 => 0x320 => 00000320 (big-endian) =>20030000 (little-endian)
def i2LeHexStr(i):
    be_hex = ('0000000'+hex(i)[2:])[-8:]
    n = 2 # split every 2 letters
    return ''.join([be_hex[i:i+n] for i in range(0, len(be_hex), n)][::-1])

BMP_HEADER = BMP_HEADER % (i2LeHexStr(len(aligned_pixels)/(BYTE/BPP)+BMP_HEADER_SIZE),i2LeHexStr(width),i2LeHexStr(height))

sys.stdout.write(Hex2Bytes(BMP_HEADER+aligned_pixels))

Edit: everything about this e-paper display and my code to display things on it can be found here: https://github.com/yy502/ePaperDisplay

enter image description here

like image 181
yy502 Avatar answered Sep 28 '22 08:09

yy502


This works for me in Imagemagick 6.9.10.23 Q16 Mac OSX Sierra

Input: enter image description here

convert logo.png -colorspace gray -depth 2 -type truecolor logo_depth8_gray_rgb.bmp

enter image description here

Adding -type truecolor converts the image to RGB, but in gray tones as per the -colorspace gray. And the depth 2 creates only 4 colors in the histogram.

identify -verbose logo_depth8_gray_rgb.bmp

Image:
  Filename: logo_depth8_gray_rgb.bmp
  Format: BMP (Microsoft Windows bitmap image)
  Class: DirectClass
  Geometry: 640x480+0+0
  Units: PixelsPerCentimeter
  Colorspace: sRGB
  Type: Grayscale
  Base type: Undefined
  Endianness: Undefined
  Depth: 8/2-bit
  Channel depth:
    red: 2-bit
    green: 2-bit
    blue: 2-bit
  Channel statistics:
    Pixels: 307200
    Red:
      min: 0  (0)
      max: 255 (1)
      mean: 228.414 (0.895742)
      standard deviation: 66.9712 (0.262632)
      kurtosis: 4.29925
      skewness: -2.38354
      entropy: 0.417933
    Green:
      min: 0  (0)
      max: 255 (1)
      mean: 228.414 (0.895742)
      standard deviation: 66.9712 (0.262632)
      kurtosis: 4.29925
      skewness: -2.38354
      entropy: 0.417933
    Blue:
      min: 0  (0)
      max: 255 (1)
      mean: 228.414 (0.895742)
      standard deviation: 66.9712 (0.262632)
      kurtosis: 4.29925
      skewness: -2.38354
      entropy: 0.417933
  Image statistics:
    Overall:
      min: 0  (0)
      max: 255 (1)
      mean: 228.414 (0.895742)
      standard deviation: 66.9712 (0.262632)
      kurtosis: 4.29928
      skewness: -2.38355
      entropy: 0.417933
  Colors: 4 <--------
  Histogram: <--------
    12730: (0,0,0) #000000 black
    24146: (85,85,85) #555555 srgb(85,85,85)
    9602: (170,170,170) #AAAAAA srgb(170,170,170)
    260722: (255,255,255) #FFFFFF white
like image 24
fmw42 Avatar answered Sep 28 '22 09:09

fmw42