pythonbitmappython-imaging-librarybitbit-depth

How to convert an 8bpp bitmap to 1bpp bitmap in Python


I have some code which is fine for creating greyscale (8bpp) images but I need to create 1bpp (binary) also for a printer. I have seen 2 posts here but cannot understand how they fix the issue. Can't format BMP image data to 1 bit per pixel in PIL I have pillow installed but call it using PIL as all the tutorials I have read seem to say to import Image from PIL.

I have a method called conv_to_1bpp which sets anything at 255 equal to 1, this is still making 8bpp images though.

Code

from PIL import Image
import numpy as np
import os
import sys
import struct


class BmpMaker:

    def __init__(self):
        self.ffp = None
        self.img_width = None
        self.img_height = None
        self.ws_activation = None
        self.bpp = None

    def make_image(self, img_width, img_height, ws_activation):
        """Creates a bitmap."""

        self.ffp = None
        self.img_width = img_width
        self.img_height = img_height
        self.ws_activation = ws_activation
       
        # Create new black image - L mode for b/w
        img = Image.new('L', (img_width, img_height))
        # Convert to Numpy array for easy processing
        na = np.array(img)
        # set the amount of white space activation
        white_rows = ws_activation
        # make white lines
        na[0:white_rows, 0:999] = 255
        # Revert to PIL Image from Numpy array and save
        self.ffp = self.make_img_title(img_width, img_height, ws_activation)
        Image.fromarray(na).save(self.ffp)
        self.bpp = self.get_bitdepth()
        return self.ffp

    def make_img_title(self, img_width, img_height, ws_activation):
        if ws_activation == 0:
            mystr = f"{img_height}x{img_width}"
        elif ws_activation is not None:
            mystr = f"{ws_activation}_{img_height}x{img_width}"
        self.ffp = os.path.join(os.getcwd(), mystr + ".bmp")
        print("img_ffp : ", self.ffp)
        return self.ffp

    def get_bitdepth(self):
        # Read first 100 bytes
        with open(self.ffp, 'rb') as f:
            BMP = f.read(100)

        if BMP[0:2] != b'BM':
            sys.exit('ERROR: Incorrect BMP signature')

        # Get BITMAPINFOHEADER size - https://en.wikipedia.org/wiki/BMP_file_format
        BITMAPINFOHEADERSIZE = struct.unpack('<i', BMP[14:18])[0]
        okSizes = [40, 52, 56, 108, 124]
        if BITMAPINFOHEADERSIZE not in okSizes:
            sys.exit(f'ERROR: BITMAPINFOHEADER size was {BITMAPINFOHEADERSIZE},'
                     f' expected one of {okSizes}')

        # Get bits per pixel
        self.bpp = struct.unpack('<H', BMP[28:30])[0]
        print(f'bbp: {self.bpp}')
        return self.bpp

    def get_img_bitdepth(self, img):
        # Read first 100 bytes
        with open(img, 'rb') as f:
            BMP = f.read(100)

        if BMP[0:2] != b'BM':
            sys.exit('ERROR: Incorrect BMP signature')

        # Get BITMAPINFOHEADER size - https://en.wikipedia.org/wiki/BMP_file_format
        BITMAPINFOHEADERSIZE = struct.unpack('<i', BMP[14:18])[0]
        okSizes = [40, 52, 56, 108, 124]
        if BITMAPINFOHEADERSIZE not in okSizes:
            sys.exit(f'ERROR: BITMAPINFOHEADER size was {BITMAPINFOHEADERSIZE},'
                     f' expected one of {okSizes}')

        # Get bits per pixel
        img_bpp = struct.unpack('<H', BMP[28:30])[0]
        print(f'bbp: {img_bpp}')
        return img_bpp

    def conv_to_1bpp(self):
        img = Image.open(self.ffp)
        arr = np.array(img)
        #print(arr)
        # Set all values at indices where the array equals 255 to 1.
        arr[arr==255] = 1
        print('\n'*3, arr)
        Image.fromarray(arr).save("my_img.bmp")
        print(self.get_img_bitdepth("my_img.bmp"))


if __name__ == "__main__":
    import PIL
    print('Pillow Version:', PIL.__version__)
    bm = BmpMaker()
    base_image = bm.make_image(1000, 8, 5)

    print("bitmap ffp: ", bm.ffp)
    print("bitmap bitdepth : ", bm.bpp)
    bm.conv_to_1bpp()

Solution

  • when creating the initial image use mode '1' instead of 'L'

        # Create new black image - L mode for b/w, '1' for binary
        img = Image.new('1', (img_width, img_height))