369 lines
9.8 KiB
GDScript
369 lines
9.8 KiB
GDScript
|
|
# These functions are the same as the ones found in the GDNative library.
|
|
# They are used if the user's platform is not supported.
|
|
|
|
const Util = preload("../util/util.gd")
|
|
|
|
var _blur_buffer : Image
|
|
|
|
|
|
func get_red_range(im: Image, rect: Rect2) -> Vector2:
|
|
rect = rect.clip(Rect2(0, 0, im.get_width(), im.get_height()))
|
|
var min_x := int(rect.position.x)
|
|
var min_y := int(rect.position.y)
|
|
var max_x := min_x + int(rect.size.x)
|
|
var max_y := min_y + int(rect.size.y)
|
|
|
|
im.lock()
|
|
|
|
var min_height := im.get_pixel(min_x, min_y).r
|
|
var max_height := min_height
|
|
|
|
for y in range(min_y, max_y):
|
|
for x in range(min_x, max_x):
|
|
var h = im.get_pixel(x, y).r
|
|
if h < min_height:
|
|
min_height = h
|
|
elif h > max_height:
|
|
max_height = h
|
|
|
|
im.unlock()
|
|
|
|
return Vector2(min_height, max_height)
|
|
|
|
|
|
func get_red_sum(im: Image, rect: Rect2) -> float:
|
|
rect = rect.clip(Rect2(0, 0, im.get_width(), im.get_height()))
|
|
var min_x := int(rect.position.x)
|
|
var min_y := int(rect.position.y)
|
|
var max_x := min_x + int(rect.size.x)
|
|
var max_y := min_y + int(rect.size.y)
|
|
|
|
var sum := 0.0
|
|
|
|
im.lock()
|
|
|
|
for y in range(min_y, max_y):
|
|
for x in range(min_x, max_x):
|
|
sum += im.get_pixel(x, y).r
|
|
|
|
im.unlock()
|
|
|
|
return sum
|
|
|
|
|
|
func get_red_sum_weighted(im: Image, brush: Image, pos: Vector2,
|
|
var factor: float) -> float:
|
|
|
|
var min_x = int(pos.x)
|
|
var min_y = int(pos.y)
|
|
var max_x = min_x + brush.get_width()
|
|
var max_y = min_y + brush.get_height()
|
|
var min_noclamp_x = min_x
|
|
var min_noclamp_y = min_y
|
|
|
|
min_x = Util.clamp_int(min_x, 0, im.get_width())
|
|
min_y = Util.clamp_int(min_y, 0, im.get_height())
|
|
max_x = Util.clamp_int(max_x, 0, im.get_width())
|
|
max_y = Util.clamp_int(max_y, 0, im.get_height())
|
|
|
|
var sum = 0.0
|
|
|
|
im.lock()
|
|
brush.lock()
|
|
|
|
for y in range(min_y, max_y):
|
|
var by = y - min_noclamp_y
|
|
|
|
for x in range(min_x, max_x):
|
|
var bx = x - min_noclamp_x
|
|
|
|
var shape_value = brush.get_pixel(bx, by).r
|
|
sum += im.get_pixel(x, y).r * shape_value * factor
|
|
|
|
im.lock()
|
|
brush.unlock()
|
|
|
|
return sum
|
|
|
|
|
|
func add_red_brush(im: Image, brush: Image, pos: Vector2, var factor: float):
|
|
var min_x = int(pos.x)
|
|
var min_y = int(pos.y)
|
|
var max_x = min_x + brush.get_width()
|
|
var max_y = min_y + brush.get_height()
|
|
var min_noclamp_x = min_x
|
|
var min_noclamp_y = min_y
|
|
|
|
min_x = Util.clamp_int(min_x, 0, im.get_width())
|
|
min_y = Util.clamp_int(min_y, 0, im.get_height())
|
|
max_x = Util.clamp_int(max_x, 0, im.get_width())
|
|
max_y = Util.clamp_int(max_y, 0, im.get_height())
|
|
|
|
im.lock()
|
|
brush.lock()
|
|
|
|
for y in range(min_y, max_y):
|
|
var by = y - min_noclamp_y
|
|
|
|
for x in range(min_x, max_x):
|
|
var bx = x - min_noclamp_x
|
|
|
|
var shape_value = brush.get_pixel(bx, by).r
|
|
var r = im.get_pixel(x, y).r + shape_value * factor
|
|
im.set_pixel(x, y, Color(r, r, r))
|
|
|
|
im.lock()
|
|
brush.unlock()
|
|
|
|
|
|
func lerp_channel_brush(im: Image, brush: Image, pos: Vector2,
|
|
factor: float, target_value: float, channel: int):
|
|
|
|
var min_x = int(pos.x)
|
|
var min_y = int(pos.y)
|
|
var max_x = min_x + brush.get_width()
|
|
var max_y = min_y + brush.get_height()
|
|
var min_noclamp_x = min_x
|
|
var min_noclamp_y = min_y
|
|
|
|
min_x = Util.clamp_int(min_x, 0, im.get_width())
|
|
min_y = Util.clamp_int(min_y, 0, im.get_height())
|
|
max_x = Util.clamp_int(max_x, 0, im.get_width())
|
|
max_y = Util.clamp_int(max_y, 0, im.get_height())
|
|
|
|
im.lock()
|
|
brush.lock()
|
|
|
|
for y in range(min_y, max_y):
|
|
var by = y - min_noclamp_y
|
|
|
|
for x in range(min_x, max_x):
|
|
var bx = x - min_noclamp_x
|
|
|
|
var shape_value = brush.get_pixel(bx, by).r
|
|
var c = im.get_pixel(x, y)
|
|
c[channel] = lerp(c[channel], target_value, shape_value * factor)
|
|
im.set_pixel(x, y, c)
|
|
|
|
im.lock()
|
|
brush.unlock()
|
|
|
|
|
|
func lerp_color_brush(im: Image, brush: Image, pos: Vector2,
|
|
factor: float, target_value: Color):
|
|
|
|
var min_x = int(pos.x)
|
|
var min_y = int(pos.y)
|
|
var max_x = min_x + brush.get_width()
|
|
var max_y = min_y + brush.get_height()
|
|
var min_noclamp_x = min_x
|
|
var min_noclamp_y = min_y
|
|
|
|
min_x = Util.clamp_int(min_x, 0, im.get_width())
|
|
min_y = Util.clamp_int(min_y, 0, im.get_height())
|
|
max_x = Util.clamp_int(max_x, 0, im.get_width())
|
|
max_y = Util.clamp_int(max_y, 0, im.get_height())
|
|
|
|
im.lock()
|
|
brush.lock()
|
|
|
|
for y in range(min_y, max_y):
|
|
var by = y - min_noclamp_y
|
|
|
|
for x in range(min_x, max_x):
|
|
var bx = x - min_noclamp_x
|
|
|
|
var shape_value = brush.get_pixel(bx, by).r
|
|
var c = im.get_pixel(x, y).linear_interpolate(target_value, factor * shape_value)
|
|
im.set_pixel(x, y, c)
|
|
|
|
im.lock()
|
|
brush.unlock()
|
|
|
|
|
|
func generate_gaussian_brush(im: Image) -> float:
|
|
var sum := 0.0
|
|
var center := Vector2(im.get_width() / 2, im.get_height() / 2)
|
|
var radius := min(im.get_width(), im.get_height()) / 2.0
|
|
|
|
im.lock()
|
|
|
|
for y in im.get_height():
|
|
for x in im.get_width():
|
|
var d := Vector2(x, y).distance_to(center) / radius
|
|
var v := clamp(1.0 - d * d * d, 0.0, 1.0)
|
|
im.set_pixel(x, y, Color(v, v, v))
|
|
sum += v;
|
|
|
|
im.unlock()
|
|
return sum
|
|
|
|
|
|
func blur_red_brush(im: Image, brush: Image, pos: Vector2, factor: float):
|
|
factor = clamp(factor, 0.0, 1.0)
|
|
|
|
if _blur_buffer == null:
|
|
_blur_buffer = Image.new()
|
|
var buffer := _blur_buffer
|
|
|
|
var buffer_width := brush.get_width() + 2
|
|
var buffer_height := brush.get_height() + 2
|
|
|
|
if buffer_width != buffer.get_width() or buffer_height != buffer.get_height():
|
|
buffer.create(buffer_width, buffer_height, false, Image.FORMAT_RF)
|
|
|
|
im.lock()
|
|
buffer.lock()
|
|
|
|
var min_x := int(pos.x) - 1
|
|
var min_y := int(pos.y) - 1
|
|
var max_x := min_x + buffer.get_width()
|
|
var max_y := min_y + buffer.get_height()
|
|
|
|
var im_clamp_w = im.get_width() - 1
|
|
var im_clamp_h = im.get_height() - 1
|
|
|
|
# Copy pixels to temporary buffer
|
|
for y in range(min_y, max_y):
|
|
for x in range(min_x, max_x):
|
|
var ix := clamp(x, 0, im_clamp_w)
|
|
var iy := clamp(y, 0, im_clamp_h)
|
|
var c = im.get_pixel(ix, iy)
|
|
buffer.set_pixel(x - min_x, y - min_y, c)
|
|
|
|
min_x = int(pos.x)
|
|
min_y = int(pos.y)
|
|
max_x = min_x + brush.get_width()
|
|
max_y = min_y + brush.get_height()
|
|
var min_noclamp_x := min_x
|
|
var min_noclamp_y := min_y
|
|
|
|
min_x = Util.clamp_int(min_x, 0, im.get_width())
|
|
min_y = Util.clamp_int(min_y, 0, im.get_height())
|
|
max_x = Util.clamp_int(max_x, 0, im.get_width())
|
|
max_y = Util.clamp_int(max_y, 0, im.get_height())
|
|
|
|
brush.lock()
|
|
|
|
# Apply blur
|
|
for y in range(min_y, max_y):
|
|
var by := y - min_noclamp_y
|
|
|
|
for x in range(min_x, max_x):
|
|
var bx := x - min_noclamp_x
|
|
|
|
var shape_value := brush.get_pixel(bx, by).r * factor
|
|
|
|
var p10 = buffer.get_pixel(bx + 1, by ).r
|
|
var p01 = buffer.get_pixel(bx, by + 1).r
|
|
var p11 = buffer.get_pixel(bx + 1, by + 1).r
|
|
var p21 = buffer.get_pixel(bx + 2, by + 1).r
|
|
var p12 = buffer.get_pixel(bx + 1, by + 2).r
|
|
|
|
var m = (p10 + p01 + p11 + p21 + p12) * 0.2
|
|
var p = lerp(p11, m, shape_value * factor)
|
|
|
|
im.set_pixel(x, y, Color(p, p, p))
|
|
|
|
im.unlock()
|
|
buffer.unlock()
|
|
brush.unlock()
|
|
|
|
|
|
func paint_indexed_splat(index_map: Image, weight_map: Image, brush: Image, pos: Vector2, \
|
|
texture_index: int, factor: float):
|
|
|
|
var min_x := pos.x
|
|
var min_y := pos.y
|
|
var max_x := min_x + brush.get_width()
|
|
var max_y := min_y + brush.get_height()
|
|
var min_noclamp_x := min_x
|
|
var min_noclamp_y := min_y
|
|
|
|
min_x = Util.clamp_int(min_x, 0, index_map.get_width())
|
|
min_y = Util.clamp_int(min_y, 0, index_map.get_height())
|
|
max_x = Util.clamp_int(max_x, 0, index_map.get_width())
|
|
max_y = Util.clamp_int(max_y, 0, index_map.get_height())
|
|
|
|
var texture_index_f := float(texture_index) / 255.0
|
|
var all_texture_index_f := Color(texture_index_f, texture_index_f, texture_index_f)
|
|
var ci := texture_index % 3
|
|
var cm := Color(-1, -1, -1)
|
|
cm[ci] = 1
|
|
|
|
index_map.lock()
|
|
weight_map.lock()
|
|
brush.lock()
|
|
|
|
for y in range(min_y, max_y):
|
|
var by := y - min_noclamp_y
|
|
|
|
for x in range(min_x, max_x):
|
|
var bx := x - min_noclamp_x
|
|
|
|
var shape_value := brush.get_pixel(bx, by).r * factor
|
|
if shape_value == 0.0:
|
|
continue
|
|
|
|
var i := index_map.get_pixel(x, y)
|
|
var w := weight_map.get_pixel(x, y)
|
|
|
|
# Decompress third weight to make computations easier
|
|
w[2] = 1.0 - w[0] - w[1]
|
|
|
|
# The index map tells which textures to blend.
|
|
# The weight map tells their blending amounts.
|
|
# This brings the limitation that up to 3 textures can blend at a time in a given pixel.
|
|
# Painting this in real time can be a challenge.
|
|
|
|
# The approach here is a compromise for simplicity.
|
|
# Each texture is associated a fixed component of the index map (R, G or B),
|
|
# so two neighbor pixels having the same component won't be guaranteed to blend.
|
|
# In other words, texture T will not be able to blend with T + N * k,
|
|
# where k is an integer, and N is the number of components in the index map (up to 4).
|
|
# It might still be able to blend due to a special case when an area is uniform,
|
|
# but not otherwise.
|
|
|
|
# Dynamic component assignment sounds like the alternative, however I wasn't able
|
|
# to find a painting algorithm that wasn't confusing, at least the current one is
|
|
# predictable.
|
|
|
|
# Need to use approximation because Color is float but GDScript uses doubles...
|
|
if abs(i[ci] - texture_index_f) > 0.001:
|
|
# Pixel does not have our texture index,
|
|
# transfer its weight to other components first
|
|
if w[ci] > shape_value:
|
|
w -= cm * shape_value
|
|
|
|
elif w[ci] >= 0.0:
|
|
w[ci] = 0.0
|
|
i[ci] = texture_index_f
|
|
|
|
else:
|
|
# Pixel has our texture index, increase its weight
|
|
if w[ci] + shape_value < 1.0:
|
|
w += cm * shape_value
|
|
|
|
else:
|
|
# Pixel weight is full, we can set all components to the same index.
|
|
# Need to nullify other weights because they would otherwise never reach
|
|
# zero due to normalization
|
|
w = Color(0, 0, 0)
|
|
w[ci] = 1.0
|
|
i = all_texture_index_f
|
|
|
|
# No `saturate` function in Color??
|
|
w[0] = clamp(w[0], 0.0, 1.0)
|
|
w[1] = clamp(w[1], 0.0, 1.0)
|
|
w[2] = clamp(w[2], 0.0, 1.0)
|
|
|
|
# Renormalize
|
|
w /= w[0] + w[1] + w[2]
|
|
|
|
index_map.set_pixel(x, y, i)
|
|
weight_map.set_pixel(x, y, w)
|
|
|
|
index_map.lock()
|
|
weight_map.lock()
|
|
brush.unlock()
|