From 227095a4f710ac6afd43f0a7e8b296f188cf20be Mon Sep 17 00:00:00 2001 From: David Luevano Alvarado Date: Tue, 31 May 2022 21:11:46 -0600 Subject: add working gif maker --- .../gdgifexporter/quantization/median_cut.gd | 163 +++++++++++++++++++++ .../gdgifexporter/quantization/uniform.gd | 88 +++++++++++ 2 files changed, 251 insertions(+) create mode 100644 src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/quantization/median_cut.gd create mode 100644 src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/quantization/uniform.gd (limited to 'src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/quantization') 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] -- cgit v1.2.3-54-g00ecf