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.