Scale graphical asset from Control nodes

Godot Version

4.3

Question

Hi everyone!
Godot has these native assets that are used in the Control nodes like CheckButton:

I’m working on a theme and I increase the size of the font and the buttons. So, in the case of CheckButton, the graphic assets are too small compared to the button itself:

Small CheckButton

How can I scale the size of the graphic asset to match the button style?

I’m doing that on my current project, though I’m running 4.5 for it. What I did was create several different versions of each asset at various pixel sizes; for square stuff, 12x12, 16x16, 24x24, 32x32, 48x48, 64x64, 96x96, 128x128. I’ve then got a function I call when the font size is set:

func set_font_size(fsize: int) -> void:
    var th: Theme = ThemeDB.get_project_theme()

    th.default_font_size = fsize

    if fsize < 16:
        th.set_icon(&"updown",    &"SpinBox",      load("res://Themes/SpinnerArrows.9x12.png"))
        th.set_icon(&"checked",   &"CheckButton",  load("res://Themes/CheckButtonOn.12.png"))
        th.set_icon(&"unchecked", &"CheckButton",  load("res://Themes/CheckButtonOff.12.png"))
        th.set_icon(&"checked",   &"PopupMenu",    load("res://Themes/CheckButtonOn.12.png"))
        th.set_icon(&"unchecked", &"PopupMenu",    load("res://Themes/CheckButtonOff.12.png"))
        th.set_icon(&"arrow",     &"OptionButton", load("res://Themes/OptionArrow.12.png"))

    elif fsize < 24:
        th.set_icon(&"updown",    &"SpinBox",      load("res://Themes/SpinnerArrows.12x16.png"))
        [...]

    [...]

interesting, but in your case you already have the icons at several different sizes. I was asking if there the option to scale it somehow, but it doesn seem to be the case.

I made the several different icons because I couldn’t find a way to make Godot scale them. :slight_smile:

2 Likes

In Godot 4.5 the DPITexture class was added. It loads an SVG and let you select the scale you want. The default icons in Godot 4.5 are all DPITextures By default they will be read-only so you will need to override them in the theme or make them unique.

If you can’t use a DPITexture (for example, you are using pngs) you can load it as an ImageTexture and use ImageTexture.set_size_override() to override it size.

For example:

extends Node

@onready var check_button: CheckButton = $CheckButton


func _ready() -> void:
	var unchecked = check_button.get_theme_icon(&"unchecked")
	var texture = ImageTexture.create_from_image(unchecked.get_image())
	texture.set_size_override(unchecked.get_size() * 4)
	check_button.add_theme_icon_override(&"unchecked", texture)

It uses RenderingServer.texture_set_size_override() internally which I tried to use directly like: RenderingServer.texture_set_size_override(unchecked.get_rid(), 32, 32) without having to create a new texture but I wasn’t able to get it working. I think it doesn’t work because Controls keep some data in a cached theme object.

I tried writing my own ScalableTexture2D class like:

@tool
class_name ScalableTexture2D extends Texture2D

## A Texture2D that can be scaled

## The base texture
@export var base: Texture2D:
	set(value):
		if not base == value:
			if base and base.changed.is_connected(emit_changed):
				base.changed.disconnect(emit_changed)

			base = value

			if base:
				_scaled_size = Vector2i(base.get_size() * scale)
				RenderingServer.texture_replace(_rid, base.get_rid())
				RenderingServer.texture_set_size_override(_rid, _scaled_size.x, _scaled_size.y)
				base.changed.connect(emit_changed)

			emit_changed()

## The scale of the texture
@export var scale: float = 1.0:
	set(value):
		scale = value
		if base:
				_scaled_size = Vector2i(base.get_size() * scale)
				RenderingServer.texture_set_size_override(_rid, _scaled_size.x, _scaled_size.y)
		emit_changed()


var _rid: RID
var _scaled_size := Vector2i.ONE


func _init() -> void:
	_rid = RenderingServer.texture_2d_placeholder_create()


func _notification(what: int) -> void:
	if what == NOTIFICATION_PREDELETE:
		RenderingServer.free_rid(_rid)


func _draw(to_canvas_item: RID, pos: Vector2, modulate: Color, transpose: bool) -> void:
	RenderingServer.canvas_item_add_texture_rect(to_canvas_item, Rect2(pos, _scaled_size), _rid, false, modulate, transpose)


func _draw_rect(to_canvas_item: RID, rect: Rect2, tile: bool, modulate: Color, transpose: bool) -> void:
	RenderingServer.canvas_item_add_texture_rect(to_canvas_item, rect, _rid, tile, modulate, transpose)


func _draw_rect_region(to_canvas_item: RID, rect: Rect2, src_rect: Rect2, modulate: Color, transpose: bool, clip_uv: bool) -> void:
	RenderingServer.canvas_item_add_texture_rect_region(to_canvas_item, rect, _rid, src_rect, modulate, transpose, clip_uv)


func _get_width() -> int:
	return _scaled_size.x


func _get_height() -> int:
	return _scaled_size.y


func _get_rid() -> RID:
	return _rid


func _has_alpha() -> bool:
	if not base:
		return false
	return base.has_alpha()


func _is_pixel_opaque(x: int, y: int) -> bool:
	# TODO
	return true

It uses the same RenderingServer.texture_set_size_override() internally. I only tried it on a CheckButton icon and It does work but there may be some bugs or errors. I didn’t test it much.

2 Likes

Thanks for the input. I will not update to 4.5 yet, so for now, I think I’ll need to make bigger textures LOL