2022-12-10 15:14:24 +01:00
|
|
|
tool
|
|
|
|
extends Reference
|
|
|
|
|
|
|
|
class_name GifReader
|
|
|
|
|
|
|
|
var filter = false
|
|
|
|
var mipmaps = false
|
|
|
|
|
|
|
|
var lzw_module = preload("./gif-lzw/lzw.gd")
|
|
|
|
var lzw = lzw_module.new()
|
|
|
|
|
|
|
|
func read(source_file):
|
|
|
|
var file = File.new()
|
|
|
|
if file.open(source_file, File.READ) != OK:
|
|
|
|
return null
|
|
|
|
var data = file.get_buffer(file.get_len())
|
|
|
|
file.close()
|
|
|
|
var pos = 0
|
|
|
|
# Header 'GIF89a'
|
2022-12-10 16:43:43 +01:00
|
|
|
|
|
|
|
var sttr : String = ""
|
|
|
|
for i in range(6):
|
|
|
|
sttr += char(data[i])
|
|
|
|
|
|
|
|
if (sttr != "GIF89a"):
|
|
|
|
print("Can only open GIF89a!")
|
|
|
|
return null
|
|
|
|
|
2022-12-10 15:14:24 +01:00
|
|
|
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 animated_texture = AnimatedTexture.new()
|
|
|
|
animated_texture.fps = 0
|
|
|
|
animated_texture.flags = 0
|
|
|
|
if filter:
|
|
|
|
animated_texture.flags = animated_texture.flags | Texture.FLAG_FILTER
|
|
|
|
if mipmaps:
|
|
|
|
animated_texture.flags = animated_texture.flags | Texture.FLAG_MIPMAPS
|
|
|
|
img.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 = PoolByteArray()
|
|
|
|
while data[pos] != 0:
|
|
|
|
block.append_array(data.subarray(pos + 1, pos + data[pos]))
|
|
|
|
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 or background_color_index != frame_transparent_color:
|
|
|
|
img.fill(local_lut[background_color_index])
|
|
|
|
else:
|
|
|
|
img.fill(Color(0,0,0,0))
|
|
|
|
var p = 0
|
|
|
|
img.lock()
|
|
|
|
for y in range(0, img_height):
|
|
|
|
for x in range(0, img_width):
|
2022-12-10 16:15:38 +01:00
|
|
|
if p >= decompressed.size():
|
|
|
|
break
|
|
|
|
|
2022-12-10 15:14:24 +01:00
|
|
|
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
|
2022-12-10 16:15:38 +01:00
|
|
|
|
2022-12-10 15:14:24 +01:00
|
|
|
img.unlock()
|
|
|
|
var frame = ImageTexture.new()
|
|
|
|
frame.create_from_image(img, 0)
|
|
|
|
animated_texture.set_frame_texture(frame_number, frame)
|
|
|
|
animated_texture.set_frame_delay(frame_number, 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
|
|
|
|
animated_texture.frames = frame_number
|
|
|
|
return animated_texture
|
|
|
|
|
|
|
|
func get_subblock(data, pos):
|
|
|
|
if data[pos] == 0:
|
|
|
|
return null
|
|
|
|
else:
|
|
|
|
return data.subarray(pos + 1, pos + data[pos])
|
|
|
|
|
|
|
|
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)
|