1/**************************************************************************/
2/* image_texture.cpp */
3/**************************************************************************/
4/* This file is part of: */
5/* GODOT ENGINE */
6/* https://godotengine.org */
7/**************************************************************************/
8/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10/* */
11/* Permission is hereby granted, free of charge, to any person obtaining */
12/* a copy of this software and associated documentation files (the */
13/* "Software"), to deal in the Software without restriction, including */
14/* without limitation the rights to use, copy, modify, merge, publish, */
15/* distribute, sublicense, and/or sell copies of the Software, and to */
16/* permit persons to whom the Software is furnished to do so, subject to */
17/* the following conditions: */
18/* */
19/* The above copyright notice and this permission notice shall be */
20/* included in all copies or substantial portions of the Software. */
21/* */
22/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29/**************************************************************************/
30
31#include "image_texture.h"
32
33#include "core/io/image_loader.h"
34#include "scene/resources/bit_map.h"
35#include "scene/resources/placeholder_textures.h"
36
37void ImageTexture::reload_from_file() {
38 String path = ResourceLoader::path_remap(get_path());
39 if (!path.is_resource_file()) {
40 return;
41 }
42
43 Ref<Image> img;
44 img.instantiate();
45
46 if (ImageLoader::load_image(path, img) == OK) {
47 set_image(img);
48 } else {
49 Resource::reload_from_file();
50 notify_property_list_changed();
51 emit_changed();
52 }
53}
54
55bool ImageTexture::_set(const StringName &p_name, const Variant &p_value) {
56 if (p_name == "image") {
57 set_image(p_value);
58 return true;
59 }
60 return false;
61}
62
63bool ImageTexture::_get(const StringName &p_name, Variant &r_ret) const {
64 if (p_name == "image") {
65 r_ret = get_image();
66 return true;
67 }
68 return false;
69}
70
71void ImageTexture::_get_property_list(List<PropertyInfo> *p_list) const {
72 p_list->push_back(PropertyInfo(Variant::OBJECT, PNAME("image"), PROPERTY_HINT_RESOURCE_TYPE, "Image", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_RESOURCE_NOT_PERSISTENT));
73}
74
75Ref<ImageTexture> ImageTexture::create_from_image(const Ref<Image> &p_image) {
76 ERR_FAIL_COND_V_MSG(p_image.is_null(), Ref<ImageTexture>(), "Invalid image: null");
77 ERR_FAIL_COND_V_MSG(p_image->is_empty(), Ref<ImageTexture>(), "Invalid image: image is empty");
78
79 Ref<ImageTexture> image_texture;
80 image_texture.instantiate();
81 image_texture->set_image(p_image);
82 return image_texture;
83}
84
85void ImageTexture::set_image(const Ref<Image> &p_image) {
86 ERR_FAIL_COND_MSG(p_image.is_null() || p_image->is_empty(), "Invalid image");
87 w = p_image->get_width();
88 h = p_image->get_height();
89 format = p_image->get_format();
90 mipmaps = p_image->has_mipmaps();
91
92 if (texture.is_null()) {
93 texture = RenderingServer::get_singleton()->texture_2d_create(p_image);
94 } else {
95 RID new_texture = RenderingServer::get_singleton()->texture_2d_create(p_image);
96 RenderingServer::get_singleton()->texture_replace(texture, new_texture);
97 }
98 notify_property_list_changed();
99 emit_changed();
100
101 image_stored = true;
102}
103
104Image::Format ImageTexture::get_format() const {
105 return format;
106}
107
108void ImageTexture::update(const Ref<Image> &p_image) {
109 ERR_FAIL_COND_MSG(p_image.is_null(), "Invalid image");
110 ERR_FAIL_COND_MSG(texture.is_null(), "Texture is not initialized.");
111 ERR_FAIL_COND_MSG(p_image->get_width() != w || p_image->get_height() != h,
112 "The new image dimensions must match the texture size.");
113 ERR_FAIL_COND_MSG(p_image->get_format() != format,
114 "The new image format must match the texture's image format.");
115 ERR_FAIL_COND_MSG(mipmaps != p_image->has_mipmaps(),
116 "The new image mipmaps configuration must match the texture's image mipmaps configuration");
117
118 RS::get_singleton()->texture_2d_update(texture, p_image);
119
120 notify_property_list_changed();
121 emit_changed();
122
123 alpha_cache.unref();
124 image_stored = true;
125}
126
127Ref<Image> ImageTexture::get_image() const {
128 if (image_stored) {
129 return RenderingServer::get_singleton()->texture_2d_get(texture);
130 } else {
131 return Ref<Image>();
132 }
133}
134
135int ImageTexture::get_width() const {
136 return w;
137}
138
139int ImageTexture::get_height() const {
140 return h;
141}
142
143RID ImageTexture::get_rid() const {
144 if (texture.is_null()) {
145 // We are in trouble, create something temporary.
146 texture = RenderingServer::get_singleton()->texture_2d_placeholder_create();
147 }
148 return texture;
149}
150
151bool ImageTexture::has_alpha() const {
152 return (format == Image::FORMAT_LA8 || format == Image::FORMAT_RGBA8);
153}
154
155void ImageTexture::draw(RID p_canvas_item, const Point2 &p_pos, const Color &p_modulate, bool p_transpose) const {
156 if ((w | h) == 0) {
157 return;
158 }
159 RenderingServer::get_singleton()->canvas_item_add_texture_rect(p_canvas_item, Rect2(p_pos, Size2(w, h)), texture, false, p_modulate, p_transpose);
160}
161
162void ImageTexture::draw_rect(RID p_canvas_item, const Rect2 &p_rect, bool p_tile, const Color &p_modulate, bool p_transpose) const {
163 if ((w | h) == 0) {
164 return;
165 }
166 RenderingServer::get_singleton()->canvas_item_add_texture_rect(p_canvas_item, p_rect, texture, p_tile, p_modulate, p_transpose);
167}
168
169void ImageTexture::draw_rect_region(RID p_canvas_item, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate, bool p_transpose, bool p_clip_uv) const {
170 if ((w | h) == 0) {
171 return;
172 }
173 RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas_item, p_rect, texture, p_src_rect, p_modulate, p_transpose, p_clip_uv);
174}
175
176bool ImageTexture::is_pixel_opaque(int p_x, int p_y) const {
177 if (!alpha_cache.is_valid()) {
178 Ref<Image> img = get_image();
179 if (img.is_valid()) {
180 if (img->is_compressed()) { //must decompress, if compressed
181 Ref<Image> decom = img->duplicate();
182 decom->decompress();
183 img = decom;
184 }
185 alpha_cache.instantiate();
186 alpha_cache->create_from_image_alpha(img);
187 }
188 }
189
190 if (alpha_cache.is_valid()) {
191 int aw = int(alpha_cache->get_size().width);
192 int ah = int(alpha_cache->get_size().height);
193 if (aw == 0 || ah == 0) {
194 return true;
195 }
196
197 int x = p_x * aw / w;
198 int y = p_y * ah / h;
199
200 x = CLAMP(x, 0, aw);
201 y = CLAMP(y, 0, ah);
202
203 return alpha_cache->get_bit(x, y);
204 }
205
206 return true;
207}
208
209void ImageTexture::set_size_override(const Size2i &p_size) {
210 Size2i s = p_size;
211 if (s.x != 0) {
212 w = s.x;
213 }
214 if (s.y != 0) {
215 h = s.y;
216 }
217 RenderingServer::get_singleton()->texture_set_size_override(texture, w, h);
218}
219
220void ImageTexture::set_path(const String &p_path, bool p_take_over) {
221 if (texture.is_valid()) {
222 RenderingServer::get_singleton()->texture_set_path(texture, p_path);
223 }
224
225 Resource::set_path(p_path, p_take_over);
226}
227
228void ImageTexture::_bind_methods() {
229 ClassDB::bind_static_method("ImageTexture", D_METHOD("create_from_image", "image"), &ImageTexture::create_from_image);
230 ClassDB::bind_method(D_METHOD("get_format"), &ImageTexture::get_format);
231
232 ClassDB::bind_method(D_METHOD("set_image", "image"), &ImageTexture::set_image);
233 ClassDB::bind_method(D_METHOD("update", "image"), &ImageTexture::update);
234 ClassDB::bind_method(D_METHOD("set_size_override", "size"), &ImageTexture::set_size_override);
235}
236
237ImageTexture::ImageTexture() {}
238
239ImageTexture::~ImageTexture() {
240 if (texture.is_valid()) {
241 ERR_FAIL_NULL(RenderingServer::get_singleton());
242 RenderingServer::get_singleton()->free(texture);
243 }
244}
245
246Image::Format ImageTextureLayered::get_format() const {
247 return format;
248}
249
250int ImageTextureLayered::get_width() const {
251 return width;
252}
253
254int ImageTextureLayered::get_height() const {
255 return height;
256}
257
258int ImageTextureLayered::get_layers() const {
259 return layers;
260}
261
262bool ImageTextureLayered::has_mipmaps() const {
263 return mipmaps;
264}
265
266ImageTextureLayered::LayeredType ImageTextureLayered::get_layered_type() const {
267 return layered_type;
268}
269
270Error ImageTextureLayered::_create_from_images(const TypedArray<Image> &p_images) {
271 Vector<Ref<Image>> images;
272 for (int i = 0; i < p_images.size(); i++) {
273 Ref<Image> img = p_images[i];
274 ERR_FAIL_COND_V(img.is_null(), ERR_INVALID_PARAMETER);
275 images.push_back(img);
276 }
277
278 return create_from_images(images);
279}
280
281TypedArray<Image> ImageTextureLayered::_get_images() const {
282 TypedArray<Image> images;
283 for (int i = 0; i < layers; i++) {
284 images.push_back(get_layer_data(i));
285 }
286 return images;
287}
288
289void ImageTextureLayered::_set_images(const TypedArray<Image> &p_images) {
290 ERR_FAIL_COND(_create_from_images(p_images) != OK);
291}
292
293Error ImageTextureLayered::create_from_images(Vector<Ref<Image>> p_images) {
294 int new_layers = p_images.size();
295 ERR_FAIL_COND_V(new_layers == 0, ERR_INVALID_PARAMETER);
296 if (layered_type == LAYERED_TYPE_CUBEMAP) {
297 ERR_FAIL_COND_V_MSG(new_layers != 6, ERR_INVALID_PARAMETER,
298 "Cubemaps require exactly 6 layers");
299 } else if (layered_type == LAYERED_TYPE_CUBEMAP_ARRAY) {
300 ERR_FAIL_COND_V_MSG((new_layers % 6) != 0, ERR_INVALID_PARAMETER,
301 "Cubemap array layers must be a multiple of 6");
302 }
303
304 ERR_FAIL_COND_V(p_images[0].is_null() || p_images[0]->is_empty(), ERR_INVALID_PARAMETER);
305
306 Image::Format new_format = p_images[0]->get_format();
307 int new_width = p_images[0]->get_width();
308 int new_height = p_images[0]->get_height();
309 bool new_mipmaps = p_images[0]->has_mipmaps();
310
311 for (int i = 1; i < p_images.size(); i++) {
312 ERR_FAIL_COND_V_MSG(p_images[i]->get_format() != new_format, ERR_INVALID_PARAMETER,
313 "All images must share the same format");
314 ERR_FAIL_COND_V_MSG(p_images[i]->get_width() != new_width || p_images[i]->get_height() != new_height, ERR_INVALID_PARAMETER,
315 "All images must share the same dimensions");
316 ERR_FAIL_COND_V_MSG(p_images[i]->has_mipmaps() != new_mipmaps, ERR_INVALID_PARAMETER,
317 "All images must share the usage of mipmaps");
318 }
319
320 if (texture.is_valid()) {
321 RID new_texture = RS::get_singleton()->texture_2d_layered_create(p_images, RS::TextureLayeredType(layered_type));
322 ERR_FAIL_COND_V(!new_texture.is_valid(), ERR_CANT_CREATE);
323 RS::get_singleton()->texture_replace(texture, new_texture);
324 } else {
325 texture = RS::get_singleton()->texture_2d_layered_create(p_images, RS::TextureLayeredType(layered_type));
326 ERR_FAIL_COND_V(!texture.is_valid(), ERR_CANT_CREATE);
327 }
328
329 format = new_format;
330 width = new_width;
331 height = new_height;
332 layers = new_layers;
333 mipmaps = new_mipmaps;
334 return OK;
335}
336
337void ImageTextureLayered::update_layer(const Ref<Image> &p_image, int p_layer) {
338 ERR_FAIL_COND_MSG(texture.is_null(), "Texture is not initialized.");
339 ERR_FAIL_COND_MSG(p_image.is_null(), "Invalid image.");
340 ERR_FAIL_COND_MSG(p_image->get_format() != format, "Image format must match texture's image format.");
341 ERR_FAIL_COND_MSG(p_image->get_width() != width || p_image->get_height() != height, "Image size must match texture's image size.");
342 ERR_FAIL_COND_MSG(p_image->has_mipmaps() != mipmaps, "Image mipmap configuration must match texture's image mipmap configuration.");
343 ERR_FAIL_INDEX_MSG(p_layer, layers, "Layer index is out of bounds.");
344 RS::get_singleton()->texture_2d_update(texture, p_image, p_layer);
345}
346
347Ref<Image> ImageTextureLayered::get_layer_data(int p_layer) const {
348 ERR_FAIL_INDEX_V(p_layer, layers, Ref<Image>());
349 return RS::get_singleton()->texture_2d_layer_get(texture, p_layer);
350}
351
352RID ImageTextureLayered::get_rid() const {
353 if (texture.is_null()) {
354 texture = RS::get_singleton()->texture_2d_layered_placeholder_create(RS::TextureLayeredType(layered_type));
355 }
356 return texture;
357}
358
359void ImageTextureLayered::set_path(const String &p_path, bool p_take_over) {
360 if (texture.is_valid()) {
361 RS::get_singleton()->texture_set_path(texture, p_path);
362 }
363
364 Resource::set_path(p_path, p_take_over);
365}
366
367void ImageTextureLayered::_bind_methods() {
368 ClassDB::bind_method(D_METHOD("create_from_images", "images"), &ImageTextureLayered::_create_from_images);
369 ClassDB::bind_method(D_METHOD("update_layer", "image", "layer"), &ImageTextureLayered::update_layer);
370
371 ClassDB::bind_method(D_METHOD("_get_images"), &ImageTextureLayered::_get_images);
372 ClassDB::bind_method(D_METHOD("_set_images", "images"), &ImageTextureLayered::_set_images);
373
374 ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "_images", PROPERTY_HINT_ARRAY_TYPE, "Image", PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_RESOURCE_NOT_PERSISTENT), "_set_images", "_get_images");
375}
376
377ImageTextureLayered::ImageTextureLayered(LayeredType p_layered_type) {
378 layered_type = p_layered_type;
379}
380
381ImageTextureLayered::~ImageTextureLayered() {
382 if (texture.is_valid()) {
383 ERR_FAIL_NULL(RenderingServer::get_singleton());
384 RS::get_singleton()->free(texture);
385 }
386}
387
388Image::Format ImageTexture3D::get_format() const {
389 return format;
390}
391int ImageTexture3D::get_width() const {
392 return width;
393}
394int ImageTexture3D::get_height() const {
395 return height;
396}
397int ImageTexture3D::get_depth() const {
398 return depth;
399}
400bool ImageTexture3D::has_mipmaps() const {
401 return mipmaps;
402}
403
404Error ImageTexture3D::_create(Image::Format p_format, int p_width, int p_height, int p_depth, bool p_mipmaps, const TypedArray<Image> &p_data) {
405 Vector<Ref<Image>> images;
406 images.resize(p_data.size());
407 for (int i = 0; i < images.size(); i++) {
408 images.write[i] = p_data[i];
409 }
410 return create(p_format, p_width, p_height, p_depth, p_mipmaps, images);
411}
412
413void ImageTexture3D::_update(const TypedArray<Image> &p_data) {
414 Vector<Ref<Image>> images;
415 images.resize(p_data.size());
416 for (int i = 0; i < images.size(); i++) {
417 images.write[i] = p_data[i];
418 }
419 return update(images);
420}
421
422Error ImageTexture3D::create(Image::Format p_format, int p_width, int p_height, int p_depth, bool p_mipmaps, const Vector<Ref<Image>> &p_data) {
423 RID tex = RenderingServer::get_singleton()->texture_3d_create(p_format, p_width, p_height, p_depth, p_mipmaps, p_data);
424 ERR_FAIL_COND_V(tex.is_null(), ERR_CANT_CREATE);
425
426 if (texture.is_valid()) {
427 RenderingServer::get_singleton()->texture_replace(texture, tex);
428 } else {
429 texture = tex;
430 }
431
432 format = p_format;
433 width = p_width;
434 height = p_height;
435 depth = p_depth;
436 mipmaps = p_mipmaps;
437
438 return OK;
439}
440
441void ImageTexture3D::update(const Vector<Ref<Image>> &p_data) {
442 ERR_FAIL_COND(!texture.is_valid());
443 RenderingServer::get_singleton()->texture_3d_update(texture, p_data);
444}
445
446Vector<Ref<Image>> ImageTexture3D::get_data() const {
447 ERR_FAIL_COND_V(!texture.is_valid(), Vector<Ref<Image>>());
448 return RS::get_singleton()->texture_3d_get(texture);
449}
450
451RID ImageTexture3D::get_rid() const {
452 if (!texture.is_valid()) {
453 texture = RS::get_singleton()->texture_3d_placeholder_create();
454 }
455 return texture;
456}
457void ImageTexture3D::set_path(const String &p_path, bool p_take_over) {
458 if (texture.is_valid()) {
459 RenderingServer::get_singleton()->texture_set_path(texture, p_path);
460 }
461
462 Resource::set_path(p_path, p_take_over);
463}
464
465void ImageTexture3D::_bind_methods() {
466 ClassDB::bind_method(D_METHOD("create", "format", "width", "height", "depth", "use_mipmaps", "data"), &ImageTexture3D::_create);
467 ClassDB::bind_method(D_METHOD("update", "data"), &ImageTexture3D::_update);
468}
469
470ImageTexture3D::ImageTexture3D() {
471}
472
473ImageTexture3D::~ImageTexture3D() {
474 if (texture.is_valid()) {
475 ERR_FAIL_NULL(RenderingServer::get_singleton());
476 RS::get_singleton()->free(texture);
477 }
478}
479
480void Texture2DArray::_bind_methods() {
481 ClassDB::bind_method(D_METHOD("create_placeholder"), &Texture2DArray::create_placeholder);
482}
483
484Ref<Resource> Texture2DArray::create_placeholder() const {
485 Ref<PlaceholderTexture2DArray> placeholder;
486 placeholder.instantiate();
487 placeholder->set_size(Size2i(get_width(), get_height()));
488 placeholder->set_layers(get_layers());
489 return placeholder;
490}
491
492void Cubemap::_bind_methods() {
493 ClassDB::bind_method(D_METHOD("create_placeholder"), &Cubemap::create_placeholder);
494}
495
496Ref<Resource> Cubemap::create_placeholder() const {
497 Ref<PlaceholderCubemap> placeholder;
498 placeholder.instantiate();
499 placeholder->set_size(Size2i(get_width(), get_height()));
500 placeholder->set_layers(get_layers());
501 return placeholder;
502}
503
504void CubemapArray::_bind_methods() {
505 ClassDB::bind_method(D_METHOD("create_placeholder"), &CubemapArray::create_placeholder);
506}
507
508Ref<Resource> CubemapArray::create_placeholder() const {
509 Ref<PlaceholderCubemapArray> placeholder;
510 placeholder.instantiate();
511 placeholder->set_size(Size2i(get_width(), get_height()));
512 placeholder->set_layers(get_layers());
513 return placeholder;
514}
515