概述
Godot内部提供了ColorPicker
和ColorPickerButton
两个颜色选择控件,也提供了GradientTexture1D
和GradientTexture2D
,来实现参数化的渐变图片。
但是有时候我们可能需要用Godot自己编写一些绘图程序,可能ColorPicker
和ColorPickerButton
就显得比较Low了,我们可能想自己实现调色板或者色环之类的。
本篇旨在阐述如何用简单的GDScript代码形式生成调色板,通过TextureRect
控件来呈现,并进取色交互。
基础原理
通过构造Image
,然后填色或设定每个像素颜色,然后再构造ImageTexture
资源,就可以将程序生成的图片赋值给控件的Texture
属性。这是本篇的基础。
我们创建一个用户界面场景,添加一个TextureRect
控件:
为TextureRect
控件添加如下代码:
extends TextureRect
func _ready():
# 构造Image
var img = Image.new()
img = img.create(100,100,true,Image.FORMAT_RGBA8) # 限定尺寸和格式
img.fill(Color.YELLOW_GREEN) # 填充纯色
# 从Image创建ImageTexture,并赋值给TextureRect
texture = ImageTexture.create_from_image(img)
上面的代码动态创建一张100×100px
带有RGBA信息的图片,然后用Image
的fill()
方法,填充纯色。
运行场景后,就可以看到一个TextureRect
显示了一张被填充为黄绿色的程序生成图片。
如果你还记得我在【Godot4.2】图片处理函数库 - textureDB一文中贴出的代码,生成纯色填充图片被写成了一个函数color_block
。所以通过简单的函数封装,代码就变成了如下形式:
extends TextureRect
func _ready():
texture = color_block(Vector2(100,100),Color.YELLOW_GREEN)
# 创建纯色块的ImageTexture
func color_block(size:Vector2,color:Color=Color.WHITE) -> ImageTexture:
var img = Image.new()
img = img.create(size.x,size.y,false,Image.FORMAT_RGBA8)
img.fill(color)
return ImageTexture.create_from_image(img)
fill()
方法与遍历图片的每个像素,然后用set_pixel()
赋予同一个颜色是等价的。
等价函数:
# 创建纯色块的ImageTexture
func color_block(size:Vector2,color:Color=Color.WHITE) -> ImageTexture:
var img = Image.new()
img = img.create(size.x,size.y,false,Image.FORMAT_RGBA8)
# 遍历每一个像素
for x in range(size.x):
for y in range(size.y):
img.set_pixel(x,y,Color.YELLOW_GREEN) # 设定该像素的颜色值
return ImageTexture.create_from_image(img)
实现颜色渐变
我们编写一个函数gradient
用来生成两种颜色的线性渐变。
同样我们需要像上面一样遍历图片的每一个像素,并设定颜色,而颜色值我们用全局函数leap进行线性插值,插值的比例我们采用x/100.0
的水平坐标百分比。
extends TextureRect
func _ready():
texture = gradient(Vector2(100,100))
# 一维渐变图片
func gradient(size:Vector2,color1:Color=Color.BLACK,color2:Color=Color.WHITE) -> ImageTexture:
var img = Image.new()
img = img.create(size.x,size.y,false,Image.FORMAT_RGBA8)
# 遍历每一个像素
for x in range(size.x):
for y in range(size.y):
img.set_pixel(x,y,lerp(color1,color2,x/100.0)) # 设定该像素的颜色值
return ImageTexture.create_from_image(img)
默认我们只传入生成的图片尺寸,则会生成一个由黑到白的线性渐变图:
我们传入自定义的两种颜色,比如黄色和黄绿色:
texture = gradient(Vector2(100,100),Color.YELLOW,Color.YELLOW_GREEN)
则会生成如下的颜色渐变纹理。
这与Godot内置的gradientTexture1D
效果是一致的。
基于GradientTexture1D和Gradient资源的版本
# 一维渐变图片
func gradient(size:Vector2,color1:Color=Color.BLACK,color2:Color=Color.WHITE) -> GradientTexture1D:
var texture = GradientTexture1D.new()
# 构建Gradient资源
var grad = Gradient.new()
grad.set_color(0.0,color1) # 在起始处添加颜色1
grad.set_color(1.0,color2) # 在末尾处添加颜色2
texture.gradient = grad
return texture
基于GradientTexture1D
和Gradient
资源创建渐变,好处是可以添加更多的颜色。
模仿ColorPicker控件的调色板
实现目标颜色的饱和度和亮度调制
下图是Godot内置的颜色选择控件——ColorPicker
。其右侧的窄带选择色相,左侧的调色板则对同一色相进行不同亮度和饱和度的调制。
这里我们首先实现左侧,对同一色相进行不同亮度和饱和度的调制。
extends TextureRect
# 要调制亮度和饱和度的颜色
@export var color:Color = Color.RED
func _ready():
# 构造Image
var img = Image.new()
img = img.create(100,100,true,Image.FORMAT_RGBA8)
# 遍历像素进行填色
for x in range(100):
for y in range(100):
var color_x = lerp(Color.WHITE,color,x/100.0) # 水平饱和度逐渐增加
var color_y = lerp(color_x,Color.BLACK,y/100.0) # 垂直亮度逐减
img.set_pixel(x,y,color_y)
# 生成并显示图片
texture = ImageTexture.create_from_image(img)
默认红色调制出的图片如下:
这里代码使用了导出变量color用于指定要调制的目标颜色,所以我们只需要修改一下,就可以调制任何其他颜色的色板出来。
实现目标颜色选择色带
接着我们实现右侧窄带部分,这部分的用于选择基础的目标颜色。
写法1
我这里用最笨的方法就是观察ColorPicker
这个目标颜色部分是由纯红色逐渐到黄色再到绿色…,然后就用RGB三色混合的方式模拟了一遍这个过程:
extends TextureRect
func _ready():
var img = Image.new()
img = img.create(10,255*6,true,Image.FORMAT_RGBA8)
for x in range(10.0):
# 红色逐渐混合绿色 -- 由红变黄
for val in range(255):
img.set_pixel(x,val,Color8(255,val,0))
# 从红绿混合中逐渐减去红色比重 -- 由黄变绿
for val in range(255):
img.set_pixel(x,val + 255,Color8(255 - val,255,0))
# 绿色逐渐混合蓝色
for val in range(255):
img.set_pixel(x,val + 255 * 2,Color8(0,255,val))
# 蓝绿混合中逐渐减去绿色比重
for val in range(255):
img.set_pixel(x,val + 255 * 3,Color8(0,255-val,255))
# 蓝色逐渐混合红色 -- 由蓝变紫
for val in range(255):
img.set_pixel(x,val + 255 * 4,Color8(val,0,255))
# 红蓝混合色中逐渐减去蓝色比重 -- 由紫变红
for val in range(255):
img.set_pixel(x,val + 255 * 5,Color8(255,0,255-val))
texture = ImageTexture.create_from_image(img)
生成的效果:
写法2
上面的代码写的不是太简洁,所以代码可以简化如下:
extends TextureRect
func _ready():
var img = Image.new()
img = img.create(10,100 * 6,true,Image.FORMAT_RGBA8)
var colors = [Color(1,0,0),Color(1,1,0),Color(0,1,0),Color(0,1,1),Color(0,0,1),Color(1,0,1),Color(1,0,0)]
for i in range(colors.size()):
if i+1 < colors.size():
var color_A = colors[i]
var color_B = colors[i+1]
for x in range(10.0):
# 红色逐渐混合绿色
for val in range(100.0):
img.set_pixel(x,val + 100 * i,lerp(color_A,color_B,val/100.0))
texture = ImageTexture.create_from_image(img)
写法3
基于GradientTexture1D
和Gradient
资源创建的写法:
extends TextureRect
func _ready():
var colors = [Color(1,0,0),Color(1,1,0),Color(0,1,0),Color(0,1,1),Color(0,0,1),Color(1,0,1),Color(1,0,0)]
# 构造Image
var grad_texture = GradientTexture2D.new()
# 垂直方向进行渐变
grad_texture.fill_from = Vector2(0,0)
grad_texture.fill_to = Vector2(0,1)
# 构造Gradient资源
var grad = Gradient.new()
# 删除默认的两个点
grad.remove_point(1)
grad.remove_point(0)
# 添加新的点
for i in range(colors.size()):
grad.add_point(float(i)/6.0,colors[i])
# 赋值
grad_texture.gradient = grad
# 显示
texture = grad_texture
自定义控件与颜色选择交互演示
通过上面探讨的基础,我们已经可以实现自己的颜色选择器功能,为了让代码清晰,我封装了ColorsBar
和HSBRect
两个自定义控件。
代码分别如下:
ColorsBar源码
# ======================================================================
# 名称:ColorsBar
# 类型:自定义控件
# 描述:创建一个色相选择的渐变图,可以选择RGB混合模式下所有可能值的颜色
# 作者:巽星石
# Godot版本:v4.2.1.stable.official [b09f793f5]
# 创建时间:2024年3月9日10:31:42
# 最后修改时间:2024年3月10日19:31:46
# ======================================================================
@tool
class_name ColorsBar extends TextureRect
signal color_changed(color:Color) # 选择颜色后触发
# ============================= 参数 =============================
var pos:Vector2 # 鼠标位置
var can_move:bool # 能否移动
# ============================= 虚函数 =============================
# 初始化
func _init():
create_colors()
# 处理鼠标交互
func _gui_input(event):
if event is InputEventMouseButton:
if event.button_index == MOUSE_BUTTON_LEFT: # 鼠标左键
can_move = event.is_pressed() # 按下开启can_move
if can_move:
get_color(event)
if event is InputEventMouseMotion: # 鼠标移动
if can_move:
get_color(event)
# 获取颜色
func get_color(event:InputEvent):
var img:Image = texture.get_image()
# 记录鼠标局部坐标
pos.y = clamp(event.position.y,0,size.y-1)
pos.x = clamp(event.position.x,0,size.x)
# 触发color_changed信号,返回获取的目标颜色
emit_signal("color_changed",img.get_pixelv(pos))
# 请求重绘
queue_redraw()
func _draw():
# 绘制白色矩形框
var rect = Rect2(Vector2(-1,pos.y-1),Vector2(size.x+1,3))
draw_rect(rect,Color.WHITE,false,1)
# ============================= 核心函数 =============================
# 创建可选颜色
func create_colors():
var colors = [Color(1,0,0),Color(1,1,0),Color(0,1,0),Color(0,1,1),Color(0,0,1),Color(1,0,1),Color(1,0,0)]
# 构造Image
var grad_texture = GradientTexture2D.new()
# 修改GradientTexture2D的图片尺寸,与TextureRect尺寸保持一致,方便鼠标交互时采样
grad_texture.width = size.x
grad_texture.height = size.y
# 垂直方向进行渐变
grad_texture.fill_from = Vector2(0,0)
grad_texture.fill_to = Vector2(0,1)
# 构造Gradient资源
var grad = Gradient.new()
# 删除默认的两个点
grad.remove_point(1)
grad.remove_point(0)
# 添加新的点
for i in range(colors.size()):
grad.add_point(float(i)/6.0,colors[i])
# 赋值
grad_texture.gradient = grad
# 显示
texture = grad_texture
HSBRect源码
# ======================================================================
# 名称:HSBRect
# 类型:自定义控件
# 描述:基于给定颜色,进行亮度和饱和度调制,生成一个矩形的颜色渐变区域,
# 可以用鼠标交互进行颜色选择
# 作者:巽星石
# Godot版本:v4.2.1.stable.official [b09f793f5]
# 创建时间:2024年3月9日10:31:42
# 最后修改时间:2024年3月10日19:31:46
# ======================================================================
@tool
class_name HSBRect extends TextureRect
signal color_changed(color:Color) # 选择颜色后触发
# ============================= 参数 =============================
pos:Vector2 = Vector2(size.x-1,0) # 鼠标位置
var can_move:bool # 能否移动
# 要调制亮度和饱和度的颜色
@export var color:Color = Color.RED:
set(val):
color = val
create_hsb()
# 触发color_changed信号
var img:Image = texture.get_image()
emit_signal("color_changed",img.get_pixelv(pos))
# ============================= 虚函数 =============================
# 初始化
func _init():
create_hsb()
# 处理鼠标交互
func _gui_input(event):
if event is InputEventMouseButton:
if event.button_index == MOUSE_BUTTON_LEFT: # 鼠标左键
can_move = event.is_pressed() # 按下开启can_move
if can_move:
get_color(event)
if event is InputEventMouseMotion: # 鼠标移动
if can_move:
get_color(event)
func _draw():
# 绘制白色矩形框
var rect = Rect2(Vector2(pos.x-1,pos.y-1),Vector2(3,3))
draw_rect(rect,Color.WHITE,false,1)
# 获取颜色
func get_color(event:InputEvent):
var img:Image = texture.get_image()
# 记录鼠标局部坐标
pos.y = clamp(event.position.y,0,size.y-1)
pos.x = clamp(event.position.x,0,size.x-1)
# 触发color_changed信号,返回获取的目标颜色
emit_signal("color_changed",img.get_pixelv(pos))
# 请求重绘
queue_redraw()
# ============================= 核心函数 =============================
# 创建由色相、饱和度和亮度渐变而成的区域
func create_hsb():
# 构造Image
var img = Image.new()
img = img.create(size.x,size.y,true,Image.FORMAT_RGBA8)
# 遍历像素进行填色
for x in range(size.x):
for y in range(size.y):
var color_x = lerp(Color.WHITE,color,x/size.x) # 水平饱和度逐渐增加
var color_y = lerp(color_x,Color.BLACK,y/size.y) # 垂直亮度逐减
img.set_pixel(x,y,color_y)
# 生成并显示图片
texture = ImageTexture.create_from_image(img)
使用测试
我们创建一个测试场景,分别添加ColorsBar
和HSBRect
实例,以及一个ColorRect
。
场景根节点代码如下:
extends Control
@onready var colors_bar = $ColorsBar # 颜色选择条
@onready var hsb_rect = $HSBRect # 色相-饱和度-亮度混色盘
@onready var color_rect = $ColorRect
# 颜色选择条选择颜色后HSBRect的色相发生相应变化
func _on_colors_bar_color_changed(color):
hsb_rect.color = color
# HSBRect选择颜色后,在ColorRect显示选择的颜色
func _on_hsb_rect_color_changed(color):
color_rect.color = color
主要是处理ColorsBar
和HSBRect
的color_changed
信号,让ColorsBar
和HSBRect
以及ColorRect
三者联动起来。
运行后的效果:
gif图有点失真: