wip write bmp

This commit is contained in:
klemek
2021-08-25 17:50:57 +02:00
parent d3e18c4157
commit 5d13db7a09
+63 -33
View File
@@ -4,79 +4,109 @@ from tkinter import filedialog
from typing import List, Optional, Tuple from typing import List, Optional, Tuple
import re import re
import os.path import os.path
from math import sqrt, log2, ceil from math import sqrt
DRAW_SCALE = 3 DRAW_SCALE = 3
class BitmapError(Exception):
pass
class Bitmap: class Bitmap:
HEADER_SIZE = 54 HEADER_SIZE = 54
FILE_TYPES = [("Bitmap Image", "*.bmp"), ("All Files", "*.*")] FILE_TYPES = [("Bitmap Image", "*.bmp"), ("All Files", "*.*")]
@classmethod @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: 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 @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) height = len(data) // (width * 3)
return cls.__get_header(width, height, len(data)) + cls.__format_data( return cls.__get_header(
width, height, data width, height, color_depth, len(data)
) ) + cls.__format_data(width, height, color_depth, data)
@classmethod @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() header = bytes()
# BMP header # BMP header
header += "BM".encode() # (2) BM header += "BM".encode() # (0, 2) BM
header += (cls.HEADER_SIZE + data_len).to_bytes( header += (cls.HEADER_SIZE + data_len).to_bytes(
4, byteorder="little" 4, byteorder="little"
) # (4) file size ) # (2, 4) file size
header += bytes([0]) * 4 # (4) application reserved header += bytes([0]) * 4 # (6, 4) application reserved
header += (cls.HEADER_SIZE).to_bytes(4, byteorder="little") # (4) data offset header += (cls.HEADER_SIZE).to_bytes(
4, byteorder="little"
) # (10, 4) data offset
# DIB header # DIB header
header += (40).to_bytes(4, byteorder="little") # (4) DIB header size header += (40).to_bytes(4, byteorder="little") # (14, 4) DIB header size
header += width.to_bytes(4, byteorder="little") # (4) width header += width.to_bytes(4, byteorder="little") # (18, 4) width
header += height.to_bytes(4, byteorder="little") # (4) height header += height.to_bytes(4, byteorder="little") # (22, 4) height
header += (1).to_bytes(2, byteorder="little") # (2) color panes header += (1).to_bytes(2, byteorder="little") # (26, 2) color panes
header += (24).to_bytes(2, byteorder="little") # (2) bits per pixel header += (color_depth * 8).to_bytes(
header += bytes([0]) * 4 # (4) BI_RGB, no compression 2, byteorder="little"
) # (28, 2) bits per pixel
header += bytes([0]) * 4 # (30, 4) BI_RGB, no compression
header += (data_len).to_bytes( header += (data_len).to_bytes(
4, byteorder="little" 4, byteorder="little"
) # (4) size of raw bitmap data ) # (34, 4) size of raw bitmap data
header += (2835).to_bytes( header += (2835).to_bytes(
4, byteorder="little" 4, byteorder="little"
) # (4) horizontal print resolution ) # (38, 4) horizontal print resolution
header += (2835).to_bytes( header += (2835).to_bytes(
4, byteorder="little" 4, byteorder="little"
) # (4) vertical print resolution ) # (42, 4) vertical print resolution
header += bytes([0]) * 4 # (4) color in palette header += bytes([0]) * 4 # (46, 4) color in palette
header += bytes([0]) * 4 # (4) 0 important colors header += bytes([0]) * 4 # (50, 4) 0 important colors
return header return header
@classmethod @classmethod
def __format_data(cls, width: int, height: int, data: bytes) -> bytes: def __format_data(
size = width * height * 3 cls, width: int, height: int, color_depth: int, data: bytes
) -> bytes:
size = width * height * color_depth
if len(data) < size: if len(data) < size:
data += bytes([0]) * (size - len(data)) data += bytes([0]) * (size - len(data))
elif len(data) > size: elif len(data) > size:
data = data[:size] data = data[:size]
line_padding = (width * 3) % 4 line_padding = (width * color_depth) % 4
output_data = bytes() output_data = bytes()
for y in range(height): for y in range(height):
start = (height - y - 1) * 3 * width start = (height - y - 1) * color_depth * width
output_data += data[start : start + width * 3] output_data += data[start : start + width * color_depth]
if line_padding > 0: if line_padding > 0:
output_data += bytes([0]) * (4 - line_padding) output_data += bytes([0]) * (4 - line_padding)
return output_data return output_data
@classmethod @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: with open(path, mode="rb") as f:
raw_data = f.read() bmp_data = f.read()
# TODO 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: class Image:
@@ -136,10 +166,10 @@ class Image:
return output return output
def export_bmp(self, path: str) -> None: 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: 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 # TODO