From 5d13db7a0918ade285064238f401bd2f66b36ae5 Mon Sep 17 00:00:00 2001 From: klemek Date: Wed, 25 Aug 2021 17:50:57 +0200 Subject: [PATCH] wip write bmp --- watchy-image-editor/main.py | 96 ++++++++++++++++++++++++------------- 1 file changed, 63 insertions(+), 33 deletions(-) diff --git a/watchy-image-editor/main.py b/watchy-image-editor/main.py index 4777012..7cca4b1 100644 --- a/watchy-image-editor/main.py +++ b/watchy-image-editor/main.py @@ -4,79 +4,109 @@ from tkinter import filedialog from typing import List, Optional, Tuple import re import os.path -from math import sqrt, log2, ceil +from math import sqrt DRAW_SCALE = 3 +class BitmapError(Exception): + pass + + class Bitmap: HEADER_SIZE = 54 FILE_TYPES = [("Bitmap Image", "*.bmp"), ("All Files", "*.*")] @classmethod - def write_bmp(cls, path: str, width: int, data: bytes) -> None: + def write_bmp(cls, path: str, width: int, color_depth: int, data: bytes) -> None: with open(path, mode="wb") as f: - f.write(cls.__get_bmp_data(width, data)) + f.write(cls.__get_bmp_data(width, color_depth, data)) @classmethod - def __get_bmp_data(cls, width: int, data: bytes) -> bytes: + def __get_bmp_data(cls, width: int, color_depth: int, data: bytes) -> bytes: height = len(data) // (width * 3) - return cls.__get_header(width, height, len(data)) + cls.__format_data( - width, height, data - ) + return cls.__get_header( + width, height, color_depth, len(data) + ) + cls.__format_data(width, height, color_depth, data) @classmethod - def __get_header(cls, width: int, height: int, data_len: int) -> bytes: + def __get_header( + cls, width: int, height: int, color_depth: int, data_len: int + ) -> bytes: header = bytes() # BMP header - header += "BM".encode() # (2) BM + header += "BM".encode() # (0, 2) BM header += (cls.HEADER_SIZE + data_len).to_bytes( 4, byteorder="little" - ) # (4) file size - header += bytes([0]) * 4 # (4) application reserved - header += (cls.HEADER_SIZE).to_bytes(4, byteorder="little") # (4) data offset + ) # (2, 4) file size + header += bytes([0]) * 4 # (6, 4) application reserved + header += (cls.HEADER_SIZE).to_bytes( + 4, byteorder="little" + ) # (10, 4) data offset # DIB header - header += (40).to_bytes(4, byteorder="little") # (4) DIB header size - header += width.to_bytes(4, byteorder="little") # (4) width - header += height.to_bytes(4, byteorder="little") # (4) height - header += (1).to_bytes(2, byteorder="little") # (2) color panes - header += (24).to_bytes(2, byteorder="little") # (2) bits per pixel - header += bytes([0]) * 4 # (4) BI_RGB, no compression + header += (40).to_bytes(4, byteorder="little") # (14, 4) DIB header size + header += width.to_bytes(4, byteorder="little") # (18, 4) width + header += height.to_bytes(4, byteorder="little") # (22, 4) height + header += (1).to_bytes(2, byteorder="little") # (26, 2) color panes + header += (color_depth * 8).to_bytes( + 2, byteorder="little" + ) # (28, 2) bits per pixel + header += bytes([0]) * 4 # (30, 4) BI_RGB, no compression header += (data_len).to_bytes( 4, byteorder="little" - ) # (4) size of raw bitmap data + ) # (34, 4) size of raw bitmap data header += (2835).to_bytes( 4, byteorder="little" - ) # (4) horizontal print resolution + ) # (38, 4) horizontal print resolution header += (2835).to_bytes( 4, byteorder="little" - ) # (4) vertical print resolution - header += bytes([0]) * 4 # (4) color in palette - header += bytes([0]) * 4 # (4) 0 important colors + ) # (42, 4) vertical print resolution + header += bytes([0]) * 4 # (46, 4) color in palette + header += bytes([0]) * 4 # (50, 4) 0 important colors return header @classmethod - def __format_data(cls, width: int, height: int, data: bytes) -> bytes: - size = width * height * 3 + def __format_data( + cls, width: int, height: int, color_depth: int, data: bytes + ) -> bytes: + size = width * height * color_depth if len(data) < size: data += bytes([0]) * (size - len(data)) elif len(data) > size: data = data[:size] - line_padding = (width * 3) % 4 + line_padding = (width * color_depth) % 4 output_data = bytes() for y in range(height): - start = (height - y - 1) * 3 * width - output_data += data[start : start + width * 3] + start = (height - y - 1) * color_depth * width + output_data += data[start : start + width * color_depth] if line_padding > 0: output_data += bytes([0]) * (4 - line_padding) return output_data @classmethod - def read_bmp(cls, path: str) -> Tuple[int, int, bytes]: + def read_bmp(cls, path: str) -> Tuple[int, int, int, bytes]: with open(path, mode="rb") as f: - raw_data = f.read() - # TODO + bmp_data = f.read() + width, height, color_depth, data_start, data_size = cls.read_header( + bmp_data + ) + # TODO read data + + @classmethod + def read_header(cls, bmp_data: bytes) -> Tuple[int, int, int, int, int]: + if bmp_data[0:2].decode() != "BM": + raise BitmapError("Not a Bitmap Image") + if int.from_bytes(bmp_data[30:34], byteorder="little") != 0: + raise BitmapError("Cannot read Bitmap: compression") + if int.from_bytes(bmp_data[26:28], byteorder="little") != 1: + raise BitmapError("Cannot read Bitmap: color panes") + width = int.from_bytes(bmp_data[18:22], byteorder="little") + height = int.from_bytes(bmp_data[22:28], byteorder="little") + color_depth = int.from_bytes(bmp_data[28:30], byteorder="little") // 8 + data_start = int.from_bytes(bmp_data[10:14], byteorder="little") + data_size = int.from_bytes(bmp_data[34:38], byteorder="little") + return width, height, color_depth, data_start, data_size class Image: @@ -136,10 +166,10 @@ class Image: return output def export_bmp(self, path: str) -> None: - Bitmap.write_bmp(path, self.width, self.get_color_bytes()) + Bitmap.write_bmp(path, self.width, 3, self.get_color_bytes()) def import_bmp(self, path: str) -> None: - self.width, self.height, data = Bitmap.read_bmp(path) + self.width, self.height, color_depth, data = Bitmap.read_bmp(path) # TODO