From 227095a4f710ac6afd43f0a7e8b296f188cf20be Mon Sep 17 00:00:00 2001 From: David Luevano Alvarado Date: Tue, 31 May 2022 21:11:46 -0600 Subject: add working gif maker --- .../godot-gdgifexporter/gdgifexporter/exporter.gd | 292 +++++++++++++++++++++ 1 file changed, 292 insertions(+) create mode 100644 src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/exporter.gd (limited to 'src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/exporter.gd') diff --git a/src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/exporter.gd b/src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/exporter.gd new file mode 100644 index 0000000..eb33c27 --- /dev/null +++ b/src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/exporter.gd @@ -0,0 +1,292 @@ +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) -- cgit v1.2.3