summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDavid Luevano Alvarado <david@luevano.xyz>2022-05-31 21:11:46 -0600
committerDavid Luevano Alvarado <david@luevano.xyz>2022-05-31 21:11:46 -0600
commit227095a4f710ac6afd43f0a7e8b296f188cf20be (patch)
treee2f7e6a323ca4cad92b6a6012ed01a1afaf517fa /src
parentbd197168c39551c3456099ed2a39375a8442483f (diff)
add working gif maker
Diffstat (limited to 'src')
-rw-r--r--src/Main.tscn45
-rw-r--r--src/addons/GifMaker/GifDecoder.gd44
-rw-r--r--src/addons/GifMaker/GifRecorder.gd205
-rw-r--r--src/addons/GifMaker/GifRectangle.gd2
-rw-r--r--src/addons/GifMaker/GifRectangle.svg43
-rw-r--r--src/addons/GifMaker/GifRectangle.svg.import35
-rw-r--r--src/addons/GifMaker/LICENSE21
-rw-r--r--src/addons/GifMaker/gif-dark.svg64
-rw-r--r--src/addons/GifMaker/gif-dark.svg.import35
-rw-r--r--src/addons/GifMaker/gif.svg57
-rw-r--r--src/addons/GifMaker/gif.svg.import35
-rw-r--r--src/addons/GifMaker/godot-gdgifexporter/LICENSE21
-rw-r--r--src/addons/GifMaker/godot-gdgifexporter/README.md60
-rw-r--r--src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/LICENSE21
-rw-r--r--src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/converter.gd59
-rw-r--r--src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/exporter.gd292
-rw-r--r--src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/gif-lzw/LICENSE21
-rw-r--r--src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/gif-lzw/lsbbitpacker.gd31
-rw-r--r--src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/gif-lzw/lsbbitunpacker.gd41
-rw-r--r--src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/gif-lzw/lzw.gd210
-rw-r--r--src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/little_endian.gd5
-rw-r--r--src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/lookup_color.shader19
-rw-r--r--src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/lookup_similar.shader22
-rw-r--r--src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/quantization/median_cut.gd163
-rw-r--r--src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/quantization/uniform.gd88
-rw-r--r--src/addons/GifMaker/godot-gdgifexporter/imgs/chocolate.pngbin0 -> 415979 bytes
-rw-r--r--src/addons/GifMaker/godot-gdgifexporter/imgs/chocolate.png.import35
-rw-r--r--src/addons/GifMaker/godot-gdgifexporter/imgs/colors.pngbin0 -> 54053 bytes
-rw-r--r--src/addons/GifMaker/godot-gdgifexporter/imgs/colors.png.import35
-rw-r--r--src/addons/GifMaker/godot-gdgifexporter/imgs/colors2.pngbin0 -> 95327 bytes
-rw-r--r--src/addons/GifMaker/godot-gdgifexporter/imgs/colors2.png.import35
-rw-r--r--src/addons/GifMaker/godot-gdgifexporter/imgs/green_parrot.pngbin0 -> 1644217 bytes
-rw-r--r--src/addons/GifMaker/godot-gdgifexporter/imgs/green_parrot.png.import35
-rw-r--r--src/addons/GifMaker/godot-gdgifexporter/imgs/half_transparent.pngbin0 -> 372 bytes
-rw-r--r--src/addons/GifMaker/godot-gdgifexporter/imgs/half_transparent.png.import35
-rw-r--r--src/addons/GifMaker/godot-gdgifexporter/imgs/img1.pngbin0 -> 212 bytes
-rw-r--r--src/addons/GifMaker/godot-gdgifexporter/imgs/img1.png.import35
-rw-r--r--src/addons/GifMaker/godot-gdgifexporter/imgs/img2.pngbin0 -> 208 bytes
-rw-r--r--src/addons/GifMaker/godot-gdgifexporter/imgs/img2.png.import35
-rw-r--r--src/addons/GifMaker/godot-gdgifexporter/imgs/one_color.pngbin0 -> 199 bytes
-rw-r--r--src/addons/GifMaker/godot-gdgifexporter/imgs/one_color.png.import35
-rw-r--r--src/addons/GifMaker/godot-gdgifexporter/imgs/parrots.pngbin0 -> 395692 bytes
-rw-r--r--src/addons/GifMaker/godot-gdgifexporter/imgs/parrots.png.import35
-rw-r--r--src/addons/GifMaker/godot-gdgifexporter/imgs/transparent.pngbin0 -> 290 bytes
-rw-r--r--src/addons/GifMaker/godot-gdgifexporter/imgs/transparent.png.import35
-rw-r--r--src/addons/GifMaker/godot-gdgifexporter/imgs/two_colors.pngbin0 -> 282 bytes
-rw-r--r--src/addons/GifMaker/godot-gdgifexporter/imgs/two_colors.png.import35
-rw-r--r--src/addons/GifMaker/plugin.cfg7
-rw-r--r--src/addons/GifMaker/plugin.gd10
-rw-r--r--src/docs/LICENSE.GifMaker21
-rw-r--r--src/docs/LICENSE.gdgifexporter21
-rw-r--r--src/project.godot22
-rw-r--r--src/screen_recorder.gd13
-rw-r--r--src/test.gifbin0 -> 34428 bytes
54 files changed, 2117 insertions, 1 deletions
diff --git a/src/Main.tscn b/src/Main.tscn
index 3a3eb6b..c0f7499 100644
--- a/src/Main.tscn
+++ b/src/Main.tscn
@@ -1,11 +1,54 @@
-[gd_scene load_steps=2 format=2]
+[gd_scene load_steps=6 format=2]
[ext_resource path="res://entities/actors/snake/scenes/Snake.tscn" type="PackedScene" id=1]
+[ext_resource path="res://addons/GifMaker/GifRecorder.gd" type="Script" id=2]
+[ext_resource path="res://addons/GifMaker/GifRectangle.gd" type="Script" id=3]
+[ext_resource path="res://main.gd" type="Script" id=4]
+[ext_resource path="res://screen_recorder.gd" type="Script" id=5]
[node name="Main" type="Node2D"]
+script = ExtResource( 4 )
[node name="Snake" parent="." instance=ExtResource( 1 )]
[node name="Camera" type="Camera2D" parent="."]
current = true
zoom = Vector2( 0.5, 0.5 )
+
+[node name="ScreenRecorder" type="CanvasLayer" parent="."]
+script = ExtResource( 5 )
+
+[node name="Control" type="Control" parent="ScreenRecorder"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+
+[node name="GifRectangle" type="ReferenceRect" parent="ScreenRecorder/Control"]
+anchor_left = 0.5
+anchor_top = 0.5
+anchor_right = 0.5
+anchor_bottom = 0.5
+margin_left = -64.0
+margin_top = -64.0
+margin_right = 64.0
+margin_bottom = 64.0
+script = ExtResource( 3 )
+
+[node name="GifRecorder" type="Viewport" parent="ScreenRecorder/Control"]
+size = Vector2( 128, 128 )
+script = ExtResource( 2 )
+render_type = 1
+seconds = 2.0
+autostart = true
+capture_node_path = NodePath("../GifRectangle")
+
+[node name="Label" type="Label" parent="ScreenRecorder/Control"]
+anchor_left = 0.5
+anchor_top = 0.5
+anchor_right = 0.5
+anchor_bottom = 0.5
+margin_left = -20.0
+margin_top = -15.5
+margin_right = 20.0
+margin_bottom = 15.5
+text = "TEST
+"
diff --git a/src/addons/GifMaker/GifDecoder.gd b/src/addons/GifMaker/GifDecoder.gd
new file mode 100644
index 0000000..08ccaa9
--- /dev/null
+++ b/src/addons/GifMaker/GifDecoder.gd
@@ -0,0 +1,44 @@
+extends Node
+class_name GifDecoder
+
+func decode_file(path):
+ var file = File.new()
+ file.open(path, File.READ)
+ var bytes = file.get_buffer(file.get_len())
+ file.close()
+
+ return decode_data(bytes)
+
+func decode_block(block: PoolByteArray, start, end):
+ var string = block.subarray(start, end).get_string_from_utf8()
+ if string.begins_with('GIF_MAKER::'):
+ return str2var(string.substr('GIF_MAKER::'.length(), string.length()))
+ return string
+
+func decode_data(bytes: PoolByteArray):
+ var index = 0
+ var start = -1
+ var end = -1
+ var data = []
+
+ for byte in bytes:
+ if start != -1 \
+ and byte == 0x21:
+ end = index - 1
+
+ data.append(decode_block(bytes, start, end))
+ start = -1
+ end = -1
+
+ if byte == 0x21 \
+ and index < bytes.size() \
+ and bytes[index + 1] == 0xFE:
+ start = index + 2
+
+ index += 1
+
+ if end == -1 and start != -1:
+ end = bytes.size() - 1
+ data.append(decode_block(bytes, start, end))
+
+ return data
diff --git a/src/addons/GifMaker/GifRecorder.gd b/src/addons/GifMaker/GifRecorder.gd
new file mode 100644
index 0000000..44e30ed
--- /dev/null
+++ b/src/addons/GifMaker/GifRecorder.gd
@@ -0,0 +1,205 @@
+extends Viewport
+class_name GifRecorder, 'res://addons/GifMaker/gif.svg'
+
+signal record_past_buffer_filled
+signal encoding_progress(percentage, frames_done)
+signal done_encoding
+
+const GIFExporter = preload('res://addons/GifMaker/godot-gdgifexporter/gdgifexporter/exporter.gd')
+const MedianCut = preload('res://addons/GifMaker/godot-gdgifexporter/gdgifexporter/quantization/median_cut.gd')
+const Uniform = preload('res://addons/GifMaker/godot-gdgifexporter/gdgifexporter/quantization/uniform.gd')
+
+enum RenderType {
+ RENDER_3D,
+ RENDER_2D
+}
+
+enum RecordType {
+ RECORD_PAST,
+ RECORD
+}
+
+enum Framerate {
+ FPS_100 = 1,
+ FPS_50 = 2,
+ FPS_33 = 3,
+ FPS_25 = 4,
+ FPS_20 = 5,
+ FPS_10 = 10,
+ FPS_8 = 12,
+ FPS_4 = 24,
+ FPS_2 = 48,
+ FPS_1 = 100
+}
+
+enum Quantization {
+ MEDIAN_CUT,
+ UNIFORM
+}
+
+# TODO: Re-write get_properties_list to fancify input
+# TODO: Perhaps find a way to render the viewport in editor
+# TODO: Record a lil' video with an example Godot project
+
+export(int, 'Render 3D', 'Render 2D') var render_type = RenderType.RENDER_3D setget set_render_type
+export(RecordType) var record_type = RecordType.RECORD_PAST
+export var seconds = 6.28 setget set_seconds
+export(Framerate) var framerate = Framerate.FPS_25 setget set_framerate
+export(Quantization) var quantization = Quantization.UNIFORM
+export var autostart = false
+
+export(NodePath) var capture_node_path
+onready var capture_node = get_node(capture_node_path)
+
+export var preview = false
+export var preview_render = false
+export(NodePath) var preview_path
+onready var preview_node = get_node(preview_path)
+
+var frame_amount = 0
+var frame_passed = 0
+var frames = []
+var exporter
+var frame_timer
+var thread
+
+func _ready():
+ frame_timer = Timer.new()
+ add_child(frame_timer)
+
+ assert(capture_node, 'No capture node detected, please add a Camera3D or GifRectangle to get started')
+
+ self.framerate = framerate
+ self.seconds = seconds
+ self.render_type = render_type
+
+ frame_timer.connect('timeout', self, 'capture')
+
+ if autostart:
+ start()
+
+func start():
+ frame_timer.start()
+
+func stop():
+ frame_timer.stop()
+
+func clear():
+ frames = []
+
+func render(metadata = null):
+ stop()
+
+ exporter = GIFExporter.new(size.x, size.y)
+
+ if OS.can_use_threads():
+ thread = Thread.new()
+ thread.start(self, 'encode', metadata)
+ while thread.is_alive():
+ yield(get_tree(), 'idle_frame')
+
+ return thread.wait_to_finish()
+ else:
+ return encode(metadata)
+
+func encode(metadata = null):
+ var frames_done = 0
+ var quantization_method = \
+ Uniform if quantization == Quantization.UNIFORM else MedianCut
+
+ for frame in frames:
+ emit_signal('encoding_progress', float(frames_done) / frames.size(), frames_done)
+ frame.convert(Image.FORMAT_RGBA8)
+ exporter.add_frame(frame, framerate, quantization_method)
+ frames_done += 1
+
+ if preview and preview_render:
+ var texture = ImageTexture.new()
+ texture.create_from_image(frame)
+ preview_node.texture = texture
+
+ if metadata:
+ exporter.add_comment_ext('GIF_MAKER::' + var2str(metadata))
+
+ exporter.add_comment_ext('🙋 Made with GifMaker by Bram Dingelstad')
+
+ emit_signal('encoding_progress', 1.0, frames_done)
+ emit_signal('done_encoding')
+ return exporter.export_file_data()
+
+func render_to_file(file_path, metadata = null):
+ var buffer = render(metadata)
+ if buffer is GDScriptFunctionState:
+ buffer = yield(buffer, 'completed')
+
+ var file = File.new()
+ file.open(file_path, File.WRITE)
+ file.store_buffer(buffer)
+ file.close()
+
+func capture():
+ var frame = Image.new()
+ var screenshot
+ match render_type:
+ RenderType.RENDER_2D:
+ screenshot = get_tree().root.get_texture().get_data()
+ screenshot.flip_y()
+ RenderType.RENDER_3D:
+ screenshot = get_texture().get_data()
+ if not render_target_v_flip:
+ screenshot.flip_y()
+
+ frame.create(size.x, size.y, false, screenshot.get_format())
+ frame.blit_rect(
+ screenshot,
+ Rect2(capture_node.rect_position if render_type == RenderType.RENDER_2D else Vector2.ZERO, size),
+ Vector2.ZERO
+ )
+ frames.append(frame)
+
+ match record_type:
+ RecordType.RECORD_PAST:
+ while frames.size() > frame_amount:
+ frames.pop_front()
+ emit_signal('record_past_buffer_filled')
+
+ if preview:
+ var texture = ImageTexture.new()
+ texture.create_from_image(frame)
+ preview_node.texture = texture
+
+func _process(delta):
+ render_target_update_mode = UPDATE_DISABLED if render_type == RenderType.RENDER_2D else UPDATE_ALWAYS
+
+ match render_type:
+ RenderType.RENDER_2D:
+ size = capture_node.rect_size
+
+ RenderType.RENDER_3D:
+ $Camera.transform = capture_node.transform
+ $Camera.fov = $Camera.fov
+
+ # TODO: Sync more properties like culling mask, projection, etc
+
+func update_frame_amount():
+ frame_amount = 100 / framerate * seconds
+
+func set_framerate(_framerate):
+ framerate = _framerate
+ if is_inside_tree():
+ frame_timer.wait_time = 1.0 / (100.0 / framerate)
+ update_frame_amount()
+
+func set_seconds(_seconds):
+ seconds = _seconds
+ update_frame_amount()
+
+func set_render_type(_render_type):
+ render_type = _render_type
+
+ match render_type:
+ RenderType.RENDER_3D:
+ var shadow_capture_node = capture_node.duplicate()
+ shadow_capture_node.name = 'Camera'
+ shadow_capture_node.script = null
+ add_child(shadow_capture_node)
diff --git a/src/addons/GifMaker/GifRectangle.gd b/src/addons/GifMaker/GifRectangle.gd
new file mode 100644
index 0000000..a75beb1
--- /dev/null
+++ b/src/addons/GifMaker/GifRectangle.gd
@@ -0,0 +1,2 @@
+extends ReferenceRect
+class_name GifRectangle, 'res://addons/GifMaker/GifRectangle.svg'
diff --git a/src/addons/GifMaker/GifRectangle.svg b/src/addons/GifMaker/GifRectangle.svg
new file mode 100644
index 0000000..4f12487
--- /dev/null
+++ b/src/addons/GifMaker/GifRectangle.svg
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ height="16"
+ viewBox="0 0 16 16"
+ width="16"
+ version="1.1"
+ id="svg52"
+ sodipodi:docname="GifRectangle.svg"
+ inkscape:version="1.1.2 (b8e25be8, 2022-02-05)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <defs
+ id="defs56" />
+ <sodipodi:namedview
+ id="namedview54"
+ pagecolor="#505050"
+ bordercolor="#eeeeee"
+ borderopacity="1"
+ inkscape:pageshadow="0"
+ inkscape:pageopacity="0"
+ inkscape:pagecheckerboard="0"
+ showgrid="false"
+ inkscape:zoom="18.910507"
+ inkscape:cx="6.5307608"
+ inkscape:cy="5.6846703"
+ inkscape:window-width="1352"
+ inkscape:window-height="815"
+ inkscape:window-x="1080"
+ inkscape:window-y="719"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg52" />
+ <path
+ d="m1 1v2h2v-2zm3 0v2h8v-2zm9 0v2h2v-2zm-12 3v8h2v-8zm12 0v8h2v-8zm-12 9v2h2v-2zm3 0v2h8v-2zm9 0v2h2v-2z"
+ fill="#8eef97"
+ id="path50" />
+ <path
+ d="M 8.7273492,5.3330521 A 1.4546988,1.4546988 0 0 0 7.2783237,6.6798122 1.4546988,1.4546988 0 0 0 6.3028512,6.3028512 1.4546988,1.4546988 0 0 0 4.8481525,7.75755 1.4546988,1.4546988 0 0 0 5.8179516,9.1270518 v 1.0549962 c 0,0.268635 0.2162604,0.4849 0.4848996,0.4849 h 2.9093977 c 0.2686392,0 0.4848996,-0.216265 0.4848996,-0.4849 V 9.6971488 L 11.151848,10.666948 V 7.75755 L 9.6971485,8.7273488 V 7.8693194 A 1.4546988,1.4546988 0 0 0 10.182048,6.7877508 1.4546988,1.4546988 0 0 0 8.7273492,5.3330521 Z"
+ fill="#8da5f3"
+ id="path98"
+ style="stroke-width:0.4849;fill:#8eef97;fill-opacity:1" />
+</svg>
diff --git a/src/addons/GifMaker/GifRectangle.svg.import b/src/addons/GifMaker/GifRectangle.svg.import
new file mode 100644
index 0000000..3f59db6
--- /dev/null
+++ b/src/addons/GifMaker/GifRectangle.svg.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/GifRectangle.svg-39b586472495007cdcfa44c2055ad072.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/GifMaker/GifRectangle.svg"
+dest_files=[ "res://.import/GifRectangle.svg-39b586472495007cdcfa44c2055ad072.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/src/addons/GifMaker/LICENSE b/src/addons/GifMaker/LICENSE
new file mode 100644
index 0000000..3c879f4
--- /dev/null
+++ b/src/addons/GifMaker/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022 Bram Dingelstad, Igor Santarek (jegor377) & Martin Novák (novhack)
+
+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.
diff --git a/src/addons/GifMaker/gif-dark.svg b/src/addons/GifMaker/gif-dark.svg
new file mode 100644
index 0000000..a215897
--- /dev/null
+++ b/src/addons/GifMaker/gif-dark.svg
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ height="128"
+ viewBox="0 0 128 128"
+ width="128"
+ version="1.1"
+ id="svg4"
+ sodipodi:docname="gif-dark.svg"
+ inkscape:version="1.1.2 (b8e25be8, 2022-02-05)"
+ inkscape:export-filename="/Users/bram/Development/GifMaker/icon.png"
+ inkscape:export-xdpi="768"
+ inkscape:export-ydpi="768"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <defs
+ id="defs8" />
+ <sodipodi:namedview
+ id="namedview6"
+ pagecolor="#505050"
+ bordercolor="#eeeeee"
+ borderopacity="1"
+ inkscape:pageshadow="0"
+ inkscape:pageopacity="0"
+ inkscape:pagecheckerboard="0"
+ showgrid="false"
+ inkscape:zoom="3.4199088"
+ inkscape:cx="46.346265"
+ inkscape:cy="59.212106"
+ inkscape:window-width="1352"
+ inkscape:window-height="815"
+ inkscape:window-x="0"
+ inkscape:window-y="35"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="svg4"
+ width="128px"
+ scale-x="4" />
+ <path
+ d="M 18.956984,1 C 9.0396154,1 1,9.0396154 1,18.956984 V 72.827935 H 9.9784918 V 18.956984 c 0,-4.958684 4.0198082,-8.9784922 8.9784922,-8.9784922 H 72.827935 V 1 Z M 117.72282,54.870951 V 108.7419 c 0,4.95869 -4.01981,8.97849 -8.97849,8.97849 H 54.873375 v 8.9785 h 53.870955 c 9.91736,0 17.95698,-8.03962 17.95698,-17.95699 V 54.870951 Z"
+ fill="#e0e0e0"
+ fill-opacity="0.99608"
+ id="path2"
+ sodipodi:nodetypes="ssccssccscssccsscc"
+ style="fill:#1a1a1a;stroke-width:8.97849" />
+ <g
+ aria-label="gif"
+ id="text1234"
+ style="font-size:5.33333px;line-height:1.25;font-family:Whacky_Joe;-inkscape-font-specification:Whacky_Joe;fill:#1a1a1a;fill-opacity:0.996078"
+ transform="matrix(8.9784918,0,0,8.9784918,-7.9784918,-7.9784918)">
+ <path
+ d="M 4.1124218,5.4277697 H 6.3346426 V 5.8722138 H 6.7790868 V 8.9833231 H 6.3346426 V 9.8722115 H 5.8901985 V 10.316656 H 3.2235334 V 9.8722115 H 2.7790892 V 8.9833231 H 3.6679776 V 9.4277673 H 5.0013101 V 8.9833231 H 5.4457543 V 8.5388789 H 3.6679776 V 8.0944347 H 3.2235334 V 6.316658 H 3.6679776 V 5.8722138 H 4.1124218 Z M 4.5568659,6.316658 V 6.7611022 H 4.1124218 V 7.2055464 H 4.5568659 V 7.6499906 H 5.4457543 V 7.2055464 H 5.8901985 V 6.7611022 H 5.4457543 V 6.316658 Z"
+ id="path21334"
+ style="fill:#1a1a1a" />
+ <path
+ d="M 8.5603359,4.9833255 H 9.4492242 V 5.8722138 H 8.5603359 Z M 8.1158917,6.316658 H 9.0047801 V 8.5388789 H 8.5603359 V 9.8722115 H 8.1158917 V 10.316656 H 7.2270033 V 8.5388789 H 7.6714475 V 6.7611022 h 0.4444442 z"
+ id="path21336"
+ style="fill:#1a1a1a" />
+ <path
+ d="m 10.782557,5.4277697 h 2.222221 v 0.4444441 h 0.444444 V 7.2055464 H 12.560333 V 6.7611022 H 12.115889 V 6.316658 h -0.444444 v 0.4444442 h -0.444444 v 0.8888884 h 0.888888 V 8.5388789 H 11.227001 V 9.4277673 H 10.782557 V 10.316656 H 9.8936684 V 8.5388789 H 10.338113 V 8.0944347 H 9.8936684 V 7.2055464 H 10.338113 V 5.8722138 h 0.444444 z"
+ id="path21338"
+ style="fill:#1a1a1a" />
+ </g>
+</svg>
diff --git a/src/addons/GifMaker/gif-dark.svg.import b/src/addons/GifMaker/gif-dark.svg.import
new file mode 100644
index 0000000..4a9d167
--- /dev/null
+++ b/src/addons/GifMaker/gif-dark.svg.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/gif-dark.svg-d128ddcd15c8f58e57f1d9e5ff273637.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/GifMaker/gif-dark.svg"
+dest_files=[ "res://.import/gif-dark.svg-d128ddcd15c8f58e57f1d9e5ff273637.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/src/addons/GifMaker/gif.svg b/src/addons/GifMaker/gif.svg
new file mode 100644
index 0000000..dbaea1e
--- /dev/null
+++ b/src/addons/GifMaker/gif.svg
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ height="16"
+ viewBox="0 0 16 16"
+ width="16"
+ version="1.1"
+ id="svg4"
+ sodipodi:docname="gif.svg"
+ inkscape:version="1.1.2 (b8e25be8, 2022-02-05)"
+ inkscape:export-filename="/Users/bram/Development/GifMaker/icon.png"
+ inkscape:export-xdpi="768"
+ inkscape:export-ydpi="768"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <defs
+ id="defs8" />
+ <sodipodi:namedview
+ id="namedview6"
+ pagecolor="#505050"
+ bordercolor="#eeeeee"
+ borderopacity="1"
+ inkscape:pageshadow="0"
+ inkscape:pageopacity="0"
+ inkscape:pagecheckerboard="0"
+ showgrid="false"
+ inkscape:zoom="25.712265"
+ inkscape:cx="7.2922398"
+ inkscape:cy="7.8756189"
+ inkscape:window-width="1352"
+ inkscape:window-height="815"
+ inkscape:window-x="1080"
+ inkscape:window-y="719"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="text1234" />
+ <path
+ d="M 3,1 C 1.8954305,1 1,1.8954305 1,3 V 9 H 2 V 3 C 2,2.4477153 2.4477153,2 3,2 H 9 V 1 Z m 11.00027,6 v 6 c 0,0.552285 -0.447715,1 -1,1 h -6 v 1 h 6 c 1.104569,0 2,-0.895431 2,-2 V 7 Z"
+ fill="#e0e0e0"
+ fill-opacity="0.99608"
+ id="path2"
+ sodipodi:nodetypes="ssccssccscssccsscc" />
+ <g
+ aria-label="gif"
+ id="text1234"
+ style="font-size:5.33333px;line-height:1.25;font-family:Whacky_Joe;-inkscape-font-specification:Whacky_Joe;fill:#e0e0e0;fill-opacity:0.996078">
+ <path
+ d="M 4.1124218,5.4277697 H 6.3346426 V 5.8722138 H 6.7790868 V 8.9833231 H 6.3346426 V 9.8722115 H 5.8901985 V 10.316656 H 3.2235334 V 9.8722115 H 2.7790892 V 8.9833231 H 3.6679776 V 9.4277673 H 5.0013101 V 8.9833231 H 5.4457543 V 8.5388789 H 3.6679776 V 8.0944347 H 3.2235334 V 6.316658 H 3.6679776 V 5.8722138 H 4.1124218 Z M 4.5568659,6.316658 V 6.7611022 H 4.1124218 V 7.2055464 H 4.5568659 V 7.6499906 H 5.4457543 V 7.2055464 H 5.8901985 V 6.7611022 H 5.4457543 V 6.316658 Z"
+ id="path21334" />
+ <path
+ d="M 8.5603359,4.9833255 H 9.4492242 V 5.8722138 H 8.5603359 Z M 8.1158917,6.316658 H 9.0047801 V 8.5388789 H 8.5603359 V 9.8722115 H 8.1158917 V 10.316656 H 7.2270033 V 8.5388789 H 7.6714475 V 6.7611022 h 0.4444442 z"
+ id="path21336" />
+ <path
+ d="m 10.782557,5.4277697 h 2.222221 v 0.4444441 h 0.444444 V 7.2055464 H 12.560333 V 6.7611022 H 12.115889 V 6.316658 h -0.444444 v 0.4444442 h -0.444444 v 0.8888884 h 0.888888 V 8.5388789 H 11.227001 V 9.4277673 H 10.782557 V 10.316656 H 9.8936684 V 8.5388789 H 10.338113 V 8.0944347 H 9.8936684 V 7.2055464 H 10.338113 V 5.8722138 h 0.444444 z"
+ id="path21338" />
+ </g>
+</svg>
diff --git a/src/addons/GifMaker/gif.svg.import b/src/addons/GifMaker/gif.svg.import
new file mode 100644
index 0000000..80e57fe
--- /dev/null
+++ b/src/addons/GifMaker/gif.svg.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/gif.svg-9260ecf151c775bcfb933188ba2f4372.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/GifMaker/gif.svg"
+dest_files=[ "res://.import/gif.svg-9260ecf151c775bcfb933188ba2f4372.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/src/addons/GifMaker/godot-gdgifexporter/LICENSE b/src/addons/GifMaker/godot-gdgifexporter/LICENSE
new file mode 100644
index 0000000..ca5b2ad
--- /dev/null
+++ b/src/addons/GifMaker/godot-gdgifexporter/LICENSE
@@ -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.
diff --git a/src/addons/GifMaker/godot-gdgifexporter/README.md b/src/addons/GifMaker/godot-gdgifexporter/README.md
new file mode 100644
index 0000000..8da7779
--- /dev/null
+++ b/src/addons/GifMaker/godot-gdgifexporter/README.md
@@ -0,0 +1,60 @@
+# Gif exporter for Godot made entirely in GDScript
+This is gif exporter for godot made entirely using GDScript. This is based on [godot-gifexporter](https://github.com/novhack/godot-gifexporter).
+
+<p align="center">
+ <a href="https://github.com/godotengine/awesome-godot">
+ <img src="https://awesome.re/mentioned-badge.svg" alt="Mentioned in Awesome Godot" />
+ </a>
+</p>
+
+## Example
+```gdscript
+extends Node2D
+
+
+# load gif exporter module
+const GIFExporter = preload("res://gdgifexporter/exporter.gd")
+# load quantization module that you want to use
+const MedianCutQuantization = preload("res://gdgifexporter/quantization/median_cut.gd")
+
+
+func _ready():
+ var img := Image.new()
+ # load your image from png file
+ img.load('res://image.png')
+ # remember to use this image format when exporting
+ img.convert(Image.FORMAT_RGBA8)
+
+ # initialize exporter object with width and height of gif canvas
+ var exporter = GIFExporter.new(img1.get_width(), img1.get_height())
+ # write image using median cut quantization method and with one second animation delay
+ exporter.add_frame(img, 1, MedianCutQuantization)
+
+ # when you have exported all frames of animation you, then you can save data into file
+ var file: File = File.new()
+ # open new file with write privlige
+ file.open('user://result.gif', File.WRITE)
+ # save data stream into file
+ file.store_buffer(exporter.export_file_data())
+ # close the file
+ file.close()
+```
+
+## Quantization methods
+Addon supports two quantization methods:
+- Median Cut
+- Uniform (with small color adjustment)
+
+Both method files are stored in gdgifexporter/quantization directory.
+
+## Error Codes
+Some methods give error codes. These are used error codes and their meaning:
+- OK = 0 (Everything went okay)
+- EMPTY_IMAGE = 1 (Passed image object has no data in it)
+- BAD_IMAGE_FORMAT = 2 (You are using different image format than FORMAT_RGBA8)
+
+# Contributors
+If you want to contribute to this code then go ahead! :) Huge thanks to Kinwailo and novhack. This project wouldn't work without their help! :D
+
+# Used external libs
+- [godot-gif-lzw](https://github.com/jegor377/godot-gif-lzw)
diff --git a/src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/LICENSE b/src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/LICENSE
new file mode 100644
index 0000000..ca5b2ad
--- /dev/null
+++ b/src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/LICENSE
@@ -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.
diff --git a/src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/converter.gd b/src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/converter.gd
new file mode 100644
index 0000000..846d93c
--- /dev/null
+++ b/src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/converter.gd
@@ -0,0 +1,59 @@
+extends Reference
+
+var _shader: Shader
+
+
+func get_indexed_datas(image: Image, colors: Array) -> PoolByteArray:
+ _shader = preload("./lookup_color.shader")
+ return _convert(image, colors)
+
+
+func get_similar_indexed_datas(image: Image, colors: Array) -> PoolByteArray:
+ _shader = preload("./lookup_similar.shader")
+ return _convert(image, colors)
+
+
+func _convert(image: Image, colors: Array) -> PoolByteArray:
+ var vp = VisualServer.viewport_create()
+ var canvas = VisualServer.canvas_create()
+ VisualServer.viewport_attach_canvas(vp, canvas)
+ VisualServer.viewport_set_size(vp, image.get_width(), image.get_height())
+ VisualServer.viewport_set_disable_3d(vp, true)
+ VisualServer.viewport_set_usage(vp, VisualServer.VIEWPORT_USAGE_2D)
+ VisualServer.viewport_set_hdr(vp, true)
+ VisualServer.viewport_set_active(vp, true)
+
+ var ci_rid = VisualServer.canvas_item_create()
+ VisualServer.viewport_set_canvas_transform(vp, canvas, Transform())
+ VisualServer.canvas_item_set_parent(ci_rid, canvas)
+ var texture = ImageTexture.new()
+ texture.create_from_image(image)
+ VisualServer.canvas_item_add_texture_rect(
+ ci_rid, Rect2(Vector2(0, 0), image.get_size()), texture
+ )
+
+ var mat_rid = VisualServer.material_create()
+ VisualServer.material_set_shader(mat_rid, _shader.get_rid())
+ var lut = Image.new()
+ lut.create(256, 1, false, Image.FORMAT_RGB8)
+ lut.fill(Color8(colors[0][0], colors[0][1], colors[0][2]))
+ lut.lock()
+ for i in colors.size():
+ lut.set_pixel(i, 0, Color8(colors[i][0], colors[i][1], colors[i][2]))
+ var lut_tex = ImageTexture.new()
+ lut_tex.create_from_image(lut)
+ VisualServer.material_set_param(mat_rid, "lut", lut_tex)
+ VisualServer.canvas_item_set_material(ci_rid, mat_rid)
+
+ VisualServer.viewport_set_update_mode(vp, VisualServer.VIEWPORT_UPDATE_ONCE)
+ VisualServer.viewport_set_vflip(vp, true)
+ VisualServer.force_draw(false)
+ image = VisualServer.texture_get_data(VisualServer.viewport_get_texture(vp))
+
+ VisualServer.free_rid(vp)
+ VisualServer.free_rid(canvas)
+ VisualServer.free_rid(ci_rid)
+ VisualServer.free_rid(mat_rid)
+
+ image.convert(Image.FORMAT_R8)
+ return image.get_data()
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)
diff --git a/src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/gif-lzw/LICENSE b/src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/gif-lzw/LICENSE
new file mode 100644
index 0000000..ca5b2ad
--- /dev/null
+++ b/src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/gif-lzw/LICENSE
@@ -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.
diff --git a/src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/gif-lzw/lsbbitpacker.gd b/src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/gif-lzw/lsbbitpacker.gd
new file mode 100644
index 0000000..3141d09
--- /dev/null
+++ b/src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/gif-lzw/lsbbitpacker.gd
@@ -0,0 +1,31 @@
+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([])
diff --git a/src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/gif-lzw/lsbbitunpacker.gd b/src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/gif-lzw/lsbbitunpacker.gd
new file mode 100644
index 0000000..9f8507f
--- /dev/null
+++ b/src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/gif-lzw/lsbbitunpacker.gd
@@ -0,0 +1,41 @@
+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)
diff --git a/src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/gif-lzw/lzw.gd b/src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/gif-lzw/lzw.gd
new file mode 100644
index 0000000..9140e7c
--- /dev/null
+++ b/src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/gif-lzw/lzw.gd
@@ -0,0 +1,210 @@
+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
diff --git a/src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/little_endian.gd b/src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/little_endian.gd
new file mode 100644
index 0000000..cb865e1
--- /dev/null
+++ b/src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/little_endian.gd
@@ -0,0 +1,5 @@
+extends Reference
+
+
+func int_to_2bytes(value: int) -> PoolByteArray:
+ return PoolByteArray([value & 255, (value >> 8) & 255])
diff --git a/src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/lookup_color.shader b/src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/lookup_color.shader
new file mode 100644
index 0000000..8379108
--- /dev/null
+++ b/src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/lookup_color.shader
@@ -0,0 +1,19 @@
+shader_type canvas_item;
+render_mode unshaded;
+
+uniform sampler2D lut;
+
+void fragment() {
+ vec4 color = texture(TEXTURE, UV);
+ float index = 0.0;
+ if (color.a > 0.0) {
+ for (int i = 0; i < 256; i++) {
+ vec4 c = texture(lut, vec2((float(i) + 0.5) / 256.0, 0.5));
+ if (c.rgb == color.rgb) {
+ index = float(i) / 255.0;
+ break;
+ }
+ }
+ }
+ COLOR = vec4(vec3(index), 1.0);
+} \ No newline at end of file
diff --git a/src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/lookup_similar.shader b/src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/lookup_similar.shader
new file mode 100644
index 0000000..0614661
--- /dev/null
+++ b/src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/lookup_similar.shader
@@ -0,0 +1,22 @@
+shader_type canvas_item;
+render_mode unshaded;
+
+uniform sampler2D lut;
+
+void fragment() {
+ vec4 color = texture(TEXTURE, UV);
+ vec4 similar = texture(lut, vec2(0.5 / 256.0, 0.5));
+ float index = 0.0;
+ if (color.a > 0.0) {
+ float dist = distance(color.xyz, similar.xyz);
+ for (int i = 1; i < 256; i++) {
+ vec4 c = texture(lut, vec2((float(i) + 0.5) / 256.0, 0.5));
+ float d = distance(color.xyz, c.xyz);
+ if (d < dist) {
+ dist = d;
+ index = float(i) / 255.0;
+ }
+ }
+ }
+ COLOR = vec4(vec3(index), 1.0);
+} \ No newline at end of file
diff --git a/src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/quantization/median_cut.gd b/src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/quantization/median_cut.gd
new file mode 100644
index 0000000..4610f03
--- /dev/null
+++ b/src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/quantization/median_cut.gd
@@ -0,0 +1,163 @@
+extends Reference
+
+var converter = preload("../converter.gd").new()
+var transparency := false
+
+
+func longest_axis(colors: Array) -> int:
+ var start := [255, 255, 255]
+ var end := [0, 0, 0]
+ for color in colors:
+ for i in 3:
+ start[i] = min(color[i], start[i])
+ end[i] = max(color[i], end[i])
+
+ var max_r = end[0] - start[0]
+ var max_g = end[1] - start[1]
+ var max_b = end[2] - start[2]
+
+ if max_r > max_g:
+ if max_r > max_b:
+ return 0
+ else:
+ if max_g > max_b:
+ return 1
+ return 2
+
+
+func get_median(colors: Array) -> Vector3:
+ return colors[colors.size() >> 1]
+
+
+func median_cut(colors: Array) -> Array:
+ var axis := longest_axis(colors)
+
+ var axis_sort := []
+ for color in colors:
+ axis_sort.append(color[axis])
+ axis_sort.sort()
+
+ var cut := axis_sort.size() >> 1
+ var median: int = axis_sort[cut]
+ axis_sort = []
+
+ var left_colors := []
+ var right_colors := []
+
+ for color in colors:
+ if color[axis] < median:
+ left_colors.append(color)
+ else:
+ right_colors.append(color)
+
+ return [left_colors, right_colors]
+
+
+func average_color(bucket: Array) -> Array:
+ var r := 0
+ var g := 0
+ var b := 0
+ for color in bucket:
+ r += color[0]
+ g += color[1]
+ b += color[2]
+ return [r / bucket.size(), g / bucket.size(), b / bucket.size()]
+
+
+func average_colors(buckets: Array) -> Dictionary:
+ var avg_colors := {}
+ for bucket in buckets:
+ if bucket.size() > 0:
+ avg_colors[average_color(bucket)] = avg_colors.size()
+ return avg_colors
+
+
+func pixels_to_colors(image: Image) -> Array:
+ image.lock()
+ var result := []
+ var data: PoolByteArray = image.get_data()
+
+ for i in range(0, data.size(), 4):
+ if data[i + 3] == 0:
+ transparency = true
+ continue
+ result.append([data[i], data[i + 1], data[i + 2]])
+ image.unlock()
+ return result
+
+
+func remove_smallest_bucket(buckets: Array) -> Array:
+ if buckets.size() == 0:
+ return buckets
+ var i_of_smallest_bucket := 0
+ for i in range(buckets.size()):
+ if buckets[i].size() < buckets[i_of_smallest_bucket].size():
+ i_of_smallest_bucket = i
+ buckets.remove(i_of_smallest_bucket)
+ return buckets
+
+
+func remove_empty_buckets(buckets: Array) -> Array:
+ if buckets.size() == 0:
+ return buckets
+
+ var i := buckets.find([])
+ while i != -1:
+ buckets.remove(i)
+ i = buckets.find([])
+
+ return buckets
+
+
+# quantizes to gif ready codes
+func quantize(image: Image) -> Array:
+ var pixels = pixels_to_colors(image)
+ if pixels.size() == 0:
+ return pixels
+
+ var buckets := [pixels]
+ var done_buckets := []
+
+ # it tells how many times buckets should be divided into two
+ var dimensions := 8
+
+ for i in range(0, dimensions):
+ var new_buckets := []
+ for bucket in buckets:
+ # don't median cut if bucket is smaller than 2, because
+ # it won't produce two new buckets.
+ if bucket.size() > 1:
+ var res := median_cut(bucket)
+ # sometimes when you try to median cut a bucket, the result
+ # is one with size equal to 0 and other with full size as the
+ # source bucket. Because of that it's useless to try to divide
+ # it further so it's better to put it into separate list and
+ # process only those buckets witch divide further.
+ if res[0].size() == 0 or res[1].size() == 0:
+ done_buckets += res
+ else:
+ new_buckets += res
+ buckets = []
+ buckets = new_buckets
+
+ var all_buckets := remove_empty_buckets(done_buckets + buckets)
+
+ buckets = []
+ done_buckets = []
+
+ if transparency:
+ if all_buckets.size() == pow(2, dimensions):
+ all_buckets = remove_smallest_bucket(all_buckets)
+
+ # dictionaries are only for speed.
+ var color_array := average_colors(all_buckets).keys()
+
+ # if pixel_to_colors detected that the image has transparent pixels
+ # then add transparency color at the beginning so it will be properly
+ # exported.
+ if transparency:
+ color_array = [[0, 0, 0]] + color_array
+
+ var data: PoolByteArray = converter.get_similar_indexed_datas(image, color_array)
+
+ return [data, color_array, transparency]
diff --git a/src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/quantization/uniform.gd b/src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/quantization/uniform.gd
new file mode 100644
index 0000000..e953342
--- /dev/null
+++ b/src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/quantization/uniform.gd
@@ -0,0 +1,88 @@
+extends Reference
+
+var converter = preload("../converter.gd").new()
+var transparency := false
+
+
+func how_many_divisions(colors_count: int) -> int:
+ return int(ceil(pow(colors_count, 1.0 / 4.0)))
+
+
+func generate_colors(colors_count: int) -> Array:
+ var divisions_count: int = how_many_divisions(colors_count)
+ var colors: Array = []
+
+ for a in range(divisions_count):
+ for b in range(divisions_count):
+ for g in range(divisions_count):
+ for r in range(divisions_count):
+ colors.append(
+ [
+ Vector3(
+ (255.0 / divisions_count) * r,
+ (255.0 / divisions_count) * g,
+ (255.0 / divisions_count) * b
+ ),
+ (255.0 / divisions_count) * a
+ ]
+ )
+
+ return colors
+
+
+func find_nearest_color(palette_color: Vector3, image_data: PoolByteArray) -> Array:
+ var nearest_color = null
+ var nearest_alpha = null
+ for i in range(0, image_data.size(), 4):
+ var color = Vector3(image_data[i], image_data[i + 1], image_data[i + 2])
+ # detect transparency
+ if image_data[3] == 0:
+ transparency = true
+ if (
+ (nearest_color == null)
+ or (
+ palette_color.distance_squared_to(color)
+ < palette_color.distance_squared_to(nearest_color)
+ )
+ ):
+ nearest_color = color
+ nearest_alpha = image_data[i + 3]
+ return [nearest_color, nearest_alpha]
+
+
+# moves every color from palette colors to the nearest found color in image
+func enhance_colors(image: Image, palette_colors: Array) -> Array:
+ var data := image.get_data()
+
+ for i in range(palette_colors.size()):
+ var nearest_color := find_nearest_color(palette_colors[i][0], data)
+ palette_colors[i] = nearest_color
+
+ return palette_colors
+
+
+func to_color_array(colors: Array) -> Array:
+ var result := []
+ for v in colors:
+ result.append([v[0].x, v[0].y, v[0].z])
+ return result
+
+
+# quantizes to gif ready codes
+func quantize(image: Image) -> Array:
+ image.lock()
+
+ var colors: Array = generate_colors(256)
+ var tmp_image: Image = Image.new()
+ tmp_image.copy_from(image)
+ tmp_image.resize(32, 32)
+ tmp_image.lock()
+ colors = enhance_colors(tmp_image, colors)
+ tmp_image.unlock()
+
+ image.unlock()
+ colors = to_color_array(colors)
+
+ var data: PoolByteArray = converter.get_similar_indexed_datas(image, colors)
+
+ return [data, colors, transparency]
diff --git a/src/addons/GifMaker/godot-gdgifexporter/imgs/chocolate.png b/src/addons/GifMaker/godot-gdgifexporter/imgs/chocolate.png
new file mode 100644
index 0000000..20ddb3c
--- /dev/null
+++ b/src/addons/GifMaker/godot-gdgifexporter/imgs/chocolate.png
Binary files differ
diff --git a/src/addons/GifMaker/godot-gdgifexporter/imgs/chocolate.png.import b/src/addons/GifMaker/godot-gdgifexporter/imgs/chocolate.png.import
new file mode 100644
index 0000000..e9e1e9a
--- /dev/null
+++ b/src/addons/GifMaker/godot-gdgifexporter/imgs/chocolate.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/chocolate.png-34e2ba81579eb1fa5f3e43edbb7d77a7.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/GifMaker/godot-gdgifexporter/imgs/chocolate.png"
+dest_files=[ "res://.import/chocolate.png-34e2ba81579eb1fa5f3e43edbb7d77a7.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/src/addons/GifMaker/godot-gdgifexporter/imgs/colors.png b/src/addons/GifMaker/godot-gdgifexporter/imgs/colors.png
new file mode 100644
index 0000000..ef5a146
--- /dev/null
+++ b/src/addons/GifMaker/godot-gdgifexporter/imgs/colors.png
Binary files differ
diff --git a/src/addons/GifMaker/godot-gdgifexporter/imgs/colors.png.import b/src/addons/GifMaker/godot-gdgifexporter/imgs/colors.png.import
new file mode 100644
index 0000000..3915844
--- /dev/null
+++ b/src/addons/GifMaker/godot-gdgifexporter/imgs/colors.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/colors.png-ce283e5c03d836da781607a180e7aa71.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/GifMaker/godot-gdgifexporter/imgs/colors.png"
+dest_files=[ "res://.import/colors.png-ce283e5c03d836da781607a180e7aa71.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/src/addons/GifMaker/godot-gdgifexporter/imgs/colors2.png b/src/addons/GifMaker/godot-gdgifexporter/imgs/colors2.png
new file mode 100644
index 0000000..746e0ec
--- /dev/null
+++ b/src/addons/GifMaker/godot-gdgifexporter/imgs/colors2.png
Binary files differ
diff --git a/src/addons/GifMaker/godot-gdgifexporter/imgs/colors2.png.import b/src/addons/GifMaker/godot-gdgifexporter/imgs/colors2.png.import
new file mode 100644
index 0000000..8d104ab
--- /dev/null
+++ b/src/addons/GifMaker/godot-gdgifexporter/imgs/colors2.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/colors2.png-ce18d0c4c3718b88a1b7f3114adf8e78.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/GifMaker/godot-gdgifexporter/imgs/colors2.png"
+dest_files=[ "res://.import/colors2.png-ce18d0c4c3718b88a1b7f3114adf8e78.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/src/addons/GifMaker/godot-gdgifexporter/imgs/green_parrot.png b/src/addons/GifMaker/godot-gdgifexporter/imgs/green_parrot.png
new file mode 100644
index 0000000..15003a8
--- /dev/null
+++ b/src/addons/GifMaker/godot-gdgifexporter/imgs/green_parrot.png
Binary files differ
diff --git a/src/addons/GifMaker/godot-gdgifexporter/imgs/green_parrot.png.import b/src/addons/GifMaker/godot-gdgifexporter/imgs/green_parrot.png.import
new file mode 100644
index 0000000..13010bd
--- /dev/null
+++ b/src/addons/GifMaker/godot-gdgifexporter/imgs/green_parrot.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/green_parrot.png-d2a592da4624540d1214565b240e40bb.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/GifMaker/godot-gdgifexporter/imgs/green_parrot.png"
+dest_files=[ "res://.import/green_parrot.png-d2a592da4624540d1214565b240e40bb.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/src/addons/GifMaker/godot-gdgifexporter/imgs/half_transparent.png b/src/addons/GifMaker/godot-gdgifexporter/imgs/half_transparent.png
new file mode 100644
index 0000000..ce9bed6
--- /dev/null
+++ b/src/addons/GifMaker/godot-gdgifexporter/imgs/half_transparent.png
Binary files differ
diff --git a/src/addons/GifMaker/godot-gdgifexporter/imgs/half_transparent.png.import b/src/addons/GifMaker/godot-gdgifexporter/imgs/half_transparent.png.import
new file mode 100644
index 0000000..cf372a0
--- /dev/null
+++ b/src/addons/GifMaker/godot-gdgifexporter/imgs/half_transparent.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/half_transparent.png-e930e02c59422de3a13ef3a308fd011c.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/GifMaker/godot-gdgifexporter/imgs/half_transparent.png"
+dest_files=[ "res://.import/half_transparent.png-e930e02c59422de3a13ef3a308fd011c.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/src/addons/GifMaker/godot-gdgifexporter/imgs/img1.png b/src/addons/GifMaker/godot-gdgifexporter/imgs/img1.png
new file mode 100644
index 0000000..6cd5643
--- /dev/null
+++ b/src/addons/GifMaker/godot-gdgifexporter/imgs/img1.png
Binary files differ
diff --git a/src/addons/GifMaker/godot-gdgifexporter/imgs/img1.png.import b/src/addons/GifMaker/godot-gdgifexporter/imgs/img1.png.import
new file mode 100644
index 0000000..6807fe2
--- /dev/null
+++ b/src/addons/GifMaker/godot-gdgifexporter/imgs/img1.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/img1.png-2f74ec6ad8e8c230c1b57cd8aa285202.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/GifMaker/godot-gdgifexporter/imgs/img1.png"
+dest_files=[ "res://.import/img1.png-2f74ec6ad8e8c230c1b57cd8aa285202.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/src/addons/GifMaker/godot-gdgifexporter/imgs/img2.png b/src/addons/GifMaker/godot-gdgifexporter/imgs/img2.png
new file mode 100644
index 0000000..b530e31
--- /dev/null
+++ b/src/addons/GifMaker/godot-gdgifexporter/imgs/img2.png
Binary files differ
diff --git a/src/addons/GifMaker/godot-gdgifexporter/imgs/img2.png.import b/src/addons/GifMaker/godot-gdgifexporter/imgs/img2.png.import
new file mode 100644
index 0000000..01c15e5
--- /dev/null
+++ b/src/addons/GifMaker/godot-gdgifexporter/imgs/img2.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/img2.png-ea1dbb037cc3a919f6ebf042098e80e1.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/GifMaker/godot-gdgifexporter/imgs/img2.png"
+dest_files=[ "res://.import/img2.png-ea1dbb037cc3a919f6ebf042098e80e1.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/src/addons/GifMaker/godot-gdgifexporter/imgs/one_color.png b/src/addons/GifMaker/godot-gdgifexporter/imgs/one_color.png
new file mode 100644
index 0000000..4672a49
--- /dev/null
+++ b/src/addons/GifMaker/godot-gdgifexporter/imgs/one_color.png
Binary files differ
diff --git a/src/addons/GifMaker/godot-gdgifexporter/imgs/one_color.png.import b/src/addons/GifMaker/godot-gdgifexporter/imgs/one_color.png.import
new file mode 100644
index 0000000..d3d76ab
--- /dev/null
+++ b/src/addons/GifMaker/godot-gdgifexporter/imgs/one_color.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/one_color.png-7864815b42a9af97ff7a9d747779a813.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/GifMaker/godot-gdgifexporter/imgs/one_color.png"
+dest_files=[ "res://.import/one_color.png-7864815b42a9af97ff7a9d747779a813.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/src/addons/GifMaker/godot-gdgifexporter/imgs/parrots.png b/src/addons/GifMaker/godot-gdgifexporter/imgs/parrots.png
new file mode 100644
index 0000000..53cb031
--- /dev/null
+++ b/src/addons/GifMaker/godot-gdgifexporter/imgs/parrots.png
Binary files differ
diff --git a/src/addons/GifMaker/godot-gdgifexporter/imgs/parrots.png.import b/src/addons/GifMaker/godot-gdgifexporter/imgs/parrots.png.import
new file mode 100644
index 0000000..3d32d1e
--- /dev/null
+++ b/src/addons/GifMaker/godot-gdgifexporter/imgs/parrots.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/parrots.png-cc20b2e4597b52743a9b12b4e00aaf3c.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/GifMaker/godot-gdgifexporter/imgs/parrots.png"
+dest_files=[ "res://.import/parrots.png-cc20b2e4597b52743a9b12b4e00aaf3c.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/src/addons/GifMaker/godot-gdgifexporter/imgs/transparent.png b/src/addons/GifMaker/godot-gdgifexporter/imgs/transparent.png
new file mode 100644
index 0000000..ef18b14
--- /dev/null
+++ b/src/addons/GifMaker/godot-gdgifexporter/imgs/transparent.png
Binary files differ
diff --git a/src/addons/GifMaker/godot-gdgifexporter/imgs/transparent.png.import b/src/addons/GifMaker/godot-gdgifexporter/imgs/transparent.png.import
new file mode 100644
index 0000000..fe0155a
--- /dev/null
+++ b/src/addons/GifMaker/godot-gdgifexporter/imgs/transparent.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/transparent.png-7e6b3a2c68ee40026b550dcef0ac0554.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/GifMaker/godot-gdgifexporter/imgs/transparent.png"
+dest_files=[ "res://.import/transparent.png-7e6b3a2c68ee40026b550dcef0ac0554.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/src/addons/GifMaker/godot-gdgifexporter/imgs/two_colors.png b/src/addons/GifMaker/godot-gdgifexporter/imgs/two_colors.png
new file mode 100644
index 0000000..ca27ea8
--- /dev/null
+++ b/src/addons/GifMaker/godot-gdgifexporter/imgs/two_colors.png
Binary files differ
diff --git a/src/addons/GifMaker/godot-gdgifexporter/imgs/two_colors.png.import b/src/addons/GifMaker/godot-gdgifexporter/imgs/two_colors.png.import
new file mode 100644
index 0000000..38eacc4
--- /dev/null
+++ b/src/addons/GifMaker/godot-gdgifexporter/imgs/two_colors.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/two_colors.png-2a9ea00d42cbfde181d536f2d6095ad8.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/GifMaker/godot-gdgifexporter/imgs/two_colors.png"
+dest_files=[ "res://.import/two_colors.png-2a9ea00d42cbfde181d536f2d6095ad8.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/src/addons/GifMaker/plugin.cfg b/src/addons/GifMaker/plugin.cfg
new file mode 100644
index 0000000..f540664
--- /dev/null
+++ b/src/addons/GifMaker/plugin.cfg
@@ -0,0 +1,7 @@
+[plugin]
+
+name="GifMaker"
+description="A simple node that can record gameplay and turn it into an animated GIF!"
+author="Bram Dingelstad"
+version="1.0"
+script="plugin.gd"
diff --git a/src/addons/GifMaker/plugin.gd b/src/addons/GifMaker/plugin.gd
new file mode 100644
index 0000000..49e712e
--- /dev/null
+++ b/src/addons/GifMaker/plugin.gd
@@ -0,0 +1,10 @@
+tool
+extends EditorPlugin
+
+
+func _enter_tree():
+ pass
+
+
+func _exit_tree():
+ pass
diff --git a/src/docs/LICENSE.GifMaker b/src/docs/LICENSE.GifMaker
new file mode 100644
index 0000000..3c879f4
--- /dev/null
+++ b/src/docs/LICENSE.GifMaker
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022 Bram Dingelstad, Igor Santarek (jegor377) & Martin Novák (novhack)
+
+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.
diff --git a/src/docs/LICENSE.gdgifexporter b/src/docs/LICENSE.gdgifexporter
new file mode 100644
index 0000000..ca5b2ad
--- /dev/null
+++ b/src/docs/LICENSE.gdgifexporter
@@ -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.
diff --git a/src/project.godot b/src/project.godot
index c23bed6..4146dfd 100644
--- a/src/project.godot
+++ b/src/project.godot
@@ -10,6 +10,21 @@ config_version=4
_global_script_classes=[ {
"base": "Node",
+"class": "GifDecoder",
+"language": "GDScript",
+"path": "res://addons/GifMaker/GifDecoder.gd"
+}, {
+"base": "Viewport",
+"class": "GifRecorder",
+"language": "GDScript",
+"path": "res://addons/GifMaker/GifRecorder.gd"
+}, {
+"base": "ReferenceRect",
+"class": "GifRectangle",
+"language": "GDScript",
+"path": "res://addons/GifMaker/GifRectangle.gd"
+}, {
+"base": "Node",
"class": "Main",
"language": "GDScript",
"path": "res://main.gd"
@@ -20,6 +35,9 @@ _global_script_classes=[ {
"path": "res://entities/actors/snake/scripts/snake.gd"
} ]
_global_script_class_icons={
+"GifDecoder": "",
+"GifRecorder": "res://addons/GifMaker/gif.svg",
+"GifRectangle": "res://addons/GifMaker/GifRectangle.svg",
"Main": "",
"Snake": ""
}
@@ -41,6 +59,10 @@ gdscript/warnings/unused_argument=false
gdscript/warnings/unused_signal=false
gdscript/warnings/return_value_discarded=false
+[editor_plugins]
+
+enabled=PoolStringArray( "res://addons/GifMaker/plugin.cfg" )
+
[gui]
common/drop_mouse_on_gui_input_disabled=true
diff --git a/src/screen_recorder.gd b/src/screen_recorder.gd
new file mode 100644
index 0000000..2ab77e5
--- /dev/null
+++ b/src/screen_recorder.gd
@@ -0,0 +1,13 @@
+extends CanvasLayer
+
+onready var gif_recorder: GifRecorder = $Control/GifRecorder
+
+
+func _input(event: InputEvent) -> void:
+ if event is InputEventKey and event.scancode == KEY_G and event.pressed:
+ print("Encoding GIF.")
+ gif_recorder.render_to_file("res://test.gif")
+ yield(gif_recorder, "done_encoding")
+ print("Done encoding GIF.")
+ gif_recorder.clear()
+ gif_recorder.start()
diff --git a/src/test.gif b/src/test.gif
new file mode 100644
index 0000000..e9219c1
--- /dev/null
+++ b/src/test.gif
Binary files differ