wip write bmp
This commit is contained in:
+63
-33
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user