ImageManager class, to manage and abstract Image type.
It allows automatic management of the next pixel to be updated with a defined set of constraints :
- Value of y : TOP/BOTTOM
- Value of x : LEFT/RIGHT
- Direction : HORIZONTAL/VERTICAL
from struct import pack,unpack
class ImageManager:
# Fill x, or y first
HORIZONTAL = 0
VERTICAL = 1
# Start y = 0, or y = height-1
TOP = 0
BOTTOM = 1
# Start x = 0, or x = width-1
LEFT = 0
RIGHT = 1
def __init__(self, _name, _width, _height, _background_color = int('0x0000', 16), _x_mode = LEFT, _y_mode = TOP, _direction_mode = HORIZONTAL):
self.name = _name
self.width = _width
self.height = _height
self.background_color = _background_color
self.x_mode = _x_mode
self.y_mode = _y_mode
self.direction_mode = _direction_mode
self.is_finished = False
self.x = 0 if self.x_mode == ImageManager.TOP else (self.width - 1)
self.y = 0 if self.y_mode == ImageManager.LEFT else (self.height - 1)
self.reset()
# Initialize a new Image with specified background
def reset(self):
self.image = Bitmap(self.width, self.height, self.background_color)
# Update corresponding pixel in memory
def update(self, _x, _y, _pixel):
self.image.set_pixel(_x, _y, _pixel)
# Update only corresponding byte in already existing Image file
def raw_update(self, _x, _y, _pixel):
self.image.write_pixel(_x, _y, _pixel, self.name +".bmp")
# Manage next pixel using specified configuration
def add(self, _pixel):
if ( self.is_finished is True ):
raise Exception("Image size limit exceeded.")
else:
self.update(self.x, self.y, _pixel)
if ( self.direction_mode == ImageManager.HORIZONTAL ):
isNextRow = False
if ( self.x_mode == ImageManager.LEFT):
if ( self.x == (self.width-1) ):
self.x = 0
isNextRow = True
else:
self.x = self.x + 1
else:
if ( self.x == 0 ):
self.x = (self.width-1)
isNextRow = True
else:
self.x = self.x - 1
if ( isNextRow == True ):
if ( self.y_mode == ImageManager.TOP):
self.y = self.y + 1
else:
self.y = self.y - 1
else:
isNextColumn = False
if ( self.y_mode == ImageManager.TOP):
if ( self.y == (self.height-1) ):
self.y = 0
isNextColumn = True
else:
self.y = self.y + 1
else:
if ( self.y == 0 ):
self.y = self.height - 1
isNextColumn = True
else:
self.y = self.y - 1
if ( isNextColumn == True ):
if ( self.x_mode == ImageManager.LEFT):
self.x = self.x + 1
else:
self.x = self.x - 1
if (
(self.x_mode == ImageManager.TOP and self.y_mode == ImageManager.LEFT and self.x == (self.width-1) and self.y == (self.height-1)) or
(self.x_mode == ImageManager.TOP and self.y_mode == ImageManager.RIGHT and self.x == (self.width-1) and self.y == 0) or
(self.x_mode == ImageManager.BOTTOM and self.y_mode == ImageManager.RIGHT and self.x == 0 and self.y == 0) or
(self.x_mode == ImageManager.BOTTOM and self.y_mode == ImageManager.LEFT and self.x == 0 and self.y == (self.height-1))
):
self.is_finished = True
def render(self):
self.image.write( self.name +".bmp" )
Bitmap class, to render a RGB16 bitmap or update an existing bitmap with a raw byte change.
class Bitmap:
def __init__(self, width, height, background_color):
self.TYPE = 19778 # Bitmap signature : BM
self.RESERVED_1 = 0
self.RESERVED_2 = 0
self.DATA_OFFSET = 54 # Data starting offset
self.SIZE = self.DATA_OFFSET + width * 2 * height
# BITMAPINFOHEADER
self.HEADER_SIZE = 40 # BITMAPINFOHEADER size
self.WIDTH = width
self.HEIGHT = height
self.PLANES = 1 # Must be 1
self.BPP = 16 # Number of bits per pixel
self.COMPRESSION = 0 # Compression method, 0 = BI_RGB (no compression)
self.IMAGE_SIZE = 0 # BI_RGB, dummy zero is allowed
self.H_RESOLUTION = 0 # Horizontal pixel per meter
self.V_RESOLUTION = 0 # Vertical pixel per meter
self.COLORS_IN_PALETTE = 0 # Number of colors in color palette, 0 default to 2^n
self.IMPORTANT_COLOR = 0 # Number of important color, 0 is every color is important
# Out of spec, utility
self.background_color = background_color
self.clear()
def clear(self):
self.data = [pack('H', self.background_color)] * self.WIDTH * self.HEIGHT
def set_pixel(self, x, y, color):
if ( x < 0 or y < 0 or x > (self.WIDTH-1) or y > (self.HEIGHT-1) ):
raise ValueError("["+ x +";"+ y +"] is out of range.")
self.data[y * self.WIDTH + x] = pack('<H', color)
def write_pixel(self, x, y, color, file):
if ( x < 0 or y < 0 or x > (self.WIDTH-1) or y > (self.HEIGHT-1) ):
raise ValueError("["+ str(x) +";"+ str(y) +"] is out of range.")
with open(file, 'rb+') as f:
f.seek( self.DATA_OFFSET, 0)
f.seek( (self.HEIGHT-1-y)*self.HEIGHT*(self.BPP/8) + x * (self.BPP/8), 1)
f.write( pack('<H', color) )
def write(self, file):
with open(file, 'wb') as f:
# Writing BITMAPFILEHEADER
f.write(pack('<HLHHL',
self.TYPE,
self.SIZE,
self.RESERVED_1,
self.RESERVED_2,
self.DATA_OFFSET))
# Writing BITMAPINFO
f.write(pack('<LLLHHLLLLLL',
self.HEADER_SIZE,
self.WIDTH,
self.HEIGHT,
self.PLANES,
self.BPP,
self.COMPRESSION, # No compression
self.IMAGE_SIZE, # Raw size, as it is a BI_RGB a dummy 0 can be set
self.H_RESOLUTION, # Horizontal pixel per meter
self.V_RESOLUTION, # Vertical pixel per meter
self.COLORS_IN_PALETTE, # Number of color, default will be 2^n
self.IMPORTANT_COLOR # Important color
))
# Writing data
for pixel in self.data:
tarte = unpack('<H', pixel)
f.write(pixel)
# Padding data
for i in range( (4 - ( (self.WIDTH*3) % 4) ) % 4):
f.write( pack('B', 0) )
Small example :
# R G B
red = int('0x7C00', 16)
white = int('0x7FFF', 16)
# In BMP, TOP and BOTTOM are reverse !
myImage = ImageManager("test", 200,200, int('0x03E0', 16), ImageManager.LEFT, ImageManager.TOP, ImageManager.HORIZONTAL)
i = 0
while ( i < 500 ):
myImage.add(red)
i = i + 1
# Save image
myImage.render()
myImage.raw_update(0, 0, white)
myImage.raw_update(0, 19, white)
myImage.raw_update(19, 0, white)
myImage.raw_update(19, 19, white)
x = 10
while ( x < 20 ):
y = 10
while ( y < 20 ):
myImage.raw_update(x, y, white)
y = y + 1
x = x + 1