def _write_chunk(cls, out_file: BinaryIO, img: CursorIcon, delay: int): out_file.write(to_bytes(cls.IMG_CHUNK_H_SIZE, 4)) out_file.write(to_bytes(cls.CURSOR_TYPE, 4)) out_file.write( to_bytes(int(img.image.size[0] * cls.SIZE_SCALING_FACTOR), 4)) out_file.write(to_bytes(1, 4)) # The width and height... width, height = img.image.size x_hot, y_hot = img.hotspot cls._assert(width <= 0x7fff, "Invalid width!") cls._assert(height <= 0x7fff, "Invalid height!") out_file.write(to_bytes(width, 4)) out_file.write(to_bytes(width, 4)) x_hot, y_hot = x_hot if (0 <= x_hot < width) else 0, y_hot if ( 0 <= y_hot < height) else 0 # Hotspot and delay... out_file.write(to_bytes(x_hot, 4)) out_file.write(to_bytes(y_hot, 4)) out_file.write(to_bytes(delay, 4)) # Now the image, ARGB packed in little endian integers...(So really BGRA)(ARGB -> BGRA) im_bytes = (np.asarray(img.image.convert("RGBA"))[:, :, (2, 1, 0, 3)]).tobytes() out_file.write(im_bytes)
def write(cls, cursor: AnimatedCursor, out: BinaryIO): """ Write an AnimatedCursor to the specified file in the windows .ani format. :param cursor: The AnimatedCursor object to write. :param out: The file buffer to write the new .ani data to. """ # Write the magic... out.write(cls.RIFF_MAGIC) # We will deal with writing the length of the entire file later... out.write(b"\0\0\0\0") out.write(cls.ACON_MAGIC) # Write the header... header = bytearray(36) # We write the header length twice for some dumb reason... header[0:4] = to_bytes(36, 4) # Header length... header[4:8] = to_bytes(len(cursor), 4) # Number of frames header[8:12] = to_bytes(len(cursor), 4) # Number of steps # Ignore width, height, and bits per pixel... # The number of planes should always be 1.... header[24:28] = to_bytes(1, 4) header[28:32] = to_bytes(10, 4) # We just pass 10 as the default delay... header[32:36] = to_bytes( 1, 4 ) # The flags, last flag is flipped which specifies data is stored in .cur write_chunk(out, b"anih", header) # Write the LIST of icons... list_data = bytearray(b"fram") delay_data = bytearray() for sub_cursor, delay in cursor: # Writing a single cursor to the list... mem_stream = BytesIO() CurFormat.write(sub_cursor, mem_stream) # We write these chunks manually to avoid wasting a ton of lines of code, as using "write_chunks" ends up # being just as complicated... cur_data = mem_stream.getvalue() list_data.extend(b"icon") list_data.extend(to_bytes(len(cur_data), 4)) list_data.extend(cur_data) # Writing the delay to the rate chunk delay_data.extend(to_bytes(round((delay * 60) / 1000), 4)) # Now that we have gathered the data actually write the chunks... write_chunk(out, b"LIST", list_data) write_chunk(out, b"rate", delay_data) # Now we are to the end, get the length of the file and write it as the RIFF chunk length... entire_file_len = out.tell() - 8 out.seek(4) out.write(to_bytes(entire_file_len, 4))
def write_chunk(buffer: BinaryIO, chunk_id: bytes, chunk_data: bytes, byteorder="little"): """ Writes a chunk to file. :param buffer: The file buffer to write to. :param chunk_id: The 4 byte chunk identifier. :param chunk_data: The chunk's data as a bytes object. :param byteorder: The byteorder to use when writing the byte's size, defaults to "little" """ buffer.write(chunk_id[:4]) buffer.write(to_bytes(len(chunk_data), 4, byteorder=byteorder)) buffer.write(chunk_data)
def write(cls, cursor: Cursor, out: BinaryIO): """ Writes cursor to a file in the form of the windows .cur format... :param cursor: The cursor object to save. :param out: The file handle to output the cursor to. """ cursor.add_sizes([(32, 32)]) out.write(cls.MAGIC) out.write(to_bytes(len(cursor), 2)) offset = out.tell() + len(cursor) * 16 imgs = [] for size in sorted(cursor): width, height = size if (width > 256 or height > 256): continue hot_x, hot_y = cursor[size].hotspot hot_x, hot_y = hot_x if (0 <= hot_x < width) else 0, hot_y if ( 0 <= hot_y < height) else 0 image_data = cls._to_bmp(cursor[size].image, (width, height)) width, height = width if (width < 256) else 0, height if ( height < 256) else 0 out.write(to_bytes(width, 1)) # Width, 1 byte. out.write(to_bytes(height, 1)) # Height, 1 byte. out.write(b"\0\0") out.write(to_bytes(hot_x, 2)) out.write(to_bytes(hot_y, 2)) out.write(to_bytes(len(image_data), 4)) out.write(to_bytes(offset, 4)) offset += len(image_data) imgs.append(image_data) for image_data in imgs: out.write(image_data)
def _write_bmp(img: Image.Image, out_file: BinaryIO): # Grab the dpi for calculations later... dpi = img.info.get("dpi", DEF_BMP_DPI) ppm = tuple(int(dpi_val * 39.3701 + 0.5) for dpi_val in dpi) # BMP HEADER: out_file.write(to_bytes(40, 4)) # BMP Header size out_file.write(to_signed_bytes(img.size[0], 4)) # Image Width out_file.write(to_signed_bytes(img.size[1] * 2, 4)) # Image Height out_file.write(to_bytes(1, 2)) # Number of planes out_file.write(to_bytes(32, 2)) # The bits per pixel... out_file.write(to_bytes( 0, 4)) # The compression method, we set it to raw or no compression... out_file.write(to_bytes(4 * img.size[0] * (img.size[1] * 2), 4)) # The size of the image data... out_file.write(to_signed_bytes( ppm[0], 4)) # The resolution of the width in pixels per meter... out_file.write(to_signed_bytes( ppm[1], 4)) # The resolution of the height in pixels per meter... out_file.write(to_bytes( 0, 4)) # The number of colors in the color table, in this case none... out_file.write(to_bytes( 0, 4)) # Number of important colors in the color table, again none... img = img.convert("RGBA") data = np.array(img) # Create the alpha channel... alpha_channel = data[:, :, 3] alpha_channel = np.packbits(alpha_channel == 0, axis=1)[::-1] # Create the main image with transparency... bgrx_data: np.ndarray = (data[::-1, :, (2, 1, 0, 3)]) # Dump the main image... out_file.write(bgrx_data.tobytes()) # We now dump the mask and some zeros to finish filling the space... mask_data = alpha_channel.tobytes() leftover_space = (img.size[0] * img.size[1] * 4) - len(mask_data) out_file.write(mask_data) out_file.write(bytes(leftover_space))
def write(cls, cursor: AnimatedCursor, out: BinaryIO): """ Write an AnimatedCursor to the specified file in the X-Org cursor format. :param cursor: The AnimatedCursor object to write. :param out: The file buffer to write the new X-Org Cursor data to. """ cursor = cursor.copy() cursor.normalize() if (len(cursor) == 0): return num_curs = sum(len(l) for l, delay in cursor) out.write(cls.MAGIC) out.write(to_bytes(cls.HEADER_SIZE, 4)) out.write(to_bytes(cls.VERSION, 4)) out.write(to_bytes(num_curs, 4)) # The initial offset... offset = (num_curs * 12 + cls.HEADER_SIZE) sorted_sizes = sorted(cursor[0][0]) # Write the Table of contents [type, subtype(size), offset] for size in sorted_sizes: for sub_cur, delay in cursor: out.write(to_bytes(cls.CURSOR_TYPE, 4)) out.write(to_bytes(int(size[0] * cls.SIZE_SCALING_FACTOR), 4)) out.write(to_bytes(offset, 4)) offset += cls.IMG_CHUNK_H_SIZE + (size[0] * size[1] * 4) # Write the actual images... for size in sorted_sizes: for sub_cur, delay in cursor: cls._write_chunk(out, sub_cur[size], delay)