mirror of
https://github.com/Relintai/MemR.git
synced 2025-01-29 15:49:22 +01:00
Added gif support using https://github.com/vbousquet/godot-gif-importer .
This commit is contained in:
parent
483842187f
commit
5cb1a3ce2f
@ -15,26 +15,26 @@ anchor_right = 0.0
|
|||||||
anchor_bottom = 0.0
|
anchor_bottom = 0.0
|
||||||
margin_left = 7.0
|
margin_left = 7.0
|
||||||
margin_top = 7.0
|
margin_top = 7.0
|
||||||
margin_right = 1017.0
|
margin_right = 593.0
|
||||||
margin_bottom = 593.0
|
margin_bottom = 593.0
|
||||||
|
|
||||||
[node name="VBoxContainer" parent="Menu" index="0"]
|
[node name="VBoxContainer" parent="Menu" index="0"]
|
||||||
margin_right = 1010.0
|
margin_right = 586.0
|
||||||
margin_bottom = 586.0
|
margin_bottom = 586.0
|
||||||
|
|
||||||
[node name="Sort" parent="Menu/VBoxContainer" index="0"]
|
[node name="Sort" parent="Menu/VBoxContainer" index="0"]
|
||||||
margin_top = 239.0
|
margin_top = 239.0
|
||||||
margin_right = 1010.0
|
margin_right = 586.0
|
||||||
margin_bottom = 259.0
|
margin_bottom = 259.0
|
||||||
|
|
||||||
[node name="Settings" parent="Menu/VBoxContainer" index="1"]
|
[node name="Settings" parent="Menu/VBoxContainer" index="1"]
|
||||||
margin_top = 283.0
|
margin_top = 283.0
|
||||||
margin_right = 1010.0
|
margin_right = 586.0
|
||||||
margin_bottom = 303.0
|
margin_bottom = 303.0
|
||||||
|
|
||||||
[node name="Exit" parent="Menu/VBoxContainer" index="2"]
|
[node name="Exit" parent="Menu/VBoxContainer" index="2"]
|
||||||
margin_top = 327.0
|
margin_top = 327.0
|
||||||
margin_right = 1010.0
|
margin_right = 586.0
|
||||||
margin_bottom = 347.0
|
margin_bottom = 347.0
|
||||||
|
|
||||||
[node name="Settings" parent="." instance=ExtResource( 3 )]
|
[node name="Settings" parent="." instance=ExtResource( 3 )]
|
||||||
@ -54,7 +54,7 @@ margin_bottom = 593.0
|
|||||||
[node name="Control" type="Control" parent="."]
|
[node name="Control" type="Control" parent="."]
|
||||||
margin_left = 7.0
|
margin_left = 7.0
|
||||||
margin_top = 7.0
|
margin_top = 7.0
|
||||||
margin_right = 1017.0
|
margin_right = 593.0
|
||||||
margin_bottom = 593.0
|
margin_bottom = 593.0
|
||||||
mouse_filter = 2
|
mouse_filter = 2
|
||||||
|
|
||||||
|
41
game/addons/gif-importer/GIF2AnimatedTexturePlugin.gd
Normal file
41
game/addons/gif-importer/GIF2AnimatedTexturePlugin.gd
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
# Derived from https://github.com/jegor377/godot-gdgifexporter
|
||||||
|
|
||||||
|
tool
|
||||||
|
extends EditorImportPlugin
|
||||||
|
|
||||||
|
func get_importer_name():
|
||||||
|
return "gif.animated.texture.plugin"
|
||||||
|
|
||||||
|
func get_visible_name():
|
||||||
|
return "Animated Texture"
|
||||||
|
|
||||||
|
func get_recognized_extensions():
|
||||||
|
return ["gif"]
|
||||||
|
|
||||||
|
func get_save_extension():
|
||||||
|
return "tres"
|
||||||
|
|
||||||
|
func get_resource_type():
|
||||||
|
return "AnimatedTexture"
|
||||||
|
|
||||||
|
func get_preset_count():
|
||||||
|
return 1
|
||||||
|
|
||||||
|
func get_preset_name(i):
|
||||||
|
return "Default"
|
||||||
|
|
||||||
|
func get_import_options(i):
|
||||||
|
return [
|
||||||
|
{"name": "Filter", "default_value": false},
|
||||||
|
{"name": "MipMaps", "default_value": false}
|
||||||
|
]
|
||||||
|
|
||||||
|
func import(source_file, save_path, options, platform_variants, gen_files):
|
||||||
|
var reader = GifReader.new()
|
||||||
|
reader.filter = options["Filter"]
|
||||||
|
reader.mipmaps = options["MipMaps"]
|
||||||
|
var tex = reader.read(source_file)
|
||||||
|
if tex == null:
|
||||||
|
return FAILED
|
||||||
|
var filename = save_path + "." + get_save_extension()
|
||||||
|
return ResourceSaver.save(filename, tex)
|
50
game/addons/gif-importer/GIF2SpriteFramesPlugin.gd
Normal file
50
game/addons/gif-importer/GIF2SpriteFramesPlugin.gd
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
# Derived from https://github.com/jegor377/godot-gdgifexporter
|
||||||
|
|
||||||
|
tool
|
||||||
|
extends EditorImportPlugin
|
||||||
|
|
||||||
|
func get_importer_name():
|
||||||
|
return "gif.animated.texture.plugin"
|
||||||
|
|
||||||
|
func get_visible_name():
|
||||||
|
return "Sprite Frames"
|
||||||
|
|
||||||
|
func get_recognized_extensions():
|
||||||
|
return ["gif"]
|
||||||
|
|
||||||
|
func get_save_extension():
|
||||||
|
return "tres"
|
||||||
|
|
||||||
|
func get_resource_type():
|
||||||
|
return "SpriteFrames"
|
||||||
|
|
||||||
|
func get_preset_count():
|
||||||
|
return 1
|
||||||
|
|
||||||
|
func get_preset_name(i):
|
||||||
|
return "Default"
|
||||||
|
|
||||||
|
func get_import_options(i):
|
||||||
|
return [
|
||||||
|
{"name": "Filter", "default_value": false},
|
||||||
|
{"name": "MipMaps", "default_value": false}
|
||||||
|
]
|
||||||
|
|
||||||
|
func import(source_file, save_path, options, platform_variants, gen_files):
|
||||||
|
var reader = GifReader.new()
|
||||||
|
reader.filter = options["Filter"]
|
||||||
|
reader.mipmaps = options["MipMaps"]
|
||||||
|
var tex = reader.read(source_file)
|
||||||
|
if tex == null:
|
||||||
|
return FAILED
|
||||||
|
var filename = save_path + "." + get_save_extension()
|
||||||
|
var sf = SpriteFrames.new()
|
||||||
|
var minLength = 1000
|
||||||
|
var maxLength = 0
|
||||||
|
for i in range(0, tex.frames):
|
||||||
|
sf.add_frame("default", tex.get_frame_texture(i))
|
||||||
|
var length = tex.get_frame_delay(i)
|
||||||
|
minLength = min(minLength, length)
|
||||||
|
maxLength = min(maxLength, length)
|
||||||
|
sf.set_animation_speed("default", 2.0 / (minLength + maxLength))
|
||||||
|
return ResourceSaver.save(filename, sf)
|
137
game/addons/gif-importer/GIFReader.gd
Normal file
137
game/addons/gif-importer/GIFReader.gd
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
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'
|
||||||
|
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):
|
||||||
|
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
|
||||||
|
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)
|
1
game/addons/gif-importer/HEAD
Normal file
1
game/addons/gif-importer/HEAD
Normal file
@ -0,0 +1 @@
|
|||||||
|
d8645b160b084fc4ea1f09c45ce09bcc15fa0bcb
|
21
game/addons/gif-importer/LICENSE
Normal file
21
game/addons/gif-importer/LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2021 Vincent Bousquet
|
||||||
|
|
||||||
|
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.
|
18
game/addons/gif-importer/README.md
Normal file
18
game/addons/gif-importer/README.md
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Godot GIF importer
|
||||||
|
Plugin for [Godot Engine](https://godotengine.org/) to import GIF image as AnimatedTexture
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Download or clone this repository and copy the contents of the `addons` folder to your own project's `addons` folder.
|
||||||
|
|
||||||
|
Then enable the plugin on the Project Settings. All your project's GIF assets should now appear in the Godot's asset browser as AnimatedTextures.
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
This little script uses [godot-gif-lzw](https://github.com/jegor377/godot-gif-lzw) to decompress images, and is entirely based on the content of this [website](http://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp).
|
||||||
|
|
||||||
|
The author of godot-gif-lzw also developped a [little godot script](https://github.com/jegor377/godot-gdgifexporter) to compress and export GIF images.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
[MIT License](LICENSE). Copyright (c) 2021 Vincent Bousquet.
|
21
game/addons/gif-importer/gif-lzw/LICENSE
Normal file
21
game/addons/gif-importer/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.
|
32
game/addons/gif-importer/gif-lzw/lsbbitpacker.gd
Normal file
32
game/addons/gif-importer/gif-lzw/lsbbitpacker.gd
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
tool
|
||||||
|
extends Reference
|
||||||
|
|
||||||
|
|
||||||
|
class LSBLZWBitPacker:
|
||||||
|
var bit_index: int = 0
|
||||||
|
var stream: int = 0
|
||||||
|
|
||||||
|
var chunks: PoolByteArray = PoolByteArray([])
|
||||||
|
|
||||||
|
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:
|
||||||
|
self.put_byte()
|
||||||
|
|
||||||
|
func pack() -> PoolByteArray:
|
||||||
|
if bit_index != 0:
|
||||||
|
self.put_byte()
|
||||||
|
return chunks
|
||||||
|
|
||||||
|
func reset() -> void:
|
||||||
|
bit_index = 0
|
||||||
|
stream = 0
|
||||||
|
chunks = PoolByteArray([])
|
42
game/addons/gif-importer/gif-lzw/lsbbitunpacker.gd
Normal file
42
game/addons/gif-importer/gif-lzw/lsbbitunpacker.gd
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
tool
|
||||||
|
extends Reference
|
||||||
|
|
||||||
|
|
||||||
|
class LSBLZWBitUnpacker:
|
||||||
|
var chunk_stream: PoolByteArray
|
||||||
|
var bit_index: int = 0
|
||||||
|
var byte: int
|
||||||
|
var byte_index: int = 0
|
||||||
|
|
||||||
|
func _init(_chunk_stream: PoolByteArray):
|
||||||
|
chunk_stream = _chunk_stream
|
||||||
|
self.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 self.get_bit(byte, bit_index) == 1:
|
||||||
|
result = self.set_bit(result, result_bit_index)
|
||||||
|
result_bit_index += 1
|
||||||
|
bit_index += 1
|
||||||
|
|
||||||
|
if bit_index == 8:
|
||||||
|
self.get_byte()
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
func remove_bits(bits_count: int) -> void:
|
||||||
|
self.read_bits(bits_count)
|
211
game/addons/gif-importer/gif-lzw/lzw.gd
Normal file
211
game/addons/gif-importer/gif-lzw/lzw.gd
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
tool
|
||||||
|
extends Reference
|
||||||
|
|
||||||
|
var lsbbitpacker = preload("./lsbbitpacker.gd")
|
||||||
|
var lsbbitunpacker = preload("./lsbbitunpacker.gd")
|
||||||
|
|
||||||
|
|
||||||
|
class CodeEntry:
|
||||||
|
var sequence: PoolByteArray
|
||||||
|
var raw_array: Array
|
||||||
|
|
||||||
|
func _init(_sequence):
|
||||||
|
raw_array = _sequence
|
||||||
|
sequence = _sequence
|
||||||
|
|
||||||
|
func add(other):
|
||||||
|
return CodeEntry.new(self.raw_array + other.raw_array)
|
||||||
|
|
||||||
|
func to_string():
|
||||||
|
var result: String = ""
|
||||||
|
for element in self.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) -> int:
|
||||||
|
self.entries[self.counter] = entry
|
||||||
|
self.lookup[entry.raw_array] = self.counter
|
||||||
|
counter += 1
|
||||||
|
return counter
|
||||||
|
|
||||||
|
func find(entry) -> int:
|
||||||
|
return self.lookup.get(entry.raw_array, -1)
|
||||||
|
|
||||||
|
func has(entry) -> bool:
|
||||||
|
return self.find(entry) != -1
|
||||||
|
|
||||||
|
func get(index) -> CodeEntry:
|
||||||
|
return self.entries.get(index, null)
|
||||||
|
|
||||||
|
func to_string() -> String:
|
||||||
|
var result: String = "CodeTable:\n"
|
||||||
|
for id in self.entries:
|
||||||
|
result += str(id) + ": " + self.entries[id].to_string() + "\n"
|
||||||
|
result += "Counter: " + str(self.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: PoolByteArray) -> 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: PoolByteArray, colors: PoolByteArray) -> 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: PoolByteArray = 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(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: PoolByteArray, min_code_size: int, colors: PoolByteArray) -> PoolByteArray:
|
||||||
|
var code_table: CodeTable = initialize_color_code_table(colors)
|
||||||
|
var index_stream: PoolByteArray = PoolByteArray([])
|
||||||
|
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.
|
||||||
|
|
||||||
|
# Remove first Clear Code from stream. We don't need it.
|
||||||
|
binary_code_stream.remove_bits(current_code_size)
|
||||||
|
|
||||||
|
# let CODE be the first code in the code stream
|
||||||
|
var code: int = binary_code_stream.read_bits(current_code_size)
|
||||||
|
# output {CODE} to index stream
|
||||||
|
index_stream.append_array(code_table.get(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)
|
||||||
|
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(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(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(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
|
7
game/addons/gif-importer/plugin.cfg
Normal file
7
game/addons/gif-importer/plugin.cfg
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[plugin]
|
||||||
|
|
||||||
|
name="GIF Importer"
|
||||||
|
description="Editor plugin ti import GIF as AnimatedTexture"
|
||||||
|
author="SoftMotionLabs"
|
||||||
|
version="1.0"
|
||||||
|
script="plugin.gd"
|
17
game/addons/gif-importer/plugin.gd
Normal file
17
game/addons/gif-importer/plugin.gd
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
tool
|
||||||
|
extends EditorPlugin
|
||||||
|
|
||||||
|
var import_sprite_frames_plugin
|
||||||
|
var import_animated_texture_plugin
|
||||||
|
|
||||||
|
func _enter_tree():
|
||||||
|
import_sprite_frames_plugin = preload("GIF2SpriteFramesPlugin.gd").new()
|
||||||
|
add_import_plugin(import_sprite_frames_plugin)
|
||||||
|
import_animated_texture_plugin = preload("GIF2AnimatedTexturePlugin.gd").new()
|
||||||
|
add_import_plugin(import_animated_texture_plugin)
|
||||||
|
|
||||||
|
func _exit_tree():
|
||||||
|
remove_import_plugin(import_sprite_frames_plugin)
|
||||||
|
import_sprite_frames_plugin = null
|
||||||
|
remove_import_plugin(import_animated_texture_plugin)
|
||||||
|
import_animated_texture_plugin = null
|
@ -8,6 +8,16 @@
|
|||||||
|
|
||||||
config_version=4
|
config_version=4
|
||||||
|
|
||||||
|
_global_script_classes=[ {
|
||||||
|
"base": "Reference",
|
||||||
|
"class": @"GifReader",
|
||||||
|
"language": @"GDScript",
|
||||||
|
"path": "res://addons/gif-importer/GIFReader.gd"
|
||||||
|
} ]
|
||||||
|
_global_script_class_icons={
|
||||||
|
@"GifReader": ""
|
||||||
|
}
|
||||||
|
|
||||||
[application]
|
[application]
|
||||||
|
|
||||||
config/name="MemR"
|
config/name="MemR"
|
||||||
@ -23,6 +33,10 @@ config/target_folder=""
|
|||||||
window/size/width=600
|
window/size/width=600
|
||||||
window/handheld/orientation="sensor"
|
window/handheld/orientation="sensor"
|
||||||
|
|
||||||
|
[editor_plugins]
|
||||||
|
|
||||||
|
enabled=PoolStringArray( "res://addons/gif-importer/plugin.cfg" )
|
||||||
|
|
||||||
[physics]
|
[physics]
|
||||||
|
|
||||||
common/enable_pause_aware_picking=true
|
common/enable_pause_aware_picking=true
|
||||||
|
15
game/sort/GifLoader.gd
Normal file
15
game/sort/GifLoader.gd
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
extends TextureRect
|
||||||
|
|
||||||
|
func load_gif(path : String) -> bool:
|
||||||
|
var reader = GifReader.new()
|
||||||
|
reader.filter = false
|
||||||
|
reader.mipmaps = false
|
||||||
|
|
||||||
|
var tex : AnimatedTexture = reader.read(path)
|
||||||
|
|
||||||
|
if tex == null:
|
||||||
|
return false
|
||||||
|
|
||||||
|
texture = tex
|
||||||
|
|
||||||
|
return true
|
8
game/sort/GifLoader.tscn
Normal file
8
game/sort/GifLoader.tscn
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
[gd_scene load_steps=2 format=2]
|
||||||
|
|
||||||
|
[ext_resource path="res://sort/GifLoader.gd" type="Script" id=1]
|
||||||
|
|
||||||
|
[node name="GifLoader" type="TextureRect"]
|
||||||
|
margin_right = 14.0
|
||||||
|
margin_bottom = 14.0
|
||||||
|
script = ExtResource( 1 )
|
@ -6,6 +6,7 @@ var target_folder : String
|
|||||||
var shell_script_name : String = "apply.sh"
|
var shell_script_name : String = "apply.sh"
|
||||||
|
|
||||||
var _texture_rect : TextureRect
|
var _texture_rect : TextureRect
|
||||||
|
var _gif_rect : TextureRect
|
||||||
var _error_label : Label
|
var _error_label : Label
|
||||||
var _categories_ob : OptionButton
|
var _categories_ob : OptionButton
|
||||||
var _sub_categories_ob : OptionButton
|
var _sub_categories_ob : OptionButton
|
||||||
@ -105,17 +106,26 @@ func next_image() -> void:
|
|||||||
next_folder()
|
next_folder()
|
||||||
return
|
return
|
||||||
|
|
||||||
var img : Image = Image.new()
|
|
||||||
if img.load(_current_folder_files[_current_file_index]) != OK:
|
|
||||||
_error_label.text = "Error loading file: " + _current_folder_files[_current_file_index]
|
|
||||||
_error_label.show()
|
|
||||||
_texture_rect.hide()
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
_error_label.hide()
|
_error_label.hide()
|
||||||
_texture_rect.show()
|
_texture_rect.hide()
|
||||||
_texture_rect.texture.create_from_image(img, 0)
|
_gif_rect.hide()
|
||||||
|
|
||||||
|
var curr_file : String = _current_folder_files[_current_file_index]
|
||||||
|
|
||||||
|
if curr_file.get_extension().to_lower() != "gif":
|
||||||
|
var img : Image = Image.new()
|
||||||
|
if img.load(curr_file) != OK:
|
||||||
|
_error_label.text = "Error loading file: " + curr_file
|
||||||
|
_error_label.show()
|
||||||
|
else:
|
||||||
|
_texture_rect.show()
|
||||||
|
_texture_rect.texture.create_from_image(img, 0)
|
||||||
|
else:
|
||||||
|
if !_gif_rect.load_gif(curr_file):
|
||||||
|
_error_label.text = "Error loading file: " + curr_file
|
||||||
|
_error_label.show()
|
||||||
|
else:
|
||||||
|
_gif_rect.show()
|
||||||
|
|
||||||
func evaluate_folders() -> void:
|
func evaluate_folders() -> void:
|
||||||
_folders.clear()
|
_folders.clear()
|
||||||
@ -284,6 +294,7 @@ func _on_NewSubCategoryPopup_confirmed() -> void:
|
|||||||
func _notification(what: int) -> void:
|
func _notification(what: int) -> void:
|
||||||
if what == NOTIFICATION_READY:
|
if what == NOTIFICATION_READY:
|
||||||
_texture_rect = get_node("ScrollContainer/VBoxContainer/TextureRect") as TextureRect
|
_texture_rect = get_node("ScrollContainer/VBoxContainer/TextureRect") as TextureRect
|
||||||
|
_gif_rect = get_node("ScrollContainer/VBoxContainer/GifRect") as TextureRect
|
||||||
_error_label = get_node("ScrollContainer/VBoxContainer/ErrorLabel") as Label
|
_error_label = get_node("ScrollContainer/VBoxContainer/ErrorLabel") as Label
|
||||||
_categories_ob = get_node("Categories/Categories") as OptionButton
|
_categories_ob = get_node("Categories/Categories") as OptionButton
|
||||||
_sub_categories_ob = get_node("SubCategoies/SubCategoies") as OptionButton
|
_sub_categories_ob = get_node("SubCategoies/SubCategoies") as OptionButton
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
[gd_scene load_steps=2 format=2]
|
[gd_scene load_steps=3 format=2]
|
||||||
|
|
||||||
[ext_resource path="res://sort/Sort.gd" type="Script" id=1]
|
[ext_resource path="res://sort/Sort.gd" type="Script" id=1]
|
||||||
|
[ext_resource path="res://sort/GifLoader.tscn" type="PackedScene" id=2]
|
||||||
|
|
||||||
[node name="Sort" type="VBoxContainer"]
|
[node name="Sort" type="VBoxContainer"]
|
||||||
margin_right = 1024.0
|
margin_right = 1024.0
|
||||||
@ -17,6 +18,12 @@ size_flags_vertical = 3
|
|||||||
|
|
||||||
[node name="TextureRect" type="TextureRect" parent="ScrollContainer/VBoxContainer"]
|
[node name="TextureRect" type="TextureRect" parent="ScrollContainer/VBoxContainer"]
|
||||||
|
|
||||||
|
[node name="GifRect" parent="ScrollContainer/VBoxContainer" instance=ExtResource( 2 )]
|
||||||
|
visible = false
|
||||||
|
margin_top = 4.0
|
||||||
|
margin_right = 0.0
|
||||||
|
margin_bottom = 4.0
|
||||||
|
|
||||||
[node name="ErrorLabel" type="Label" parent="ScrollContainer/VBoxContainer"]
|
[node name="ErrorLabel" type="Label" parent="ScrollContainer/VBoxContainer"]
|
||||||
visible = false
|
visible = false
|
||||||
margin_top = 4.0
|
margin_top = 4.0
|
||||||
|
Loading…
Reference in New Issue
Block a user