diff --git a/project.godot b/project.godot index 0800b9b7b..90c82a9d5 100644 --- a/project.godot +++ b/project.godot @@ -60,6 +60,7 @@ config/Version="v0.7" Global="*res://src/Autoload/Global.gd" Import="*res://src/Autoload/Import.gd" OpenSave="*res://src/Autoload/OpenSave.gd" +DrawingAlgos="*res://src/Autoload/DrawingAlgos.gd" [debug] diff --git a/src/Autoload/DrawingAlgos.gd b/src/Autoload/DrawingAlgos.gd new file mode 100644 index 000000000..2e2cba406 --- /dev/null +++ b/src/Autoload/DrawingAlgos.gd @@ -0,0 +1,439 @@ +extends Node + + +var mouse_press_pixels := [] # Cleared after mouse release +var mouse_press_pressure_values := [] # Cleared after mouse release + + +# Algorithm based on http://members.chello.at/easyfilter/bresenham.html +func plot_circle(sprite : Image, xm : int, ym : int, r : int, color : Color, fill := false) -> void: + var radius := r # Used later for filling + var x := -r + var y := 0 + var err := 2 - r * 2 # II. Quadrant + while x < 0: + var quadrant_1 := Vector2(xm - x, ym + y) + var quadrant_2 := Vector2(xm - y, ym - x) + var quadrant_3 := Vector2(xm + x, ym - y) + var quadrant_4 := Vector2(xm + y, ym + x) + draw_pixel_blended(sprite, quadrant_1, color) + draw_pixel_blended(sprite, quadrant_2, color) + draw_pixel_blended(sprite, quadrant_3, color) + draw_pixel_blended(sprite, quadrant_4, color) + + r = err + if r <= y: + y += 1 + err += y * 2 + 1 + if r > x || err > y: + x += 1 + err += x * 2 + 1 + + if fill: + for j in range (-radius, radius + 1): + for i in range (-radius, radius + 1): + if i * i + j * j <= radius * radius: + var draw_pos := Vector2(i + xm, j + ym) + draw_pixel_blended(sprite, draw_pos, color) + + +func draw_pixel_blended(sprite : Image, pos : Vector2, color : Color) -> void: + var saved_pixel_index := mouse_press_pixels.find(pos) + var west_limit = Global.canvas.west_limit + var east_limit = Global.canvas.east_limit + var north_limit = Global.canvas.north_limit + var south_limit = Global.canvas.south_limit + var pen_pressure = Global.canvas.pen_pressure + + if point_in_rectangle(pos, Vector2(west_limit - 1, north_limit - 1), Vector2(east_limit, south_limit)) && (saved_pixel_index == -1 || pen_pressure > mouse_press_pressure_values[saved_pixel_index]): + if color.a > 0 && color.a < 1: + color = blend_colors(color, sprite.get_pixelv(pos)) + + if saved_pixel_index == -1: + mouse_press_pixels.append(pos) + mouse_press_pressure_values.append(pen_pressure) + else: + mouse_press_pressure_values[saved_pixel_index] = pen_pressure + sprite.set_pixelv(pos, color) + + +# Thanks to https://en.wikipedia.org/wiki/Flood_fill +func flood_fill(sprite : Image, pos : Vector2, target_color : Color, replace_color : Color) -> void: + var west_limit = Global.canvas.west_limit + var east_limit = Global.canvas.east_limit + var north_limit = Global.canvas.north_limit + var south_limit = Global.canvas.south_limit + pos = pos.floor() + var pixel = sprite.get_pixelv(pos) + if target_color == replace_color: + return + elif pixel != target_color: + return + else: + + if !point_in_rectangle(pos, Vector2(west_limit - 1, north_limit - 1), Vector2(east_limit, south_limit)): + return + + var q = [pos] + for n in q: + # If the difference in colors is very small, break the loop (thanks @azagaya on GitHub!) + if target_color == replace_color: + break + var west : Vector2 = n + var east : Vector2 = n + while west.x >= west_limit && sprite.get_pixelv(west) == target_color: + west += Vector2.LEFT + while east.x < east_limit && sprite.get_pixelv(east) == target_color: + east += Vector2.RIGHT + for px in range(west.x + 1, east.x): + var p := Vector2(px, n.y) + # Draw + sprite.set_pixelv(p, replace_color) + replace_color = sprite.get_pixelv(p) + var north := p + Vector2.UP + var south := p + Vector2.DOWN + if north.y >= north_limit && sprite.get_pixelv(north) == target_color: + q.append(north) + if south.y < south_limit && sprite.get_pixelv(south) == target_color: + q.append(south) + + Global.canvas.sprite_changed_this_frame = true + + +func pattern_fill(sprite : Image, pos : Vector2, pattern : Image, target_color : Color, var offset : Vector2) -> void: + var west_limit = Global.canvas.west_limit + var east_limit = Global.canvas.east_limit + var north_limit = Global.canvas.north_limit + var south_limit = Global.canvas.south_limit + pos = pos.floor() + if !point_in_rectangle(pos, Vector2(west_limit - 1, north_limit - 1), Vector2(east_limit, south_limit)): + return + + pattern.lock() + var pattern_size := pattern.get_size() + var q = [pos] + + for n in q: + var west : Vector2 = n + var east : Vector2 = n + while west.x >= west_limit && sprite.get_pixelv(west) == target_color: + west += Vector2.LEFT + while east.x < east_limit && sprite.get_pixelv(east) == target_color: + east += Vector2.RIGHT + + for px in range(west.x + 1, east.x): + var p := Vector2(px, n.y) + var xx : int = int(px + offset.x) % int(pattern_size.x) + var yy : int = int(n.y + offset.y) % int(pattern_size.y) + var pattern_color : Color = pattern.get_pixel(xx, yy) + if pattern_color == target_color: + continue + sprite.set_pixelv(p, pattern_color) + + var north := p + Vector2.UP + var south := p + Vector2.DOWN + if north.y >= north_limit && sprite.get_pixelv(north) == target_color: + q.append(north) + if south.y < south_limit && sprite.get_pixelv(south) == target_color: + q.append(south) + + pattern.unlock() + Global.canvas.sprite_changed_this_frame = true + + +func blend_colors(color_1 : Color, color_2 : Color) -> Color: + var color := Color() + color.a = color_1.a + color_2.a * (1 - color_1.a) # Blend alpha + if color.a != 0: + # Blend colors + color.r = (color_1.r * color_1.a + color_2.r * color_2.a * (1-color_1.a)) / color.a + color.g = (color_1.g * color_1.a + color_2.g * color_2.a * (1-color_1.a)) / color.a + color.b = (color_1.b * color_1.a + color_2.b * color_2.a * (1-color_1.a)) / color.a + return color + + +# Custom blend rect function, needed because Godot's issue #31124 +func blend_rect(bg : Image, brush : Image, src_rect : Rect2, dst : Vector2) -> void: + var brush_size := brush.get_size() + var clipped_src_rect := Rect2(Vector2.ZERO, brush_size).clip(src_rect) + if clipped_src_rect.size.x <= 0 || clipped_src_rect.size.y <= 0: + return + var src_underscan := Vector2(min(0, src_rect.position.x), min(0, src_rect.position.y)) + var dest_rect := Rect2(0, 0, bg.get_width(), bg.get_height()).clip(Rect2(dst - src_underscan, clipped_src_rect.size)) + + for x in range(0, dest_rect.size.x): + for y in range(0, dest_rect.size.y): + var src_x := clipped_src_rect.position.x + x; + var src_y := clipped_src_rect.position.y + y; + + var dst_x := dest_rect.position.x + x; + var dst_y := dest_rect.position.y + y; + + brush.lock() + var brush_color := brush.get_pixel(src_x, src_y) + var bg_color := bg.get_pixel(dst_x, dst_y) + var out_color := blend_colors(brush_color, bg_color) + if out_color.a != 0: + bg.set_pixel(dst_x, dst_y, out_color) + brush.unlock() + + +func scale3X(sprite : Image, tol : float = 50) -> Image: + var scaled = Image.new() + scaled.create(sprite.get_width()*3, sprite.get_height()*3, false, Image.FORMAT_RGBA8) + scaled.lock() + sprite.lock() + var a : Color + var b : Color + var c : Color + var d : Color + var e : Color + var f : Color + var g : Color + var h : Color + var i : Color + + for x in range(1,sprite.get_width()-1): + for y in range(1,sprite.get_height()-1): + var xs : float = 3*x + var ys : float = 3*y + + a = sprite.get_pixel(x-1,y-1) + b = sprite.get_pixel(x,y-1) + c = sprite.get_pixel(x+1,y-1) + d = sprite.get_pixel(x-1,y) + e = sprite.get_pixel(x,y) + f = sprite.get_pixel(x+1,y) + g = sprite.get_pixel(x-1,y+1) + h = sprite.get_pixel(x,y+1) + i = sprite.get_pixel(x+1,y+1) + + var db : bool = similarColors(d, b, tol) + var dh : bool = similarColors(d, h, tol) + var bf : bool = similarColors(f, b, tol) + var ec : bool = similarColors(e, c, tol) + var ea : bool = similarColors(e, a, tol) + var fh : bool = similarColors(f, h, tol) + var eg : bool = similarColors(e, g, tol) + var ei : bool = similarColors(e, i, tol) + + scaled.set_pixel(xs-1, ys-1, d if (db and !dh and !bf) else e ) + scaled.set_pixel(xs, ys-1, b if (db and !dh and !bf and !ec) or + (bf and !db and !fh and !ea) else e) + scaled.set_pixel(xs+1, ys-1, f if (bf and !db and !fh) else e) + scaled.set_pixel(xs-1, ys, d if (dh and !fh and !db and !ea) or + (db and !dh and !bf and !eg) else e) + scaled.set_pixel(xs, ys, e); + scaled.set_pixel(xs+1, ys, f if (bf and !db and !fh and !ei) or + (fh and !bf and !dh and !ec) else e) + scaled.set_pixel(xs-1, ys+1, d if (dh and !fh and !db) else e) + scaled.set_pixel(xs, ys+1, h if (fh and !bf and !dh and !eg) or + (dh and !fh and !db and !ei) else e) + scaled.set_pixel(xs+1, ys+1, f if (fh and !bf and !dh) else e) + + scaled.unlock() + sprite.unlock() + return scaled + + +func rotxel(sprite : Image, angle : float) -> void: + + # If angle is simple, then nn rotation is the best + + if angle == 0 || angle == PI/2 || angle == PI || angle == 2*PI: + nn_rotate(sprite, angle) + return + + var aux : Image = Image.new() + aux.copy_from(sprite) + var center : Vector2 = Vector2(sprite.get_width()/2, sprite.get_height()/2) + var ox : int + var oy : int + var p : Color + aux.lock() + sprite.lock() + for x in range(sprite.get_width()): + for y in range(sprite.get_height()): + var dx = 3*(x - center.x) + var dy = 3*(y - center.y) + var found_pixel : bool = false + for k in range(9): + var i = -1 + k % 3 + var j = -1 + int(k / 3) + var dir = atan2(dy + j, dx + i) + var mag = sqrt(pow(dx + i, 2) + pow(dy + j, 2)) + dir -= angle + ox = round(center.x*3 + 1 + mag*cos(dir)) + oy = round(center.y*3 + 1 + mag*sin(dir)) + + if (sprite.get_width() % 2 != 0): + ox += 1 + oy += 1 + + if (ox >= 0 && ox < sprite.get_width()*3 + && oy >= 0 && oy < sprite.get_height()*3): + found_pixel = true + break + + if !found_pixel: + sprite.set_pixel(x, y, Color(0,0,0,0)) + continue + + var fil : int = oy % 3 + var col : int = ox % 3 + var index : int = col + 3*fil + + ox = round((ox - 1)/3.0); + oy = round((oy - 1)/3.0); + var a : Color + var b : Color + var c : Color + var d : Color + var e : Color + var f : Color + var g : Color + var h : Color + var i : Color + if (ox == 0 || ox == sprite.get_width() - 1 || + oy == 0 || oy == sprite.get_height() - 1): + p = aux.get_pixel(ox, oy) + else: + a = aux.get_pixel(ox-1,oy-1); + b = aux.get_pixel(ox,oy-1); + c = aux.get_pixel(ox+1,oy-1); + d = aux.get_pixel(ox-1,oy); + e = aux.get_pixel(ox,oy); + f = aux.get_pixel(ox+1,oy); + g = aux.get_pixel(ox-1,oy+1); + h = aux.get_pixel(ox,oy+1); + i = aux.get_pixel(ox+1,oy+1); + + match(index): + 0: + p = d if (similarColors(d,b) && !similarColors(d,h) + && !similarColors(b,f)) else e; + 1: + p = b if ((similarColors(d,b) && !similarColors(d,h) && + !similarColors(b,f) && !similarColors(e,c)) || + (similarColors(b,f) && !similarColors(d,b) && + !similarColors(f,h) && !similarColors(e,a))) else e; + 2: + p = f if (similarColors(b,f) && !similarColors(d,b) && + !similarColors(f,h)) else e; + 3: + p = d if ((similarColors(d,h) && !similarColors(f,h) && + !similarColors(d,b) && !similarColors(e,a)) || + (similarColors(d,b) && !similarColors(d,h) && + !similarColors(b,f) && !similarColors(e,g))) else e; + 4: + p = e + 5: + p = f if((similarColors(b,f) && !similarColors(d,b) && + !similarColors(f,h) && !similarColors(e,i)) + || (similarColors(f,h) && !similarColors(b,f) && + !similarColors(d,h) && !similarColors(e,c))) else e; + 6: + p = d if (similarColors(d,h) && !similarColors(f,h) && + !similarColors(d,b)) else e; + 7: + p = h if ((similarColors(f,h) && !similarColors(f,b) && + !similarColors(d,h) && !similarColors(e,g)) + || (similarColors(d,h) && !similarColors(f,h) && + !similarColors(d,b) && !similarColors(e,i))) else e; + 8: + p = f if (similarColors(f,h) && !similarColors(f,b) && + !similarColors(d,h)) else e; + sprite.set_pixel(x, y, p) + sprite.unlock() + aux.unlock() + + +func fake_rotsprite(sprite : Image, angle : float) -> void: + sprite.copy_from(scale3X(sprite)) + nn_rotate(sprite,angle) + sprite.resize(sprite.get_width()/3,sprite.get_height()/3,0) + + +func nn_rotate(sprite : Image, angle : float) -> void: + var aux : Image = Image.new() + aux.copy_from(sprite) + sprite.lock() + aux.lock() + var ox: int + var oy: int + var center : Vector2 = Vector2(sprite.get_width()/2, sprite.get_height()/2) + for x in range(sprite.get_width()): + for y in range(sprite.get_height()): + ox = (x - center.x)*cos(angle) + (y - center.y)*sin(angle) + center.x + oy = -(x - center.x)*sin(angle) + (y - center.y)*cos(angle) + center.y + if ox >= 0 && ox < sprite.get_width() && oy >= 0 && oy < sprite.get_height(): + sprite.set_pixel(x, y, aux.get_pixel(ox, oy)) + else: + sprite.set_pixel(x, y, Color(0,0,0,0)) + sprite.unlock() + aux.unlock() + + +func similarColors(c1 : Color, c2 : Color, tol : float = 100) -> bool: + var dist = colorDistance(c1, c2) + return dist <= tol + + +func colorDistance(c1 : Color, c2 : Color) -> float: + return sqrt(pow((c1.r - c2.r)*255, 2) + pow((c1.g - c2.g)*255, 2) + + pow((c1.b - c2.b)*255, 2) + pow((c1.a - c2.a)*255, 2)) + + +func adjust_hsv(img: Image, id : int, delta : float) -> void: + var west_limit = Global.canvas.west_limit + var east_limit = Global.canvas.east_limit + var north_limit = Global.canvas.north_limit + var south_limit = Global.canvas.south_limit + img.lock() + + match id: + 0: # Hue + for i in range(west_limit, east_limit): + for j in range(north_limit, south_limit): + var c : Color = img.get_pixel(i,j) + var hue = range_lerp(c.h,0,1,-180,180) + hue = hue + delta + + while(hue >= 180): + hue -= 360 + while(hue < -180): + hue += 360 + c.h = range_lerp(hue,-180,180,0,1) + img.set_pixel(i,j,c) + + 1: # Saturation + for i in range(west_limit, east_limit): + for j in range(north_limit, south_limit): + var c : Color = img.get_pixel(i,j) + var sat = c.s + if delta > 0: + sat = range_lerp(delta,0,100,c.s,1) + elif delta < 0: + sat = range_lerp(delta,-100,0,0,c.s) + c.s = sat + img.set_pixel(i,j,c) + + 2: # Value + for i in range(west_limit, east_limit): + for j in range(north_limit, south_limit): + var c : Color = img.get_pixel(i,j) + var val = c.v + if delta > 0: + val = range_lerp(delta,0,100,c.v,1) + elif delta < 0: + val = range_lerp(delta,-100,0,0,c.v) + + c.v = val + img.set_pixel(i,j,c) + + img.unlock() + + +# Checks if a point is inside a rectangle +func point_in_rectangle(p : Vector2, coord1 : Vector2, coord2 : Vector2) -> bool: + return p.x > coord1.x && p.y > coord1.y && p.x < coord2.x && p.y < coord2.y diff --git a/src/Autoload/Global.gd b/src/Autoload/Global.gd index 09c9fd6d0..3ce2947c9 100644 --- a/src/Autoload/Global.gd +++ b/src/Autoload/Global.gd @@ -981,212 +981,6 @@ func plot_circle(r : int) -> Array: return circle_points -func scale3X(sprite : Image, tol : float = 50) -> Image: - var scaled = Image.new() - scaled.create(sprite.get_width()*3, sprite.get_height()*3, false, Image.FORMAT_RGBA8) - scaled.lock() - sprite.lock() - var a : Color - var b : Color - var c : Color - var d : Color - var e : Color - var f : Color - var g : Color - var h : Color - var i : Color - - for x in range(1,sprite.get_width()-1): - for y in range(1,sprite.get_height()-1): - var xs : float = 3*x - var ys : float = 3*y - - a = sprite.get_pixel(x-1,y-1) - b = sprite.get_pixel(x,y-1) - c = sprite.get_pixel(x+1,y-1) - d = sprite.get_pixel(x-1,y) - e = sprite.get_pixel(x,y) - f = sprite.get_pixel(x+1,y) - g = sprite.get_pixel(x-1,y+1) - h = sprite.get_pixel(x,y+1) - i = sprite.get_pixel(x+1,y+1) - - var db : bool = similarColors(d, b, tol) - var dh : bool = similarColors(d, h, tol) - var bf : bool = similarColors(f, b, tol) - var ec : bool = similarColors(e, c, tol) - var ea : bool = similarColors(e, a, tol) - var fh : bool = similarColors(f, h, tol) - var eg : bool = similarColors(e, g, tol) - var ei : bool = similarColors(e, i, tol) - - scaled.set_pixel(xs-1, ys-1, d if (db and !dh and !bf) else e ) - scaled.set_pixel(xs, ys-1, b if (db and !dh and !bf and !ec) or - (bf and !db and !fh and !ea) else e) - scaled.set_pixel(xs+1, ys-1, f if (bf and !db and !fh) else e) - scaled.set_pixel(xs-1, ys, d if (dh and !fh and !db and !ea) or - (db and !dh and !bf and !eg) else e) - scaled.set_pixel(xs, ys, e); - scaled.set_pixel(xs+1, ys, f if (bf and !db and !fh and !ei) or - (fh and !bf and !dh and !ec) else e) - scaled.set_pixel(xs-1, ys+1, d if (dh and !fh and !db) else e) - scaled.set_pixel(xs, ys+1, h if (fh and !bf and !dh and !eg) or - (dh and !fh and !db and !ei) else e) - scaled.set_pixel(xs+1, ys+1, f if (fh and !bf and !dh) else e) - - scaled.unlock() - sprite.unlock() - return scaled - - -func rotxel(sprite : Image, angle : float) -> void: - - # If angle is simple, then nn rotation is the best - - if angle == 0 || angle == PI/2 || angle == PI || angle == 2*PI: - nn_rotate(sprite, angle) - return - - var aux : Image = Image.new() - aux.copy_from(sprite) - var center : Vector2 = Vector2(sprite.get_width()/2, sprite.get_height()/2) - var ox : int - var oy : int - var p : Color - aux.lock() - sprite.lock() - for x in range(sprite.get_width()): - for y in range(sprite.get_height()): - var dx = 3*(x - center.x) - var dy = 3*(y - center.y) - var found_pixel : bool = false - for k in range(9): - var i = -1 + k % 3 - var j = -1 + int(k / 3) - var dir = atan2(dy + j, dx + i) - var mag = sqrt(pow(dx + i, 2) + pow(dy + j, 2)) - dir -= angle - ox = round(center.x*3 + 1 + mag*cos(dir)) - oy = round(center.y*3 + 1 + mag*sin(dir)) - - if (sprite.get_width() % 2 != 0): - ox += 1 - oy += 1 - - if (ox >= 0 && ox < sprite.get_width()*3 - && oy >= 0 && oy < sprite.get_height()*3): - found_pixel = true - break - - if !found_pixel: - sprite.set_pixel(x, y, Color(0,0,0,0)) - continue - - var fil : int = oy % 3 - var col : int = ox % 3 - var index : int = col + 3*fil - - ox = round((ox - 1)/3.0); - oy = round((oy - 1)/3.0); - var a : Color - var b : Color - var c : Color - var d : Color - var e : Color - var f : Color - var g : Color - var h : Color - var i : Color - if (ox == 0 || ox == sprite.get_width() - 1 || - oy == 0 || oy == sprite.get_height() - 1): - p = aux.get_pixel(ox, oy) - else: - a = aux.get_pixel(ox-1,oy-1); - b = aux.get_pixel(ox,oy-1); - c = aux.get_pixel(ox+1,oy-1); - d = aux.get_pixel(ox-1,oy); - e = aux.get_pixel(ox,oy); - f = aux.get_pixel(ox+1,oy); - g = aux.get_pixel(ox-1,oy+1); - h = aux.get_pixel(ox,oy+1); - i = aux.get_pixel(ox+1,oy+1); - - match(index): - 0: - p = d if (similarColors(d,b) && !similarColors(d,h) - && !similarColors(b,f)) else e; - 1: - p = b if ((similarColors(d,b) && !similarColors(d,h) && - !similarColors(b,f) && !similarColors(e,c)) || - (similarColors(b,f) && !similarColors(d,b) && - !similarColors(f,h) && !similarColors(e,a))) else e; - 2: - p = f if (similarColors(b,f) && !similarColors(d,b) && - !similarColors(f,h)) else e; - 3: - p = d if ((similarColors(d,h) && !similarColors(f,h) && - !similarColors(d,b) && !similarColors(e,a)) || - (similarColors(d,b) && !similarColors(d,h) && - !similarColors(b,f) && !similarColors(e,g))) else e; - 4: - p = e - 5: - p = f if((similarColors(b,f) && !similarColors(d,b) && - !similarColors(f,h) && !similarColors(e,i)) - || (similarColors(f,h) && !similarColors(b,f) && - !similarColors(d,h) && !similarColors(e,c))) else e; - 6: - p = d if (similarColors(d,h) && !similarColors(f,h) && - !similarColors(d,b)) else e; - 7: - p = h if ((similarColors(f,h) && !similarColors(f,b) && - !similarColors(d,h) && !similarColors(e,g)) - || (similarColors(d,h) && !similarColors(f,h) && - !similarColors(d,b) && !similarColors(e,i))) else e; - 8: - p = f if (similarColors(f,h) && !similarColors(f,b) && - !similarColors(d,h)) else e; - sprite.set_pixel(x, y, p) - sprite.unlock() - aux.unlock() - - -func fake_rotsprite(sprite : Image, angle : float) -> void: - sprite.copy_from(scale3X(sprite)) - nn_rotate(sprite,angle) - sprite.resize(sprite.get_width()/3,sprite.get_height()/3,0) - - -func nn_rotate(sprite : Image, angle : float) -> void: - var aux : Image = Image.new() - aux.copy_from(sprite) - sprite.lock() - aux.lock() - var ox: int - var oy: int - var center : Vector2 = Vector2(sprite.get_width()/2, sprite.get_height()/2) - for x in range(sprite.get_width()): - for y in range(sprite.get_height()): - ox = (x - center.x)*cos(angle) + (y - center.y)*sin(angle) + center.x - oy = -(x - center.x)*sin(angle) + (y - center.y)*cos(angle) + center.y - if ox >= 0 && ox < sprite.get_width() && oy >= 0 && oy < sprite.get_height(): - sprite.set_pixel(x, y, aux.get_pixel(ox, oy)) - else: - sprite.set_pixel(x, y, Color(0,0,0,0)) - sprite.unlock() - aux.unlock() - - -func similarColors(c1 : Color, c2 : Color, tol : float = 100) -> bool: - var dist = colorDistance(c1, c2) - return dist <= tol - - -func colorDistance(c1 : Color, c2 : Color) -> float: - return sqrt(pow((c1.r - c2.r)*255, 2) + pow((c1.g - c2.g)*255, 2) - + pow((c1.b - c2.b)*255, 2) + pow((c1.a - c2.a)*255, 2)) - - func _exit_tree() -> void: config_cache.set_value("window", "screen", OS.current_screen) config_cache.set_value("window", "maximized", OS.window_maximized || OS.window_fullscreen) diff --git a/src/Canvas.gd b/src/Canvas.gd index b997b554d..ecfdcb1e6 100644 --- a/src/Canvas.gd +++ b/src/Canvas.gd @@ -24,8 +24,6 @@ var north_limit := location.y var south_limit := location.y + size.y var mouse_inside_canvas := false # used for undo var sprite_changed_this_frame := false # for optimization purposes -var mouse_press_pixels := [] # Cleared after mouse release -var mouse_press_pressure_values := [] # Cleared after mouse release var is_making_line := false var made_line := false var is_making_selection := "None" @@ -232,8 +230,8 @@ func _input(event : InputEvent) -> void: if (Input.is_action_just_released("left_mouse") && !Input.is_action_pressed("right_mouse")) || (Input.is_action_just_released("right_mouse") && !Input.is_action_pressed("left_mouse")): made_line = false - mouse_press_pixels.clear() - mouse_press_pressure_values.clear() + DrawingAlgos.mouse_press_pixels.clear() + DrawingAlgos.mouse_press_pressure_values.clear() pixel_perfect_drawer.reset() pixel_perfect_drawer_h_mirror.reset() pixel_perfect_drawer_v_mirror.reset() @@ -384,27 +382,27 @@ func _input(event : InputEvent) -> void: vertical_mirror = Global.right_vertical_mirror if fill_with == 1 && pattern_image: # Pattern fill - pattern_fill(sprite, mouse_pos, pattern_image, sprite.get_pixelv(mouse_pos), pattern_offset) + DrawingAlgos.pattern_fill(sprite, mouse_pos, pattern_image, sprite.get_pixelv(mouse_pos), pattern_offset) if horizontal_mirror: var pos := Vector2(mirror_x, mouse_pos.y) - pattern_fill(sprite, pos, pattern_image, sprite.get_pixelv(mouse_pos), pattern_offset) + DrawingAlgos.pattern_fill(sprite, pos, pattern_image, sprite.get_pixelv(mouse_pos), pattern_offset) if vertical_mirror: var pos := Vector2(mouse_pos.x, mirror_y) - pattern_fill(sprite, pos, pattern_image, sprite.get_pixelv(mouse_pos), pattern_offset) + DrawingAlgos.pattern_fill(sprite, pos, pattern_image, sprite.get_pixelv(mouse_pos), pattern_offset) if horizontal_mirror && vertical_mirror: var pos := Vector2(mirror_x, mirror_y) - pattern_fill(sprite, pos, pattern_image, sprite.get_pixelv(mouse_pos), pattern_offset) + DrawingAlgos.pattern_fill(sprite, pos, pattern_image, sprite.get_pixelv(mouse_pos), pattern_offset) else: # Flood fill - flood_fill(sprite, mouse_pos, sprite.get_pixelv(mouse_pos), current_color) + DrawingAlgos.flood_fill(sprite, mouse_pos, sprite.get_pixelv(mouse_pos), current_color) if horizontal_mirror: var pos := Vector2(mirror_x, mouse_pos.y) - flood_fill(sprite, pos, sprite.get_pixelv(pos), current_color) + DrawingAlgos.flood_fill(sprite, pos, sprite.get_pixelv(pos), current_color) if vertical_mirror: var pos := Vector2(mouse_pos.x, mirror_y) - flood_fill(sprite, pos, sprite.get_pixelv(pos), current_color) + DrawingAlgos.flood_fill(sprite, pos, sprite.get_pixelv(pos), current_color) if horizontal_mirror && vertical_mirror: var pos := Vector2(mirror_x, mirror_y) - flood_fill(sprite, pos, sprite.get_pixelv(pos), current_color) + DrawingAlgos.flood_fill(sprite, pos, sprite.get_pixelv(pos), current_color) else: # Paint all pixels of the same color var pixel_color : Color = sprite.get_pixelv(mouse_pos) @@ -709,10 +707,10 @@ func draw_brush(sprite : Image, pos : Vector2, color : Color, current_mouse_butt var current_pixel_color : Color = sprite.get_pixel(cur_pos_x, cur_pos_y) var _c := color if current_action == "Pencil" && color.a < 1: - _c = blend_colors(color, current_pixel_color) + _c = DrawingAlgos.blend_colors(color, current_pixel_color) - var saved_pixel_index := mouse_press_pixels.find(pos_floored) - if current_pixel_color != _c && (saved_pixel_index == -1 || pen_pressure > mouse_press_pressure_values[saved_pixel_index]): + var saved_pixel_index = DrawingAlgos.mouse_press_pixels.find(pos_floored) + if current_pixel_color != _c && (saved_pixel_index == -1 || pen_pressure > DrawingAlgos.mouse_press_pressure_values[saved_pixel_index]): if current_action == "LightenDarken": _c = current_pixel_color if _c.a > 0: @@ -722,10 +720,10 @@ func draw_brush(sprite : Image, pos : Vector2, color : Color, current_mouse_butt _c = current_pixel_color.darkened(ld_amount) if saved_pixel_index == -1: - mouse_press_pixels.append(pos_floored) - mouse_press_pressure_values.append(pen_pressure) + DrawingAlgos.mouse_press_pixels.append(pos_floored) + DrawingAlgos.mouse_press_pressure_values.append(pen_pressure) else: - mouse_press_pressure_values[saved_pixel_index] = pen_pressure + DrawingAlgos.mouse_press_pressure_values[saved_pixel_index] = pen_pressure drawer.set_pixel(sprite, Vector2(cur_pos_x, cur_pos_y), _c) sprite_changed_this_frame = true @@ -742,8 +740,8 @@ func draw_brush(sprite : Image, pos : Vector2, color : Color, current_mouse_butt else: _c = current_pixel_color.darkened(ld_amount) - mouse_press_pixels.append(pos_floored) - mouse_press_pressure_values.append(pen_pressure) + DrawingAlgos.mouse_press_pixels.append(pos_floored) + DrawingAlgos.mouse_press_pressure_values.append(pen_pressure) drawer_h_mirror.set_pixel(sprite, Vector2(mirror_x, cur_pos_y), _c) sprite_changed_this_frame = true @@ -755,8 +753,8 @@ func draw_brush(sprite : Image, pos : Vector2, color : Color, current_mouse_butt _c = current_pixel_color.lightened(ld_amount) else: _c = current_pixel_color.darkened(ld_amount) - mouse_press_pixels.append(pos_floored) - mouse_press_pressure_values.append(pen_pressure) + DrawingAlgos.mouse_press_pixels.append(pos_floored) + DrawingAlgos.mouse_press_pressure_values.append(pen_pressure) drawer_v_mirror.set_pixel(sprite, Vector2(cur_pos_x, mirror_y), _c) sprite_changed_this_frame = true @@ -769,23 +767,23 @@ func draw_brush(sprite : Image, pos : Vector2, color : Color, current_mouse_butt else: _c = current_pixel_color.darkened(ld_amount) - mouse_press_pixels.append(pos_floored) - mouse_press_pressure_values.append(pen_pressure) + DrawingAlgos.mouse_press_pixels.append(pos_floored) + DrawingAlgos.mouse_press_pressure_values.append(pen_pressure) drawer_hv_mirror.set_pixel(sprite, Vector2(mirror_x, mirror_y), _c) sprite_changed_this_frame = true elif brush_type == Global.Brush_Types.CIRCLE || brush_type == Global.Brush_Types.FILLED_CIRCLE: - plot_circle(sprite, pos.x, pos.y, brush_size, color, brush_type == Global.Brush_Types.FILLED_CIRCLE) + DrawingAlgos.plot_circle(sprite, pos.x, pos.y, brush_size, color, brush_type == Global.Brush_Types.FILLED_CIRCLE) # Handle mirroring var mirror_x := east_limit + west_limit - pos.x var mirror_y := south_limit + north_limit - pos.y if horizontal_mirror: - plot_circle(sprite, mirror_x, pos.y, brush_size, color, brush_type == Global.Brush_Types.FILLED_CIRCLE) + DrawingAlgos.plot_circle(sprite, mirror_x, pos.y, brush_size, color, brush_type == Global.Brush_Types.FILLED_CIRCLE) if vertical_mirror: - plot_circle(sprite, pos.x, mirror_y, brush_size, color, brush_type == Global.Brush_Types.FILLED_CIRCLE) + DrawingAlgos.plot_circle(sprite, pos.x, mirror_y, brush_size, color, brush_type == Global.Brush_Types.FILLED_CIRCLE) if horizontal_mirror && vertical_mirror: - plot_circle(sprite, mirror_x, mirror_y, brush_size, color, brush_type == Global.Brush_Types.FILLED_CIRCLE) + DrawingAlgos.plot_circle(sprite, mirror_x, mirror_y, brush_size, color, brush_type == Global.Brush_Types.FILLED_CIRCLE) sprite_changed_this_frame = true @@ -828,13 +826,13 @@ func draw_brush(sprite : Image, pos : Vector2, color : Color, current_mouse_butt mirror_y -= 1 # Use custom blend function cause of godot's issue #31124 if color.a > 0: # If it's the pencil - blend_rect(sprite, custom_brush_image, src_rect, dst) + DrawingAlgos.blend_rect(sprite, custom_brush_image, src_rect, dst) if horizontal_mirror: - blend_rect(sprite, custom_brush_image, src_rect, Vector2(mirror_x, dst.y)) + DrawingAlgos.blend_rect(sprite, custom_brush_image, src_rect, Vector2(mirror_x, dst.y)) if vertical_mirror: - blend_rect(sprite, custom_brush_image, src_rect, Vector2(dst.x, mirror_y)) + DrawingAlgos.blend_rect(sprite, custom_brush_image, src_rect, Vector2(dst.x, mirror_y)) if horizontal_mirror && vertical_mirror: - blend_rect(sprite, custom_brush_image, src_rect, Vector2(mirror_x, mirror_y)) + DrawingAlgos.blend_rect(sprite, custom_brush_image, src_rect, Vector2(mirror_x, mirror_y)) else: # if it's transparent - if it's the eraser var custom_brush := Image.new() @@ -887,138 +885,6 @@ func fill_gaps(sprite : Image, mouse_pos : Vector2, prev_mouse_pos : Vector2, co y += sy -# Thanks to https://en.wikipedia.org/wiki/Flood_fill -func flood_fill(sprite : Image, pos : Vector2, target_color : Color, replace_color : Color) -> void: - pos = pos.floor() - var pixel = sprite.get_pixelv(pos) - if target_color == replace_color: - return - elif pixel != target_color: - return - else: - - if !point_in_rectangle(pos, Vector2(west_limit - 1, north_limit - 1), Vector2(east_limit, south_limit)): - return - - var q = [pos] - for n in q: - # If the difference in colors is very small, break the loop (thanks @azagaya on GitHub!) - if target_color == replace_color: - break - var west : Vector2 = n - var east : Vector2 = n - while west.x >= west_limit && sprite.get_pixelv(west) == target_color: - west += Vector2.LEFT - while east.x < east_limit && sprite.get_pixelv(east) == target_color: - east += Vector2.RIGHT - for px in range(west.x + 1, east.x): - var p := Vector2(px, n.y) - # Draw - sprite.set_pixelv(p, replace_color) - replace_color = sprite.get_pixelv(p) - var north := p + Vector2.UP - var south := p + Vector2.DOWN - if north.y >= north_limit && sprite.get_pixelv(north) == target_color: - q.append(north) - if south.y < south_limit && sprite.get_pixelv(south) == target_color: - q.append(south) - sprite_changed_this_frame = true - - -func pattern_fill(sprite : Image, pos : Vector2, pattern : Image, target_color : Color, var offset : Vector2) -> void: - pos = pos.floor() - if !point_in_rectangle(pos, Vector2(west_limit - 1, north_limit - 1), Vector2(east_limit, south_limit)): - return - - pattern.lock() - var pattern_size := pattern.get_size() - var q = [pos] - - for n in q: - var west : Vector2 = n - var east : Vector2 = n - while west.x >= west_limit && sprite.get_pixelv(west) == target_color: - west += Vector2.LEFT - while east.x < east_limit && sprite.get_pixelv(east) == target_color: - east += Vector2.RIGHT - - for px in range(west.x + 1, east.x): - var p := Vector2(px, n.y) - var xx : int = int(px + offset.x) % int(pattern_size.x) - var yy : int = int(n.y + offset.y) % int(pattern_size.y) - var pattern_color : Color = pattern.get_pixel(xx, yy) - if pattern_color == target_color: - continue - sprite.set_pixelv(p, pattern_color) - - var north := p + Vector2.UP - var south := p + Vector2.DOWN - if north.y >= north_limit && sprite.get_pixelv(north) == target_color: - q.append(north) - if south.y < south_limit && sprite.get_pixelv(south) == target_color: - q.append(south) - - pattern.unlock() - - - -# Algorithm based on http://members.chello.at/easyfilter/bresenham.html -func plot_circle(sprite : Image, xm : int, ym : int, r : int, color : Color, fill := false) -> void: - var radius := r # Used later for filling - var x := -r - var y := 0 - var err := 2 - r * 2 # II. Quadrant - while x < 0: - var quadrant_1 := Vector2(xm - x, ym + y) - var quadrant_2 := Vector2(xm - y, ym - x) - var quadrant_3 := Vector2(xm + x, ym - y) - var quadrant_4 := Vector2(xm + y, ym + x) - draw_pixel_blended(sprite, quadrant_1, color) - draw_pixel_blended(sprite, quadrant_2, color) - draw_pixel_blended(sprite, quadrant_3, color) - draw_pixel_blended(sprite, quadrant_4, color) - - r = err - if r <= y: - y += 1 - err += y * 2 + 1 - if r > x || err > y: - x += 1 - err += x * 2 + 1 - - if fill: - for j in range (-radius, radius + 1): - for i in range (-radius, radius + 1): - if i * i + j * j <= radius * radius: - var draw_pos := Vector2(i + xm, j + ym) - draw_pixel_blended(sprite, draw_pos, color) - - -func draw_pixel_blended(sprite : Image, pos : Vector2, color : Color) -> void: - var saved_pixel_index := mouse_press_pixels.find(pos) - if point_in_rectangle(pos, Vector2(west_limit - 1, north_limit - 1), Vector2(east_limit, south_limit)) && (saved_pixel_index == -1 || pen_pressure > mouse_press_pressure_values[saved_pixel_index]): - if color.a > 0 && color.a < 1: - color = blend_colors(color, sprite.get_pixelv(pos)) - - if saved_pixel_index == -1: - mouse_press_pixels.append(pos) - mouse_press_pressure_values.append(pen_pressure) - else: - mouse_press_pressure_values[saved_pixel_index] = pen_pressure - sprite.set_pixelv(pos, color) - - -func blend_colors(color_1 : Color, color_2 : Color) -> Color: - var color := Color() - color.a = color_1.a + color_2.a * (1 - color_1.a) # Blend alpha - if color.a != 0: - # Blend colors - color.r = (color_1.r * color_1.a + color_2.r * color_2.a * (1-color_1.a)) / color.a - color.g = (color_1.g * color_1.a + color_2.g * color_2.a * (1-color_1.a)) / color.a - color.b = (color_1.b * color_1.a + color_2.b * color_2.a * (1-color_1.a)) / color.a - return color - - # Checks if a point is inside a rectangle func point_in_rectangle(p : Vector2, coord1 : Vector2, coord2 : Vector2) -> bool: return p.x > coord1.x && p.y > coord1.y && p.x < coord2.x && p.y < coord2.y @@ -1027,75 +893,3 @@ func point_in_rectangle(p : Vector2, coord1 : Vector2, coord2 : Vector2) -> bool # Returns the position in the middle of a rectangle func rectangle_center(rect_position : Vector2, rect_size : Vector2) -> Vector2: return (rect_position - rect_size / 2).floor() - - -# Custom blend rect function, needed because Godot's issue #31124 -func blend_rect(bg : Image, brush : Image, src_rect : Rect2, dst : Vector2) -> void: - var brush_size := brush.get_size() - var clipped_src_rect := Rect2(Vector2.ZERO, brush_size).clip(src_rect) - if clipped_src_rect.size.x <= 0 || clipped_src_rect.size.y <= 0: - return - var src_underscan := Vector2(min(0, src_rect.position.x), min(0, src_rect.position.y)) - var dest_rect := Rect2(0, 0, bg.get_width(), bg.get_height()).clip(Rect2(dst - src_underscan, clipped_src_rect.size)) - - for x in range(0, dest_rect.size.x): - for y in range(0, dest_rect.size.y): - var src_x := clipped_src_rect.position.x + x; - var src_y := clipped_src_rect.position.y + y; - - var dst_x := dest_rect.position.x + x; - var dst_y := dest_rect.position.y + y; - - brush.lock() - var brush_color := brush.get_pixel(src_x, src_y) - var bg_color := bg.get_pixel(dst_x, dst_y) - var out_color := blend_colors(brush_color, bg_color) - if out_color.a != 0: - bg.set_pixel(dst_x, dst_y, out_color) - brush.unlock() - - -func adjust_hsv(img: Image, id : int, delta : float) -> void: - var layer : Image = img - layer.lock() - match id: - 0: # Hue - for i in range(west_limit, east_limit): - for j in range(north_limit, south_limit): - var c : Color = layer.get_pixel(i,j) - var hue = range_lerp(c.h,0,1,-180,180) - hue = hue + delta - - while(hue >= 180): - hue -= 360 - while(hue < -180): - hue += 360 - c.h = range_lerp(hue,-180,180,0,1) - layer.set_pixel(i,j,c) - - 1: # Saturation - for i in range(west_limit, east_limit): - for j in range(north_limit, south_limit): - var c : Color = layer.get_pixel(i,j) - var sat = c.s - if delta > 0: - sat = range_lerp(delta,0,100,c.s,1) - elif delta < 0: - sat = range_lerp(delta,-100,0,0,c.s) - c.s = sat - layer.set_pixel(i,j,c) - - 2: # Value - for i in range(west_limit, east_limit): - for j in range(north_limit, south_limit): - var c : Color = layer.get_pixel(i,j) - var val = c.v - if delta > 0: - val = range_lerp(delta,0,100,c.v,1) - elif delta < 0: - val = range_lerp(delta,-100,0,0,c.v) - - c.v = val - layer.set_pixel(i,j,c) - - layer.unlock() diff --git a/src/UI/Dialogs/ExportDialog.gd b/src/UI/Dialogs/ExportDialog.gd index 5b3a512d0..fc36187af 100644 --- a/src/UI/Dialogs/ExportDialog.gd +++ b/src/UI/Dialogs/ExportDialog.gd @@ -375,7 +375,7 @@ func blend_layers(image: Image, canvas: Canvas, origin: Vector2 = Vector2(0, 0)) var pixel_color := layer_image.get_pixel(xx, yy) var alpha : float = pixel_color.a * layer[2] layer_image.set_pixel(xx, yy, Color(pixel_color.r, pixel_color.g, pixel_color.b, alpha)) - canvas.blend_rect(image, layer_image, Rect2(canvas.position, canvas.size), origin) + DrawingAlgos.blend_rect(image, layer_image, Rect2(canvas.position, canvas.size), origin) layer_i += 1 image.unlock() diff --git a/src/UI/Dialogs/HSVDialog.gd b/src/UI/Dialogs/HSVDialog.gd index e98667076..4b03a263f 100644 --- a/src/UI/Dialogs/HSVDialog.gd +++ b/src/UI/Dialogs/HSVDialog.gd @@ -35,9 +35,9 @@ func _on_Cancel_pressed() -> void: func _on_Apply_pressed() -> void: Global.canvas.handle_undo("Draw") - Global.canvas.adjust_hsv(current_layer,0,hue_slider.value) - Global.canvas.adjust_hsv(current_layer,1,sat_slider.value) - Global.canvas.adjust_hsv(current_layer,2,val_slider.value) + DrawingAlgos.adjust_hsv(current_layer,0,hue_slider.value) + DrawingAlgos.adjust_hsv(current_layer,1,sat_slider.value) + DrawingAlgos.adjust_hsv(current_layer,2,val_slider.value) Global.canvas.update_texture(Global.current_layer) Global.canvas.handle_redo("Draw") reset() @@ -57,9 +57,9 @@ func reset() -> void: func update_preview() -> void: preview_image.copy_from(current_layer) - Global.canvas.adjust_hsv(preview_image,0,hue_slider.value) - Global.canvas.adjust_hsv(preview_image,1,sat_slider.value) - Global.canvas.adjust_hsv(preview_image,2,val_slider.value) + DrawingAlgos.adjust_hsv(preview_image,0,hue_slider.value) + DrawingAlgos.adjust_hsv(preview_image,1,sat_slider.value) + DrawingAlgos.adjust_hsv(preview_image,2,val_slider.value) preview_texture.create_from_image(preview_image, 0) preview.texture = preview_texture diff --git a/src/UI/Dialogs/RotateImage.gd b/src/UI/Dialogs/RotateImage.gd index 23e6e7c99..3cbd90ade 100644 --- a/src/UI/Dialogs/RotateImage.gd +++ b/src/UI/Dialogs/RotateImage.gd @@ -32,11 +32,11 @@ func _on_RotateImage_confirmed() -> void: Global.canvas.handle_undo("Draw") match $VBoxContainer/HBoxContainer2/OptionButton.text: "Rotxel": - Global.rotxel(layer,$VBoxContainer/HBoxContainer/HSlider.value*PI/180) + DrawingAlgos.rotxel(layer,$VBoxContainer/HBoxContainer/HSlider.value*PI/180) "Nearest neighbour": - Global.nn_rotate(layer,$VBoxContainer/HBoxContainer/HSlider.value*PI/180) + DrawingAlgos.nn_rotate(layer,$VBoxContainer/HBoxContainer/HSlider.value*PI/180) "Upscale, Rotate and Downscale": - Global.fake_rotsprite(layer,$VBoxContainer/HBoxContainer/HSlider.value*PI/180) + DrawingAlgos.fake_rotsprite(layer,$VBoxContainer/HBoxContainer/HSlider.value*PI/180) Global.canvas.handle_redo("Draw") $VBoxContainer/HBoxContainer/HSlider.value = 0 @@ -45,11 +45,11 @@ func rotate() -> void: sprite.copy_from(aux_img) match $VBoxContainer/HBoxContainer2/OptionButton.text: "Rotxel": - Global.rotxel(sprite,$VBoxContainer/HBoxContainer/HSlider.value*PI/180) + DrawingAlgos.rotxel(sprite,$VBoxContainer/HBoxContainer/HSlider.value*PI/180) "Nearest neighbour": - Global.nn_rotate(sprite,$VBoxContainer/HBoxContainer/HSlider.value*PI/180) + DrawingAlgos.nn_rotate(sprite,$VBoxContainer/HBoxContainer/HSlider.value*PI/180) "Upscale, Rotate and Downscale": - Global.fake_rotsprite(sprite,$VBoxContainer/HBoxContainer/HSlider.value*PI/180) + DrawingAlgos.fake_rotsprite(sprite,$VBoxContainer/HBoxContainer/HSlider.value*PI/180) texture.create_from_image(sprite, 0) diff --git a/src/UI/Timeline/AnimationTimeline.gd b/src/UI/Timeline/AnimationTimeline.gd index 98fd860df..89a52b70b 100644 --- a/src/UI/Timeline/AnimationTimeline.gd +++ b/src/UI/Timeline/AnimationTimeline.gd @@ -447,7 +447,7 @@ func _on_MergeDownLayer_pressed() -> void: var new_layer := Image.new() new_layer.copy_from(c.layers[Global.current_layer - 1][0]) new_layer.lock() - c.blend_rect(new_layer, selected_layer, Rect2(c.position, c.size), Vector2.ZERO) + DrawingAlgos.blend_rect(new_layer, selected_layer, Rect2(c.position, c.size), Vector2.ZERO) new_layers_canvas.remove(Global.current_layer) if !selected_layer.is_invisible() and Global.layers[Global.current_layer - 1][5].size() > 1 and (c in Global.layers[Global.current_layer - 1][5]): new_layers[Global.current_layer - 1][5].erase(c)