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 +++++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/quantization/median_cut.gd (limited to 'src/addons/GifMaker/godot-gdgifexporter/gdgifexporter/quantization/median_cut.gd') 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] -- cgit v1.2.3