extends Reference enum Error { OK = 0, EMPTY_IMAGE = 1, BAD_IMAGE_FORMAT = 2 } var little_endian = preload("./little_endian.gd").new() var lzw = preload("./gif-lzw/lzw.gd").new() var converter = preload("./converter.gd") var last_color_table := [] var last_transparency_index := -1 # File data and Header var data := PoolByteArray([]) func _init(_width: int, _height: int): add_header() add_logical_screen_descriptor(_width, _height) add_application_ext("NETSCAPE", "2.0", [1, 0, 0]) func export_file_data() -> PoolByteArray: return data + PoolByteArray([0x3b]) func add_header() -> void: data += "GIF".to_ascii() + "89a".to_ascii() func add_logical_screen_descriptor(width: int, height: int) -> void: # not Global Color Table Flag # Color Resolution = 8 bits # Sort Flag = 0, not sorted. # Size of Global Color Table set to 0 # because we'll use only Local Tables var packed_fields: int = 0b01110000 var background_color_index: int = 0 var pixel_aspect_ratio: int = 0 data += little_endian.int_to_2bytes(width) data += little_endian.int_to_2bytes(height) data.append(packed_fields) data.append(background_color_index) data.append(pixel_aspect_ratio) func add_application_ext(app_iden: String, app_auth_code: String, _data: Array) -> void: var extension_introducer := 0x21 var extension_label := 0xff var block_size := 11 data.append(extension_introducer) data.append(extension_label) data.append(block_size) data += app_iden.to_ascii() data += app_auth_code.to_ascii() data.append(_data.size()) data += PoolByteArray(_data) data.append(0) func add_comment_ext(string: String) -> void: var extension_introducer := 0x21 var comment_label := 0xFE data.append(extension_introducer) data.append(comment_label) data += string.to_utf8() data.append(0) # finds the image color table. Stops if the size gets larger than 256. func find_color_table(image: Image) -> Dictionary: image.lock() var result: Dictionary = {} var image_data: PoolByteArray = image.get_data() for i in range(0, image_data.size(), 4): var color: Array = [ int(image_data[i]), int(image_data[i + 1]), int(image_data[i + 2]), int(image_data[i + 3]) ] if not color in result: result[color] = result.size() if result.size() > 256: break image.unlock() return result func find_transparency_color_index(color_table: Dictionary) -> int: for color in color_table: if color[3] == 0: return color_table[color] return -1 func colors_to_codes(img: Image, col_palette: Dictionary, transp_color_index: int) -> PoolByteArray: img.lock() var image_data: PoolByteArray = img.get_data() var result: PoolByteArray = PoolByteArray([]) for i in range(0, image_data.size(), 4): var color: Array = [image_data[i], image_data[i + 1], image_data[i + 2], image_data[i + 3]] if color in col_palette: if color[3] == 0 and transp_color_index != -1: result.append(transp_color_index) else: result.append(col_palette[color]) else: result.append(0) push_warning("colors_to_codes: color not found! [%d, %d, %d, %d]" % color) img.unlock() return result # makes sure that the color table is at least size 4. func make_proper_size(color_table: Array) -> Array: var result := [] + color_table if color_table.size() < 4: for i in range(4 - color_table.size()): result.append([0, 0, 0, 0]) return result func color_table_to_indexes(colors: Array) -> PoolByteArray: var result: PoolByteArray = PoolByteArray([]) for i in range(colors.size()): result.append(i) return result func add_frame(image: Image, delay_time: int, quantizator: Script) -> int: # check if image is of good format if image.get_format() != Image.FORMAT_RGBA8: return Error.BAD_IMAGE_FORMAT # check if image isn't empty if image.is_empty(): return Error.EMPTY_IMAGE var found_color_table: Dictionary = find_color_table(image) var image_converted_to_codes: PoolByteArray var transparency_color_index: int = -1 var color_table: Array if found_color_table.size() <= 256: # we don't need to quantize the image. # try to find transparency color index. transparency_color_index = find_transparency_color_index(found_color_table) # if didn't found transparency color index but there is atleast one # place for this color then add it artificially. if transparency_color_index == -1 and found_color_table.size() <= 255: found_color_table[[0, 0, 0, 0]] = found_color_table.size() transparency_color_index = found_color_table.size() - 1 image_converted_to_codes = colors_to_codes( image, found_color_table, transparency_color_index ) color_table = make_proper_size(found_color_table.keys()) else: # we have to quantize the image. var quantization_result: Array = quantizator.new().quantize(image) image_converted_to_codes = quantization_result[0] color_table = quantization_result[1] # transparency index should always be as the first element of color table. transparency_color_index = 0 if quantization_result[2] else -1 last_color_table = color_table last_transparency_index = transparency_color_index var color_table_indexes := color_table_to_indexes(color_table) var compressed_image_result: Array = lzw.compress_lzw( image_converted_to_codes, color_table_indexes ) var compressed_image_data: PoolByteArray = compressed_image_result[0] var lzw_min_code_size: int = compressed_image_result[1] add_graphic_constrol_ext(delay_time, transparency_color_index) add_image_descriptor(Vector2.ZERO, image.get_size(), color_table_bit_size(color_table)) add_local_color_table(color_table) add_image_data_block(lzw_min_code_size, compressed_image_data) return Error.OK # adds frame with last color informations func add_frame_with_lci(image: Image, delay_time: int) -> int: # check if image is of good format if image.get_format() != Image.FORMAT_RGBA8: return Error.BAD_IMAGE_FORMAT # check if image isn't empty if image.is_empty(): return Error.EMPTY_IMAGE var image_converted_to_codes: PoolByteArray = converter.new().get_similar_indexed_datas( image, last_color_table ) var color_table_indexes := color_table_to_indexes(last_color_table) var compressed_image_result: Array = lzw.compress_lzw( image_converted_to_codes, color_table_indexes ) var compressed_image_data: PoolByteArray = compressed_image_result[0] var lzw_min_code_size: int = compressed_image_result[1] add_graphic_constrol_ext(delay_time, last_transparency_index) add_image_descriptor(Vector2.ZERO, image.get_size(), color_table_bit_size(last_color_table)) add_local_color_table(last_color_table) add_image_data_block(lzw_min_code_size, compressed_image_data) return Error.OK func add_graphic_constrol_ext(_delay_time: float, tci: int = -1) -> void: var extension_introducer: int = 0x21 var graphic_control_label: int = 0xf9 var block_size: int = 4 var packed_fields: int = 0b00001000 if tci != -1: packed_fields = 0b00001001 var delay_time: int = _delay_time var transparent_color_index: int = tci if tci != -1 else 0 data.append(extension_introducer) data.append(graphic_control_label) data.append(block_size) data.append(packed_fields) data += little_endian.int_to_2bytes(delay_time) data.append(transparent_color_index) data.append(0) func add_image_descriptor(pos: Vector2, size: Vector2, l_color_table_size: int) -> void: var image_separator: int = 0x2c var packed_fields: int = 0b10000000 | (0b111 & l_color_table_size) data.append(image_separator) data += little_endian.int_to_2bytes(int(pos.x)) # left pos data += little_endian.int_to_2bytes(int(pos.y)) # top pos data += little_endian.int_to_2bytes(int(size.x)) # width data += little_endian.int_to_2bytes(int(size.y)) # height data.append(packed_fields) func color_table_bit_size(color_table: Array) -> int: if color_table.size() <= 1: return 0 var bit_size := int(ceil(log(color_table.size()) / log(2.0))) return bit_size - 1 func add_local_color_table(color_table: Array) -> void: for color in color_table: data.append(color[0]) data.append(color[1]) data.append(color[2]) var size := color_table_bit_size(color_table) var proper_size := int(pow(2, size + 1)) if color_table.size() != proper_size: for i in range(proper_size - color_table.size()): data += PoolByteArray([0, 0, 0]) func add_image_data_block(lzw_min_code_size: int, _data: PoolByteArray) -> void: data.append(lzw_min_code_size) var block_size_index: int = 0 var i: int = 0 var data_index: int = 0 while data_index < _data.size(): if i == 0: data.append(0) block_size_index = data.size() - 1 data.append(_data[data_index]) data[block_size_index] += 1 data_index += 1 i += 1 if i == 254: i = 0 if not _data.empty(): data.append(0)