diff --git a/core/io/image.cpp b/core/io/image.cpp
index 55d138b9024f..6054747da27d 100644
--- a/core/io/image.cpp
+++ b/core/io/image.cpp
@@ -37,6 +37,7 @@
#include "core/io/resource_loader.h"
#include "core/math/math_funcs.h"
#include "core/object/class_db.h"
+#include "core/object/worker_thread_pool.h"
#include "core/templates/hash_map.h"
#include "core/variant/dictionary.h"
@@ -4168,6 +4169,38 @@ bool Image::detect_signed(bool p_include_mips) const {
return false;
}
+_FORCE_INLINE_ uint16_t _srgb2lin_hf(uint16_t p_s) {
+ float sf = Math::half_to_float(p_s);
+ if (sf < 0.04045) {
+ sf /= 12.92f;
+ } else {
+ sf = Math::pow((sf + 0.055f) / 1.055f, 2.4f);
+ }
+ return Math::make_half_float(sf);
+}
+
+void Image::_srgb2lin_hft4(void *p_td, uint32_t p_i) {
+ Image *img = (Image *)p_td;
+ uint16_t *data_ptr = (uint16_t *)img->data.ptrw();
+ for (int x = 0; x < img->width; x++) {
+ int i = img->width * p_i + x;
+ data_ptr[(i << 2) + 0] = _srgb2lin_hf(data_ptr[(i << 2) + 0]);
+ data_ptr[(i << 2) + 1] = _srgb2lin_hf(data_ptr[(i << 2) + 1]);
+ data_ptr[(i << 2) + 2] = _srgb2lin_hf(data_ptr[(i << 2) + 2]);
+ }
+}
+
+void Image::_srgb2lin_hft3(void *p_td, uint32_t p_i) {
+ Image *img = (Image *)p_td;
+ uint16_t *data_ptr = (uint16_t *)img->data.ptrw();
+ for (int x = 0; x < img->width; x++) {
+ int i = img->width * p_i + x;
+ data_ptr[(i * 3) + 0] = _srgb2lin_hf(data_ptr[(i * 3) + 0]);
+ data_ptr[(i * 3) + 1] = _srgb2lin_hf(data_ptr[(i * 3) + 1]);
+ data_ptr[(i * 3) + 2] = _srgb2lin_hf(data_ptr[(i * 3) + 2]);
+ }
+}
+
void Image::srgb_to_linear() {
if (data.is_empty()) {
return;
@@ -4175,9 +4208,15 @@ void Image::srgb_to_linear() {
static const uint8_t srgb2lin[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, 11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 22, 22, 23, 23, 24, 24, 25, 26, 26, 27, 27, 28, 29, 29, 30, 31, 31, 32, 33, 33, 34, 35, 36, 36, 37, 38, 38, 39, 40, 41, 42, 42, 43, 44, 45, 46, 47, 47, 48, 49, 50, 51, 52, 53, 54, 55, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 70, 71, 72, 73, 74, 75, 76, 77, 78, 80, 81, 82, 83, 84, 85, 87, 88, 89, 90, 92, 93, 94, 95, 97, 98, 99, 101, 102, 103, 105, 106, 107, 109, 110, 112, 113, 114, 116, 117, 119, 120, 122, 123, 125, 126, 128, 129, 131, 132, 134, 135, 137, 139, 140, 142, 144, 145, 147, 148, 150, 152, 153, 155, 157, 159, 160, 162, 164, 166, 167, 169, 171, 173, 175, 176, 178, 180, 182, 184, 186, 188, 190, 192, 193, 195, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215, 218, 220, 222, 224, 226, 228, 230, 232, 235, 237, 239, 241, 243, 245, 248, 250, 252, 255 };
- ERR_FAIL_COND(format != FORMAT_RGB8 && format != FORMAT_RGBA8);
+ ERR_FAIL_COND(format != FORMAT_RGB8 && format != FORMAT_RGBA8 && format != FORMAT_RGBH && format != FORMAT_RGBAH);
- if (format == FORMAT_RGBA8) {
+ if (format == FORMAT_RGBAH) {
+ WorkerThreadPool::GroupID group_task = WorkerThreadPool::get_singleton()->add_native_group_task(&Image::_srgb2lin_hft4, this, height, -1, true, String("SRGB2LIN4"));
+ WorkerThreadPool::get_singleton()->wait_for_group_task_completion(group_task);
+ } else if (format == FORMAT_RGBH) {
+ WorkerThreadPool::GroupID group_task = WorkerThreadPool::get_singleton()->add_native_group_task(&Image::_srgb2lin_hft3, this, height, -1, true, String("SRGB2LIN3"));
+ WorkerThreadPool::get_singleton()->wait_for_group_task_completion(group_task);
+ } else if (format == FORMAT_RGBA8) {
int len = data.size() / 4;
uint8_t *data_ptr = data.ptrw();
@@ -4199,6 +4238,38 @@ void Image::srgb_to_linear() {
}
}
+_FORCE_INLINE_ uint16_t _lin2srgb_hf(uint16_t p_l) {
+ float lf = Math::half_to_float(p_l);
+ if (lf <= 0.0031308f) {
+ lf *= 12.92f;
+ } else {
+ lf = 1.055f * Math::pow(lf, 1.0f / 2.4f) - 0.055f;
+ }
+ return Math::make_half_float(lf);
+}
+
+void Image::_lin2srgb_hft4(void *p_td, uint32_t p_i) {
+ Image *img = (Image *)p_td;
+ uint16_t *data_ptr = (uint16_t *)img->data.ptrw();
+ for (int x = 0; x < img->width; x++) {
+ int i = img->width * p_i + x;
+ data_ptr[(p_i << 2) + 0] = _lin2srgb_hf(data_ptr[(i << 2) + 0]);
+ data_ptr[(p_i << 2) + 1] = _lin2srgb_hf(data_ptr[(i << 2) + 1]);
+ data_ptr[(p_i << 2) + 2] = _lin2srgb_hf(data_ptr[(i << 2) + 2]);
+ }
+}
+
+void Image::_lin2srgb_hft3(void *p_td, uint32_t p_i) {
+ Image *img = (Image *)p_td;
+ uint16_t *data_ptr = (uint16_t *)img->data.ptrw();
+ for (int x = 0; x < img->width; x++) {
+ int i = img->width * p_i + x;
+ data_ptr[(i * 3) + 0] = _lin2srgb_hf(data_ptr[(i * 3) + 0]);
+ data_ptr[(i * 3) + 1] = _lin2srgb_hf(data_ptr[(i * 3) + 1]);
+ data_ptr[(i * 3) + 2] = _lin2srgb_hf(data_ptr[(i * 3) + 2]);
+ }
+}
+
void Image::linear_to_srgb() {
if (data.is_empty()) {
return;
@@ -4206,9 +4277,15 @@ void Image::linear_to_srgb() {
static const uint8_t lin2srgb[256] = { 0, 12, 21, 28, 33, 38, 42, 46, 49, 52, 55, 58, 61, 63, 66, 68, 70, 73, 75, 77, 79, 81, 82, 84, 86, 88, 89, 91, 93, 94, 96, 97, 99, 100, 102, 103, 104, 106, 107, 109, 110, 111, 112, 114, 115, 116, 117, 118, 120, 121, 122, 123, 124, 125, 126, 127, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 151, 152, 153, 154, 155, 156, 157, 157, 158, 159, 160, 161, 161, 162, 163, 164, 165, 165, 166, 167, 168, 168, 169, 170, 171, 171, 172, 173, 174, 174, 175, 176, 176, 177, 178, 179, 179, 180, 181, 181, 182, 183, 183, 184, 185, 185, 186, 187, 187, 188, 189, 189, 190, 191, 191, 192, 193, 193, 194, 194, 195, 196, 196, 197, 197, 198, 199, 199, 200, 201, 201, 202, 202, 203, 204, 204, 205, 205, 206, 206, 207, 208, 208, 209, 209, 210, 210, 211, 212, 212, 213, 213, 214, 214, 215, 215, 216, 217, 217, 218, 218, 219, 219, 220, 220, 221, 221, 222, 222, 223, 223, 224, 224, 225, 226, 226, 227, 227, 228, 228, 229, 229, 230, 230, 231, 231, 232, 232, 233, 233, 234, 234, 235, 235, 236, 236, 237, 237, 237, 238, 238, 239, 239, 240, 240, 241, 241, 242, 242, 243, 243, 244, 244, 245, 245, 245, 246, 246, 247, 247, 248, 248, 249, 249, 250, 250, 251, 251, 251, 252, 252, 253, 253, 254, 254, 255 };
- ERR_FAIL_COND(format != FORMAT_RGB8 && format != FORMAT_RGBA8);
+ ERR_FAIL_COND(format != FORMAT_RGB8 && format != FORMAT_RGBA8 && format != FORMAT_RGBH && format != FORMAT_RGBAH);
- if (format == FORMAT_RGBA8) {
+ if (format == FORMAT_RGBAH) {
+ WorkerThreadPool::GroupID group_task = WorkerThreadPool::get_singleton()->add_native_group_task(&Image::_lin2srgb_hft4, this, height, -1, true, String("LIN2SRGB4"));
+ WorkerThreadPool::get_singleton()->wait_for_group_task_completion(group_task);
+ } else if (format == FORMAT_RGBH) {
+ WorkerThreadPool::GroupID group_task = WorkerThreadPool::get_singleton()->add_native_group_task(&Image::_lin2srgb_hft3, this, height, -1, true, String("LIN2SRGB3"));
+ WorkerThreadPool::get_singleton()->wait_for_group_task_completion(group_task);
+ } else if (format == FORMAT_RGBA8) {
int len = data.size() / 4;
uint8_t *data_ptr = data.ptrw();
diff --git a/core/io/image.h b/core/io/image.h
index 30d64e300fd0..66c3ff2898be 100644
--- a/core/io/image.h
+++ b/core/io/image.h
@@ -195,6 +195,11 @@ class Image : public Resource {
float rdo_quality_loss = 0;
};
+ static void _srgb2lin_hft4(void *p_td, uint32_t p_i);
+ static void _srgb2lin_hft3(void *p_td, uint32_t p_i);
+ static void _lin2srgb_hft4(void *p_td, uint32_t p_i);
+ static void _lin2srgb_hft3(void *p_td, uint32_t p_i);
+
// External saver function pointers.
static inline SavePNGFunc save_png_func = nullptr;
diff --git a/doc/classes/Image.xml b/doc/classes/Image.xml
index fed764b8cd43..4a2d064e68bc 100644
--- a/doc/classes/Image.xml
+++ b/doc/classes/Image.xml
@@ -327,7 +327,7 @@
- Converts the entire image from linear encoding to nonlinear sRGB encoding by using a lookup table. Only works on images with [constant FORMAT_RGB8] or [constant FORMAT_RGBA8] formats.
+ Converts the entire image from linear encoding to nonlinear sRGB encoding by using a lookup table. Only works on images with [constant FORMAT_RGB8], [constant FORMAT_RGBA8], [constant FORMAT_RGBH], or [constant FORMAT_RGBAH] formats.
@@ -636,7 +636,7 @@
- Converts the raw data from nonlinear sRGB encoding to linear encoding using a lookup table. Only works on images with [constant FORMAT_RGB8] or [constant FORMAT_RGBA8] formats.
+ Converts the raw data from nonlinear sRGB encoding to linear encoding using a lookup table. Only works on images with [constant FORMAT_RGB8], [constant FORMAT_RGBA8], [constant FORMAT_RGBH], and [constant FORMAT_RGBAH] formats.
[b]Note:[/b] The 8-bit formats required by this method are not suitable for storing linearly encoded values; a significant amount of color information will be lost in darker values. To maintain image quality, this method should not be used.
diff --git a/doc/classes/ViewportTexture.xml b/doc/classes/ViewportTexture.xml
index 135b13755413..9eb247767a4d 100644
--- a/doc/classes/ViewportTexture.xml
+++ b/doc/classes/ViewportTexture.xml
@@ -10,8 +10,8 @@
[b]Note:[/b] Instantiating scenes containing a high-resolution [ViewportTexture] may cause noticeable stutter.
[b]Note:[/b] When using a [Viewport] with [member Viewport.use_hdr_2d] set to [code]true[/code], the returned texture will be an HDR image that uses linear encoding. This may look darker than normal when displayed directly on screen. To convert to nonlinear sRGB encoding, you can do the following:
[codeblock]
- img.convert(Image.FORMAT_RGBA8)
img.linear_to_srgb()
+ img.convert(Image.FORMAT_RGBA8)
[/codeblock]
[b]Note:[/b] Some nodes such as [Decal], [Light3D], and [PointLight2D] do not support using [ViewportTexture] directly. To use texture data from a [ViewportTexture] in these nodes, you need to create an [ImageTexture] by calling [method Texture2D.get_image] on the [ViewportTexture] and passing the result to [method ImageTexture.create_from_image]. This conversion is a slow operation, so it should not be performed every frame.
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index 48e081760c8d..fc351b32ff4a 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -4021,13 +4021,13 @@ void EditorNode::_save_screenshot_with_embedded_process(int64_t p_w, int64_t p_h
ERR_FAIL_COND_MSG(texture.is_null(), "Cannot get a viewport texture from the editor main screen.");
Ref img = texture->get_image();
ERR_FAIL_COND_MSG(img.is_null(), "Cannot get an image from a viewport texture of the editor main screen.");
- img->convert(Image::FORMAT_RGBA8);
#ifdef RD_ENABLED
RenderingDevice *rendering_device = RD::get_singleton();
if (rendering_device && RenderingServer::get_singleton()->viewport_is_using_hdr_2d(viewport->get_viewport_rid())) {
img->linear_to_srgb();
}
#endif
+ img->convert(Image::FORMAT_RGBA8);
ERR_FAIL_COND(p_emb_path.is_empty());
Ref overlay = Image::load_from_file(p_emb_path);
DirAccess::remove_absolute(p_emb_path);
@@ -4052,13 +4052,13 @@ void EditorNode::_save_screenshot(const String &p_path) {
ERR_FAIL_COND_MSG(texture.is_null(), "Cannot get a viewport texture from the editor main screen.");
Ref img = texture->get_image();
ERR_FAIL_COND_MSG(img.is_null(), "Cannot get an image from a viewport texture of the editor main screen.");
- img->convert(Image::FORMAT_RGBA8);
#ifdef RD_ENABLED
RenderingDevice *rendering_device = RD::get_singleton();
if (rendering_device && RenderingServer::get_singleton()->viewport_is_using_hdr_2d(viewport->get_viewport_rid())) {
img->linear_to_srgb();
}
#endif
+ img->convert(Image::FORMAT_RGBA8);
Error error = img->save_png(p_path);
ERR_FAIL_COND_MSG(error != OK, "Cannot save screenshot to file '" + p_path + "'.");
diff --git a/scene/debugger/scene_debugger.cpp b/scene/debugger/scene_debugger.cpp
index bf3551609b91..0cbca0a29e2d 100644
--- a/scene/debugger/scene_debugger.cpp
+++ b/scene/debugger/scene_debugger.cpp
@@ -550,13 +550,13 @@ Error SceneDebugger::_msg_rq_screenshot(const Array &p_args) {
}
suffix_i += 1;
}
- img->convert(Image::FORMAT_RGBA8);
#ifdef RD_ENABLED
RenderingDevice *rendering_device = RD::get_singleton();
if (rendering_device && RenderingServer::get_singleton()->viewport_is_using_hdr_2d(viewport->get_viewport_rid())) {
img->linear_to_srgb();
}
#endif
+ img->convert(Image::FORMAT_RGBA8);
img->save_png(path);
Array arr;
diff --git a/servers/movie_writer/movie_writer.cpp b/servers/movie_writer/movie_writer.cpp
index ba5e101347b8..d936796a0754 100644
--- a/servers/movie_writer/movie_writer.cpp
+++ b/servers/movie_writer/movie_writer.cpp
@@ -232,8 +232,8 @@ void MovieWriter::add_frame() {
}
if (RenderingServer::get_singleton()->viewport_is_using_hdr_2d(main_vp_rid)) {
- vp_tex->convert(Image::FORMAT_RGBA8);
vp_tex->linear_to_srgb();
+ vp_tex->convert(Image::FORMAT_RGBA8);
}
RenderingServer::get_singleton()->viewport_set_measure_render_time(main_vp_rid, true);