pokepurple/addons/twitcher/media/native/GIFReader.gd
Mario Steele c11a4ebbc2 Initial Commit
Initial commit of Code Base.
2025-06-12 14:31:14 -05:00

128 lines
4 KiB
GDScript

@tool
extends RefCounted
class_name GifReader
var lzw_module = preload("./gif-lzw/lzw.gd")
var lzw = lzw_module.new()
func read(source_file) -> SpriteFrames:
var file = FileAccess.open(source_file, FileAccess.READ)
if file == null:
return null
var data = file.get_buffer(file.get_length())
file.close()
return load_gif(data)
func load_gif(data):
var pos = 0
# Header 'GIF89a'
pos = pos + 6
# Logical Screen Descriptor
var width = get_int(data, pos)
var height = get_int(data, pos + 2)
var packed_info = data[pos + 4]
var background_color_index = data[pos + 5]
pos = pos + 7
# Global color table
var global_lut
if (packed_info & 0x80) != 0:
var lut_size = 1 << (1 + (packed_info & 0x07))
global_lut = get_lut(data, pos, lut_size)
pos = pos + 3 * lut_size
# Frames
var repeat = -1
var img = Image.new()
var frame_number = 0
var frame_delay = -1
var frame_anim_packed_info = -1
var frame_transparent_color = -1
var sprite_frame = SpriteFrames.new()
img = Image.create(width, height, false, Image.FORMAT_RGBA8)
while pos < data.size():
if data[pos] == 0x21: # Extension block
var ext_type = data[pos + 1]
pos = pos + 2 # 21 xx ...
match ext_type:
0xF9: # Graphic extension
var subblock = get_subblock(data, pos)
frame_anim_packed_info = subblock[0]
frame_delay = get_int(subblock, 1)
frame_transparent_color = subblock[3]
0xFF: # Application extension
var subblock = get_subblock(data, pos)
if subblock != null and subblock.get_string_from_ascii() == "NETSCAPE2.0":
subblock = get_subblock(data, pos + 1 + subblock.size())
repeat = get_int(subblock, 1)
_: # Miscelaneous extension
#print("extension ", data[pos + 1])
pass
var block_len = 0
while data[pos + block_len] != 0:
block_len = block_len + data[pos + block_len] + 1
pos = pos + block_len + 1
elif data[pos] == 0x2C: # Image data
var img_left = get_int(data, pos + 1)
var img_top = get_int(data, pos + 3)
var img_width = get_int(data, pos + 5)
var img_height = get_int(data, pos + 7)
var img_packed_info = get_int(data, pos + 9)
pos = pos + 10
# Local color table
var local_lut = global_lut
if (img_packed_info & 0x80) != 0:
var lut_size = 1 << (1 + (img_packed_info & 0x07))
local_lut = get_lut(data, pos, lut_size)
pos = pos + 3 * lut_size
# Image data
var min_code_size = data[pos]
pos = pos + 1
var colors = []
for i in range(0, 1 << min_code_size):
colors.append(i)
var block = PackedByteArray()
while data[pos] != 0:
block.append_array(data.slice(pos + 1, pos + data[pos] + 1))
pos = pos + data[pos] + 1
pos = pos + 1
var decompressed = lzw.decompress_lzw(block, min_code_size, colors)
var disposal = (frame_anim_packed_info >> 2) & 7 # 1 = Keep, 2 = Clear
var transparency = frame_anim_packed_info & 1
if disposal == 2:
if transparency == 0 and background_color_index != frame_transparent_color:
img.fill(local_lut[background_color_index])
else:
img.fill(Color(0,0,0,0))
var p = 0
for y in range(0, img_height):
for x in range(0, img_width):
var c = decompressed[p]
if transparency == 0 or c != frame_transparent_color:
img.set_pixel(img_left + x, img_top + y, local_lut[c])
p = p + 1
var frame = ImageTexture.create_from_image(img);
sprite_frame.add_frame(&"default", frame, frame_delay / 100.0);
frame_anim_packed_info = -1
frame_transparent_color = -1
frame_delay = -1
frame_number = frame_number + 1
elif data[pos] == 0x3B: # Trailer
pos = pos + 1
sprite_frame.set_animation_speed(&"default", 1);
return sprite_frame
func get_subblock(data: PackedByteArray, pos):
if data[pos] == 0:
return null
else:
return data.slice(pos + 1, pos + data[pos] + 1)
func get_lut(data, pos, size):
var colors = Array()
for i in range(0, size):
colors.append(Color(data[pos + i * 3] / 255.0, data[pos + 1 + i * 3] / 255.0, data[pos + 2 + i * 3] / 255.0))
return colors
func get_int(data, pos):
return data[pos] + (data[pos + 1] << 8)