Initial Commit
Initial commit of Code Base.
This commit is contained in:
parent
293b1213e1
commit
c11a4ebbc2
653 changed files with 36893 additions and 1 deletions
53
addons/twitcher/media/native/GIF2SpriteFramesPlugin.gd
Normal file
53
addons/twitcher/media/native/GIF2SpriteFramesPlugin.gd
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
# Derived from https://github.com/jegor377/godot-gdgifexporter
|
||||
|
||||
@tool
|
||||
extends EditorImportPlugin
|
||||
|
||||
class_name GifImporterNative
|
||||
|
||||
enum Presets { DEFAULT }
|
||||
|
||||
func _get_importer_name() -> String:
|
||||
return "gif.animated.texture.plugin"
|
||||
|
||||
func _get_visible_name() -> String:
|
||||
return "Sprite Frames (Native)"
|
||||
|
||||
func _get_recognized_extensions() -> PackedStringArray:
|
||||
return ["gif"]
|
||||
|
||||
func _get_save_extension() -> String:
|
||||
return "res"
|
||||
|
||||
func _get_resource_type() -> String:
|
||||
return "SpriteFrames"
|
||||
|
||||
func _get_priority() -> float:
|
||||
return 90.0;
|
||||
|
||||
func _get_preset_count() -> int:
|
||||
return Presets.size()
|
||||
|
||||
func _get_preset_name(preset_index: int) -> String:
|
||||
return "Default"
|
||||
|
||||
func _get_import_options(path: String, preset_index: int) -> Array[Dictionary]:
|
||||
return []
|
||||
|
||||
func _get_import_order() -> int:
|
||||
return 0
|
||||
|
||||
func _get_option_visibility(path: String, option_name: StringName, options: Dictionary) -> bool:
|
||||
return true
|
||||
|
||||
func _import(source_file: String, save_path: String, options: Dictionary, platform_variants: Array[String], gen_files: Array[String]) -> Error:
|
||||
var reader = GifReader.new()
|
||||
var tex = reader.read(source_file)
|
||||
if tex == null:
|
||||
return FAILED
|
||||
var filename = save_path + "." + _get_save_extension()
|
||||
return ResourceSaver.save(
|
||||
tex,
|
||||
"%s.%s" % [save_path, _get_save_extension()],
|
||||
ResourceSaver.SaverFlags.FLAG_COMPRESS
|
||||
)
|
||||
|
|
@ -0,0 +1 @@
|
|||
uid://cr3b26x6lrmf6
|
||||
128
addons/twitcher/media/native/GIFReader.gd
Normal file
128
addons/twitcher/media/native/GIFReader.gd
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
@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)
|
||||
1
addons/twitcher/media/native/GIFReader.gd.uid
Normal file
1
addons/twitcher/media/native/GIFReader.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://m6k2g1678fpc
|
||||
2
addons/twitcher/media/native/README.md
Normal file
2
addons/twitcher/media/native/README.md
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
# Known Bugs
|
||||
- https://github.com/ImageMagick/ImageMagick/issues/4634 exists in this lib too. Images that are not correctly encoded makes problems
|
||||
21
addons/twitcher/media/native/gif-lzw/LICENSE
Normal file
21
addons/twitcher/media/native/gif-lzw/LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2020 Igor Santarek
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
31
addons/twitcher/media/native/gif-lzw/lsbbitpacker.gd
Normal file
31
addons/twitcher/media/native/gif-lzw/lsbbitpacker.gd
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
@tool
|
||||
extends RefCounted
|
||||
|
||||
class LSBLZWBitPacker:
|
||||
var bit_index: int = 0
|
||||
var stream: int = 0
|
||||
|
||||
var chunks: PackedByteArray = PackedByteArray([])
|
||||
|
||||
func put_byte():
|
||||
chunks.append(stream & 0xff)
|
||||
bit_index -= 8
|
||||
stream >>= 8
|
||||
|
||||
func write_bits(value: int, bits_count: int) -> void:
|
||||
value &= (1 << bits_count) - 1
|
||||
value <<= bit_index
|
||||
stream |= value
|
||||
bit_index += bits_count
|
||||
while bit_index >= 8:
|
||||
put_byte()
|
||||
|
||||
func pack() -> PackedByteArray:
|
||||
if bit_index != 0:
|
||||
put_byte()
|
||||
return chunks
|
||||
|
||||
func reset() -> void:
|
||||
bit_index = 0
|
||||
stream = 0
|
||||
chunks = PackedByteArray([])
|
||||
1
addons/twitcher/media/native/gif-lzw/lsbbitpacker.gd.uid
Normal file
1
addons/twitcher/media/native/gif-lzw/lsbbitpacker.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://rbrgncjo0m63
|
||||
44
addons/twitcher/media/native/gif-lzw/lsbbitunpacker.gd
Normal file
44
addons/twitcher/media/native/gif-lzw/lsbbitunpacker.gd
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
@tool
|
||||
extends RefCounted
|
||||
|
||||
class LSBLZWBitUnpacker:
|
||||
var chunk_stream: PackedByteArray
|
||||
var bit_index: int = 0
|
||||
var byte: int
|
||||
var byte_index: int = 0
|
||||
|
||||
func _init(_chunk_stream: PackedByteArray):
|
||||
chunk_stream = _chunk_stream
|
||||
get_byte()
|
||||
|
||||
func get_bit(value: int, index: int) -> int:
|
||||
return (value >> index) & 1
|
||||
|
||||
func set_bit(value: int, index: int) -> int:
|
||||
return value | (1 << index)
|
||||
|
||||
func get_byte():
|
||||
byte = chunk_stream[byte_index]
|
||||
byte_index += 1
|
||||
bit_index = 0
|
||||
|
||||
func read_bits(bits_count: int) -> int:
|
||||
var result: int = 0
|
||||
var result_bit_index: int = 0
|
||||
|
||||
for _i in range(bits_count):
|
||||
if get_bit(byte, bit_index) == 1:
|
||||
result = set_bit(result, result_bit_index)
|
||||
result_bit_index += 1
|
||||
bit_index += 1
|
||||
|
||||
if chunk_stream.size() == byte_index && result_bit_index == bits_count:
|
||||
return result;
|
||||
|
||||
if bit_index == 8:
|
||||
get_byte()
|
||||
|
||||
return result
|
||||
|
||||
func remove_bits(bits_count: int) -> void:
|
||||
read_bits(bits_count)
|
||||
|
|
@ -0,0 +1 @@
|
|||
uid://cjogpplbpf7hp
|
||||
213
addons/twitcher/media/native/gif-lzw/lzw.gd
Normal file
213
addons/twitcher/media/native/gif-lzw/lzw.gd
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
@tool
|
||||
extends RefCounted
|
||||
|
||||
var lsbbitpacker = preload("./lsbbitpacker.gd")
|
||||
var lsbbitunpacker = preload("./lsbbitunpacker.gd")
|
||||
|
||||
class CodeEntry:
|
||||
var sequence: PackedByteArray
|
||||
var raw_array: PackedByteArray
|
||||
|
||||
func _init(_sequence : PackedByteArray) -> void:
|
||||
raw_array = _sequence
|
||||
sequence = _sequence
|
||||
|
||||
func add(other) -> CodeEntry:
|
||||
return CodeEntry.new(raw_array + other.raw_array)
|
||||
|
||||
func to_string() -> String:
|
||||
var result: String = ""
|
||||
for element in sequence:
|
||||
result += str(element) + ", "
|
||||
return result.substr(0, result.length() - 2)
|
||||
|
||||
|
||||
class CodeTable:
|
||||
var entries: Dictionary = {}
|
||||
var counter: int = 0
|
||||
var lookup: Dictionary = {}
|
||||
|
||||
func add(entry: CodeEntry) -> int:
|
||||
entries[counter] = entry
|
||||
lookup[entry.raw_array] = counter
|
||||
counter += 1
|
||||
return counter
|
||||
|
||||
func find(entry: CodeEntry) -> int:
|
||||
return lookup.get(entry.raw_array, -1)
|
||||
|
||||
func has_entry(entry: CodeEntry) -> bool:
|
||||
return find(entry) != -1
|
||||
|
||||
func get_entry(index: int) -> CodeEntry:
|
||||
return entries.get(index, null)
|
||||
|
||||
func to_string() -> String:
|
||||
var result: String = "CodeTable:\n"
|
||||
for id in entries:
|
||||
result += str(id) + ": " + entries[id].to_string() + "\n"
|
||||
result += "Counter: " + str(counter) + "\n"
|
||||
return result
|
||||
|
||||
|
||||
func log2(value: float) -> float:
|
||||
return log(value) / log(2.0)
|
||||
|
||||
func get_bits_number_for(value: int) -> int:
|
||||
if value == 0:
|
||||
return 1
|
||||
return int(ceil(log2(value + 1)))
|
||||
|
||||
func initialize_color_code_table(colors: PackedByteArray) -> CodeTable:
|
||||
var result_code_table: CodeTable = CodeTable.new()
|
||||
for color_id in colors:
|
||||
# warning-ignore:return_value_discarded
|
||||
result_code_table.add(CodeEntry.new([color_id]))
|
||||
# move counter to the first available compression code index
|
||||
var last_color_index: int = colors.size() - 1
|
||||
var clear_code_index: int = pow(2, get_bits_number_for(last_color_index))
|
||||
result_code_table.counter = clear_code_index + 2
|
||||
return result_code_table
|
||||
|
||||
|
||||
# compression and decompression done with source:
|
||||
# http://www.matthewflickinger.com/lab/whatsinagif/lzw_image_data.asp
|
||||
|
||||
|
||||
func compress_lzw(image: PackedByteArray, colors: PackedByteArray) -> Array:
|
||||
# Initialize code table
|
||||
var code_table: CodeTable = initialize_color_code_table(colors)
|
||||
# Clear Code index is 2**<code size>
|
||||
# <code size> is the amount of bits needed to write down all colors
|
||||
# from color table. We use last color index because we can write
|
||||
# all colors (for example 16 colors) with indexes from 0 to 15.
|
||||
# Number 15 is in binary 0b1111, so we'll need 4 bits to write all
|
||||
# colors down.
|
||||
var last_color_index: int = colors.size() - 1
|
||||
var clear_code_index: int = pow(2, get_bits_number_for(last_color_index))
|
||||
var index_stream: PackedByteArray = image
|
||||
var current_code_size: int = get_bits_number_for(clear_code_index)
|
||||
var binary_code_stream = lsbbitpacker.LSBLZWBitPacker.new()
|
||||
|
||||
# initialize with Clear Code
|
||||
binary_code_stream.write_bits(clear_code_index, current_code_size)
|
||||
|
||||
# Read first index from index stream.
|
||||
var index_buffer: CodeEntry = CodeEntry.new([index_stream[0]])
|
||||
var data_index: int = 1
|
||||
# <LOOP POINT>
|
||||
while data_index < index_stream.size():
|
||||
# Get the next index from the index stream.
|
||||
var k: CodeEntry = CodeEntry.new([index_stream[data_index]])
|
||||
data_index += 1
|
||||
# Is index buffer + k in our code table?
|
||||
var new_index_buffer: CodeEntry = index_buffer.add(k)
|
||||
if code_table.has_entry(new_index_buffer): # if YES
|
||||
# Add k to the end of the index buffer
|
||||
index_buffer = new_index_buffer
|
||||
else: # if NO
|
||||
# Add a row for index buffer + k into our code table
|
||||
binary_code_stream.write_bits(code_table.find(index_buffer), current_code_size)
|
||||
|
||||
# We don't want to add new code to code table if we've exceeded 4095
|
||||
# index.
|
||||
var last_entry_index: int = code_table.counter - 1
|
||||
if last_entry_index != 4095:
|
||||
# Output the code for just the index buffer to our code stream
|
||||
# warning-ignore:return_value_discarded
|
||||
code_table.add(new_index_buffer)
|
||||
else:
|
||||
# if we exceeded 4095 index (code table is full), we should
|
||||
# output Clear Code and reset everything.
|
||||
binary_code_stream.write_bits(clear_code_index, current_code_size)
|
||||
code_table = initialize_color_code_table(colors)
|
||||
# get_bits_number_for(clear_code_index) is the same as
|
||||
# LZW code size + 1
|
||||
current_code_size = get_bits_number_for(clear_code_index)
|
||||
|
||||
# Detect when you have to save new codes in bigger bits boxes
|
||||
# change current code size when it happens because we want to save
|
||||
# flexible code sized codes
|
||||
var new_code_size_candidate: int = get_bits_number_for(code_table.counter - 1)
|
||||
if new_code_size_candidate > current_code_size:
|
||||
current_code_size = new_code_size_candidate
|
||||
|
||||
# Index buffer is set to k
|
||||
index_buffer = k
|
||||
# Output code for contents of index buffer
|
||||
binary_code_stream.write_bits(code_table.find(index_buffer), current_code_size)
|
||||
|
||||
# output end with End Of Information Code
|
||||
binary_code_stream.write_bits(clear_code_index + 1, current_code_size)
|
||||
|
||||
var min_code_size: int = get_bits_number_for(clear_code_index) - 1
|
||||
|
||||
return [binary_code_stream.pack(), min_code_size]
|
||||
|
||||
|
||||
# gdlint: ignore=max-line-length
|
||||
func decompress_lzw(code_stream_data: PackedByteArray, min_code_size: int, colors: PackedByteArray) -> PackedByteArray:
|
||||
var code_table: CodeTable = initialize_color_code_table(colors)
|
||||
var index_stream: PackedByteArray = PackedByteArray([])
|
||||
var binary_code_stream = lsbbitunpacker.LSBLZWBitUnpacker.new(code_stream_data)
|
||||
var current_code_size: int = min_code_size + 1
|
||||
var clear_code_index: int = pow(2, min_code_size)
|
||||
|
||||
# CODE is an index of code table, {CODE} is sequence inside
|
||||
# code table with index CODE. The same goes for PREVCODE.
|
||||
|
||||
# let CODE be the first code in the code stream
|
||||
var code: int = binary_code_stream.read_bits(current_code_size)
|
||||
|
||||
# Remove first Clear Code from stream. We don't need it.
|
||||
if code == clear_code_index:
|
||||
code = binary_code_stream.read_bits(current_code_size);
|
||||
|
||||
# output {CODE} to index stream
|
||||
index_stream.append_array(code_table.get_entry(code).sequence)
|
||||
# set PREVCODE = CODE
|
||||
var prevcode: int = code
|
||||
# <LOOP POINT>
|
||||
while true:
|
||||
# let CODE be the next code in the code stream
|
||||
code = binary_code_stream.read_bits(current_code_size)
|
||||
# Detect Clear Code. When detected reset everything and get next code.
|
||||
if code == clear_code_index:
|
||||
code_table = initialize_color_code_table(colors)
|
||||
current_code_size = min_code_size + 1
|
||||
code = binary_code_stream.read_bits(current_code_size)
|
||||
index_stream.append_array(code_table.get_entry(code).sequence)
|
||||
prevcode = code
|
||||
continue
|
||||
elif code == clear_code_index + 1: # Stop when detected EOI Code.
|
||||
break
|
||||
# is CODE in the code table?
|
||||
var code_entry: CodeEntry = code_table.get_entry(code)
|
||||
if code_entry != null: # if YES
|
||||
# output {CODE} to index stream
|
||||
index_stream.append_array(code_entry.sequence)
|
||||
# let k be the first index in {CODE}
|
||||
var k: CodeEntry = CodeEntry.new([code_entry.sequence[0]])
|
||||
# warning-ignore:return_value_discarded
|
||||
# add {PREVCODE} + k to the code table
|
||||
code_table.add(code_table.get_entry(prevcode).add(k))
|
||||
# set PREVCODE = CODE
|
||||
prevcode = code
|
||||
else: # if NO
|
||||
# let k be the first index of {PREVCODE}
|
||||
var prevcode_entry: CodeEntry = code_table.get_entry(prevcode)
|
||||
var k: CodeEntry = CodeEntry.new([prevcode_entry.sequence[0]])
|
||||
# output {PREVCODE} + k to index stream
|
||||
index_stream.append_array(prevcode_entry.add(k).sequence)
|
||||
# add {PREVCODE} + k to code table
|
||||
# warning-ignore:return_value_discarded
|
||||
code_table.add(prevcode_entry.add(k))
|
||||
# set PREVCODE = CODE
|
||||
prevcode = code
|
||||
|
||||
# Detect when we should increase current code size and increase it.
|
||||
var new_code_size_candidate: int = get_bits_number_for(code_table.counter)
|
||||
if new_code_size_candidate > current_code_size:
|
||||
current_code_size = new_code_size_candidate
|
||||
|
||||
return index_stream
|
||||
1
addons/twitcher/media/native/gif-lzw/lzw.gd.uid
Normal file
1
addons/twitcher/media/native/gif-lzw/lzw.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://c2gildpalbfq1
|
||||
Loading…
Add table
Add a link
Reference in a new issue