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