def import_data(self, buffer, parent=None): enable = mrc.property_get(self.enable, parent) if not enable: return mrc.TransformResult(payload=buffer, end_offset=len(buffer)) output = bytearray(len(buffer) * 2) for i in range(len(buffer)): output[2 * i] = buffer[i] & 0x0f output[2 * i + 1] = buffer[i] >> 4 return mrc.TransformResult(payload=output, end_offset=len(buffer))
def update_buffer_with_value(self, value, buffer, parent=None): super().update_buffer_with_value(value, buffer, parent) offset = mrc.property_get(self.offset, parent) length = self.get_size(value, parent) remainder = value if len(buffer) < offset + length: buffer.extend(b'\x00' * (offset + length - len(buffer))) for i in range(length): buffer[offset + i] = remainder & 0x7f remainder >>= 7 if remainder == 0: buffer[offset + i] |= 0x80 break remainder -= 1 return
def export_data(self, buffer, parent=None): enable = mrc.property_get(self.enable, parent) if not enable: return mrc.TransformResult(payload=buffer) if buffer: assert max(buffer) <= 0xf output = bytearray(len(buffer) // 2) for i in range(len(buffer)): if i % 2: output[i // 2] |= buffer[i] << 4 else: output[i // 2] |= buffer[i] return mrc.TransformResult(payload=output, end_offset=len(buffer))
def update_buffer_with_value( self, value, buffer, parent=None ): super().update_buffer_with_value( value, buffer, parent ) offset = mrc.property_get( self.offset, parent ) length = self.get_size( value, parent ) remainder = value if len( buffer ) < offset+length: buffer.extend( b'\x00'*(offset+length-len( buffer )) ) for i in range( length ): buffer[offset+i] = remainder & 0x7f remainder >>= 7 if remainder == 0: buffer[offset+i] |= 0x80 break remainder -= 1 return
def get_from_buffer(self, buffer, parent=None): assert utils.is_bytes(buffer) offset = mrc.property_get(self.offset, parent) pointer = offset total = 0 shift = 0 while pointer < len(buffer): test = buffer[pointer] pointer += 1 total += (test & 0x7f) << shift shift += 7 if test & 0x80: break total += 1 << shift return total
def get_from_buffer( self, buffer, parent=None ): assert utils.is_bytes( buffer ) offset = mrc.property_get( self.offset, parent ) pointer = offset total = 0 shift = 0 while pointer < len( buffer ): test = buffer[pointer] pointer += 1 total += (test & 0x7f) << shift shift += 7 if test & 0x80: break total += 1 << shift return total
def export_data(self, buffer: bytes, parent=None): assert utils.is_bytes(buffer) # load in constructor properties bpp = mrc.property_get(self.bpp, parent) width = mrc.property_get(self.width, parent) height = mrc.property_get(self.height, parent) plane_size = mrc.property_get(self.plane_size, parent) plane_padding = mrc.property_get(self.plane_padding, parent) frame_offset = mrc.property_get(self.frame_offset, parent) frame_count = mrc.property_get(self.frame_count, parent) frame_stride = mrc.property_get(self.frame_stride, parent) assert (bpp >= 0) and (bpp <= 8) if (width or height): assert (width * height) % 8 == 0 if plane_size: raise Exception( 'Can\'t define plane_size when either width or height is defined.' ) elif plane_size is None and frame_count == 1: # for a single frame without a plane size, assume the buffer contains everything assert len(buffer) % bpp == 0 plane_size = len(buffer) // bpp else: assert plane_size is not None if not plane_size: plane_size = math.ceil(width * height / 8) assert (frame_count >= 1) if frame_count >= 2 and frame_stride is None: frame_stride = bpp * (plane_size + plane_padding) else: frame_stride = frame_stride if frame_stride is not None else 0 if frame_count == 1: assert len(buffer) >= frame_offset + plane_size * 8 else: assert len(buffer) >= frame_offset + frame_count * frame_stride # this method just does the opposite of the above; split chunky pixels back into planes. planes = array('Q') segment_size = plane_size + plane_padding if frame_count == 1: raw_planes = bytearray(frame_offset + segment_size * bpp) else: raw_planes = bytearray(frame_offset + frame_count * frame_stride) for f in range(frame_count): pointer = frame_offset + f * frame_stride planes = planes[0:0] # load our chunky pixels into the 64-bit int array planes.frombytes(buffer[f * plane_size * 8:(f + 1) * plane_size * 8]) # check for endianness! if sys.byteorder == 'little': planes.byteswap() for b in range(bpp): for i in range(plane_size): # for each group of 8 chunky pixels, use pack_bits to fill up 8 bits # of the relevant bitplane raw_planes[pointer + b * segment_size + i] = bits.pack_bits((planes[i] >> b)) return mrc.TransformResult(payload=raw_planes)
def import_data(self, buffer: bytes, parent=None): assert utils.is_bytes(buffer) # load in constructor properties bpp = mrc.property_get(self.bpp, parent) width = mrc.property_get(self.width, parent) height = mrc.property_get(self.height, parent) plane_size = mrc.property_get(self.plane_size, parent) plane_padding = mrc.property_get(self.plane_padding, parent) frame_offset = mrc.property_get(self.frame_offset, parent) frame_count = mrc.property_get(self.frame_count, parent) frame_stride = mrc.property_get(self.frame_stride, parent) row_planar_size = mrc.property_get(self.row_planar_size, parent) plane_order = mrc.property_get(self.plane_order, parent) assert (bpp >= 0) and (bpp <= 8) if (width or height): assert (width * height) % 8 == 0 if plane_size: raise Exception( 'Can\'t define plane_size when either width or height is defined.' ) elif plane_size is None and frame_count == 1: # for a single frame without a plane size, assume the buffer contains everything assert len(buffer) % bpp == 0 plane_size = len(buffer) // bpp else: assert plane_size is not None assert (frame_count >= 1) if plane_size is None: plane_size = math.ceil(width * height / 8) if frame_count >= 2 and frame_stride is None: frame_stride = bpp * (plane_size + plane_padding) else: frame_stride = frame_stride if frame_stride is not None else 0 if row_planar_size: assert row_planar_size >= 1 if not plane_order: plane_order = range(bpp) else: assert all([y in range(bpp) for y in plane_order]) assert len(plane_order) == len(set(plane_order)) # because frame_stride can potentially read past the buffer, only worry about measuring # the last n-1 strides + one frame assert len(buffer) >= frame_offset + ( frame_count - 1) * frame_stride + bpp * plane_size # our output is going to be "chunky"; each byte is a pixel (8-bit or 256 colour mode) raw_image = bytearray(plane_size * frame_count) # the input is planar. this is a packed format found occasionally in old graphics hardware, # and in old image formats where space was paramount. # the trick is you can have less than 8 bits in your colourspace! # e.g. if you only need 8 colours, you can get away with a 3-bit colourspace and save 62.5% space. # instead of each byte being a pixel, each byte stores 8 pixels worth of data for a single plane. # there is one plane per bit of colourspace, and the planes are stored one after another. # in order for the calculations to be fast, planar graphics are pretty much always divisible by 8. # we're going to abuse this and unpack our bitplanes using 64-bit integers. # let's make a big array of them. planes = array('Q', (0, ) * (plane_size)) segment_size = plane_size + plane_padding for f in range(frame_count): pointer = frame_offset + f * frame_stride for bi, b in enumerate(plane_order): for i in range(plane_size): # for the first iteration, clear the plane if bi == 0: planes[i] = 0 if row_planar_size is None: address = pointer + b * segment_size + i else: address = pointer + (row_planar_size * bpp) * ( i // row_planar_size) + row_planar_size * b + ( i % row_planar_size) # bits.unpack_bits is a helper method which converts a 1-byte bitfield # into 8 bool bytes (i.e. 1 or 0) stored as a 64-bit int. # we can effectively work on 8 chunky pixels at once! # because the chunky pixels are bitfields, combining planes is an easy # left shift (i.e. move all the bits up by [plane ID] places) and bitwise OR planes[i] |= bits.unpack_bits(buffer[address]) << bi # check for endianness! for most intel and ARM chips the order of bytes in hardware is reversed, # so we need to flip it around for the bytes to be sequential. if sys.byteorder == 'little': planes.byteswap() # convert our planes array to bytes, and you have your chunky pixels raw_image[f * plane_size * 8:(f + 1) * plane_size * 8] = planes.tobytes() if frame_count > 1: end_offset = frame_offset + frame_count * frame_stride else: plane_bits = plane_size * 8 * bpp end_offset = frame_offset + (plane_bits) // 8 + (1 if (plane_bits % 8) else 0) return mrc.TransformResult(payload=bytes(raw_image), end_offset=end_offset)
def ansi_format_iter(self, x_start=0, y_start=0, width=None, height=None, frame=0, columns=1, downsample=1, frame_index=None, frame_flip_v=0, frame_flip_h=0): """Return the ANSI escape sequence to render the image. x_start Offset from the left of the image data to render from. Defaults to 0. y_start Offset from the top of the image data to render from. Defaults to 0. width Width of the image data to render. Defaults to the image width. height Height of the image data to render. Defaults to the image height. frame Single frame number/object, or a list of frames to render in sequence. Defaults to frame 0. columns Number of frames to render per line (useful for printing tilemaps!). Defaults to 1. downsample Shrink larger images by printing every nth pixel only. Defaults to 1. frame_index Constant or mrc.Ref for a frame object property denoting the index. Defaults to None (i.e. frame itself should be an index). frame_flip_v Constant or mrc.Ref for a frame object property for whether to mirror vertically. Defaults to 0. frame_flip_h Constant or mrc.Ref for a frame object property for whether to mirror horizontally. Defaults to 0. """ assert x_start in range(0, self.width) assert y_start in range(0, self.height) if frame_index is not None: fn_index = lambda fr: mrc.property_get(frame_index, fr) else: fn_index = lambda fr: fr if fr in range(0, self.frame_count ) else None fn_flip_v = lambda fr: mrc.property_get(frame_flip_v, fr) fn_flip_h = lambda fr: mrc.property_get(frame_flip_h, fr) frames = [] try: frame_iter = iter(frame) frames = [f for f in frame_iter] except TypeError: frames = [frame] if not width: width = self.width - x_start if not height: height = self.height - y_start stride = width * height image_size = stride * self.frame_count self.overflow = False self.overflow_area = '' self.overflow_size = 0 def data_fetch(x, y, fr_obj): fr = fn_index(fr_obj) if fr is None: return Transparent() if not ((0 <= x < self.width) and (0 <= y < self.height)): return Transparent() if fn_flip_h(fr_obj): x = self.width - x - 1 if fn_flip_v(fr_obj): y = self.height - y - 1 index = self.width * y + x offset = stride * fr + index if offset >= len(self.source): if not self.overflow: self.overflow = True self.overflow_area = 'source' self.overflow_size = len(self.source) return Transparent() p = self.source[offset] if self.mask: if offset >= len(self.mask): if not self.overflow: self.overflow = True self.overflow_area = 'mask' self.overflow_size = len(self.mask) return Transparent() p = p if self.mask[offset] else None return self.palette[p] if p is not None else Transparent() for x in ansi.format_image_iter(data_fetch, x_start, y_start, width, height, frames, columns, downsample): yield x if self.overflow: logger.warning( 'Image {} requires {} pixels but {} only has {} pixels'.format( self, image_size, self.overflow_area, self.overflow_size)) return
def get_start_offset(self, value, parent=None, index=None): assert index is None offset = mrc.property_get(self.offset, parent) return offset
def export_data( self, buffer: bytes, parent=None ): assert utils.is_bytes( buffer ) # load in constructor properties bpp = mrc.property_get( self.bpp, parent ) width = mrc.property_get( self.width, parent ) height = mrc.property_get( self.height, parent ) plane_size = mrc.property_get( self.plane_size, parent ) plane_padding = mrc.property_get( self.plane_padding, parent ) frame_offset = mrc.property_get( self.frame_offset, parent ) frame_count = mrc.property_get( self.frame_count, parent ) frame_stride = mrc.property_get( self.frame_stride, parent ) assert (bpp >= 0) and (bpp <= 8) if (width or height): assert (width*height) % 8 == 0 if plane_size: raise Exception( 'Can\'t define plane_size when either width or height is defined.' ) elif plane_size is None and frame_count == 1: # for a single frame without a plane size, assume the buffer contains everything assert len( buffer ) % bpp == 0 plane_size = len( buffer ) // bpp else: assert plane_size is not None if not plane_size: plane_size = math.ceil( width*height/8 ) assert (frame_count >= 1) if frame_count >= 2 and frame_stride is None: frame_stride = bpp*(plane_size+plane_padding) else: frame_stride = frame_stride if frame_stride is not None else 0 if frame_count == 1: assert len( buffer ) >= frame_offset + plane_size*8 else: assert len( buffer ) >= frame_offset + frame_count*frame_stride # this method just does the opposite of the above; split chunky pixels back into planes. planes = array( 'Q' ) segment_size = plane_size+plane_padding if frame_count == 1: raw_planes = bytearray( frame_offset+segment_size*bpp ) else: raw_planes = bytearray( frame_offset+frame_count*frame_stride ) for f in range( frame_count ): pointer = frame_offset+f*frame_stride planes = planes[0:0] # load our chunky pixels into the 64-bit int array planes.frombytes( buffer[f*plane_size*8:(f+1)*plane_size*8] ) # check for endianness! if sys.byteorder == 'little': planes.byteswap() for b in range( bpp ): for i in range( plane_size ): # for each group of 8 chunky pixels, use pack_bits to fill up 8 bits # of the relevant bitplane raw_planes[pointer+b*segment_size+i] = utils.pack_bits( (planes[i] >> b) ) return mrc.TransformResult( payload=raw_planes )
def import_data( self, buffer: bytes, parent=None ): assert utils.is_bytes( buffer ) # load in constructor properties bpp = mrc.property_get( self.bpp, parent ) width = mrc.property_get( self.width, parent ) height = mrc.property_get( self.height, parent ) plane_size = mrc.property_get( self.plane_size, parent ) plane_padding = mrc.property_get( self.plane_padding, parent ) frame_offset = mrc.property_get( self.frame_offset, parent ) frame_count = mrc.property_get( self.frame_count, parent ) frame_stride = mrc.property_get( self.frame_stride, parent ) row_planar_size = mrc.property_get( self.row_planar_size, parent ) plane_order = mrc.property_get( self.plane_order, parent ) assert (bpp >= 0) and (bpp <= 8) if (width or height): assert (width*height) % 8 == 0 if plane_size: raise Exception( 'Can\'t define plane_size when either width or height is defined.' ) elif plane_size is None and frame_count == 1: # for a single frame without a plane size, assume the buffer contains everything assert len( buffer ) % bpp == 0 plane_size = len( buffer ) // bpp else: assert plane_size is not None assert (frame_count >= 1) if plane_size is None: plane_size = math.ceil( width*height/8 ) if frame_count >= 2 and frame_stride is None: frame_stride = bpp*(plane_size+plane_padding) else: frame_stride = frame_stride if frame_stride is not None else 0 if row_planar_size: assert row_planar_size >= 1 if not plane_order: plane_order = range( bpp ) else: assert all( [y in range( bpp ) for y in plane_order] ) assert len( plane_order ) == len( set( plane_order ) ) # because frame_stride can potentially read past the buffer, only worry about measuring # the last n-1 strides + one frame assert len( buffer ) >= frame_offset + (frame_count-1)*frame_stride + bpp*plane_size # our output is going to be "chunky"; each byte is a pixel (8-bit or 256 colour mode) raw_image = bytearray( plane_size*frame_count ) # the input is planar. this is a packed format found occasionally in old graphics hardware, # and in old image formats where space was paramount. # the trick is you can have less than 8 bits in your colourspace! # e.g. if you only need 8 colours, you can get away with a 3-bit colourspace and save 62.5% space. # instead of each byte being a pixel, each byte stores 8 pixels worth of data for a single plane. # there is one plane per bit of colourspace, and the planes are stored one after another. # in order for the calculations to be fast, planar graphics are pretty much always divisible by 8. # we're going to abuse this and unpack our bitplanes using 64-bit integers. # let's make a big array of them. planes = array( 'Q', (0,)*(plane_size) ) segment_size = plane_size+plane_padding for f in range( frame_count ): pointer = frame_offset+f*frame_stride for bi, b in enumerate( plane_order ): for i in range( plane_size ): # for the first iteration, clear the plane if bi==0: planes[i] = 0 if row_planar_size is None: address = pointer+b*segment_size+i else: address = pointer + (row_planar_size*bpp)*(i // row_planar_size) + row_planar_size*b + (i % row_planar_size) # utils.unpack_bits is a helper method which converts a 1-byte bitfield # into 8 bool bytes (i.e. 1 or 0) stored as a 64-bit int. # we can effectively work on 8 chunky pixels at once! # because the chunky pixels are bitfields, combining planes is an easy # left shift (i.e. move all the bits up by [plane ID] places) and bitwise OR planes[i] |= utils.unpack_bits( buffer[address] ) << bi # check for endianness! for most intel and ARM chips the order of bytes in hardware is reversed, # so we need to flip it around for the bytes to be sequential. if sys.byteorder == 'little': planes.byteswap() # convert our planes array to bytes, and you have your chunky pixels raw_image[f*plane_size*8:(f+1)*plane_size*8] = planes.tobytes() if frame_count > 1: end_offset = frame_offset + frame_count*frame_stride else: bits = plane_size*8*bpp end_offset = frame_offset + (bits)//8 + (1 if (bits % 8) else 0) return mrc.TransformResult( payload=bytes( raw_image ), end_offset=end_offset )
def ansi_format_iter( self, x_start=0, y_start=0, width=None, height=None, frame=0, columns=1, downsample=1, frame_index=None, frame_flip_v=0, frame_flip_h=0 ): """Return the ANSI escape sequence to render the image. x_start Offset from the left of the image data to render from. Defaults to 0. y_start Offset from the top of the image data to render from. Defaults to 0. width Width of the image data to render. Defaults to the image width. height Height of the image data to render. Defaults to the image height. frame Single frame number/object, or a list of frames to render in sequence. Defaults to frame 0. columns Number of frames to render per line (useful for printing tilemaps!). Defaults to 1. downsample Shrink larger images by printing every nth pixel only. Defaults to 1. frame_index Constant or mrc.Ref for a frame object property denoting the index. Defaults to None (i.e. frame itself should be an index). frame_flip_v Constant or mrc.Ref for a frame object property for whether to mirror vertically. Defaults to 0. frame_flip_h Constant or mrc.Ref for a frame object property for whether to mirror horizontally. Defaults to 0. """ assert x_start in range( 0, self.width ) assert y_start in range( 0, self.height ) if frame_index is not None: fn_index = lambda fr: mrc.property_get( frame_index, fr ) else: fn_index = lambda fr: fr if fr in range( 0, self.frame_count ) else None fn_flip_v = lambda fr: mrc.property_get( frame_flip_v, fr ) fn_flip_h = lambda fr: mrc.property_get( frame_flip_h, fr ) frames = [] try: frame_iter = iter( frame ) frames = [f for f in frame_iter] except TypeError: frames = [frame] if not width: width = self.width-x_start if not height: height = self.height-y_start stride = width*height def data_fetch( x, y, fr_obj ): fr = fn_index( fr_obj ) if fr is None: return Transparent() if not ((0 <= x < self.width) and (0 <= y < self.height)): return Transparent() if fn_flip_h( fr_obj ): x = self.width - x - 1 if fn_flip_v( fr_obj ): y = self.height - y - 1 index = self.width*y + x p = self.source[stride*fr+index] if self.mask: p = p if self.mask[stride*fr+index] else None return self.palette[p] if p is not None else Transparent() for x in ansi.format_image_iter( data_fetch, x_start, y_start, width, height, frames, columns, downsample ): yield x return
def get_start_offset( self, value, parent=None, index=None ): assert index is None offset = mrc.property_get( self.offset, parent ) return offset