diff options
author | Brecht Van Lommel <brecht@blender.org> | 2022-01-21 20:57:00 +0300 |
---|---|---|
committer | Brecht Van Lommel <brecht@blender.org> | 2022-06-02 19:04:38 +0300 |
commit | 33f5e8f2391944acf8c6cc56bcc352bc10af016c (patch) | |
tree | dc18c07bdd6d7e257b92250cac1293f693be0325 /intern/cycles/blender/image.cpp | |
parent | 10488d54d9836b04f36d95269552bd449dd97e7e (diff) |
Cycles: load 8 bit image textures as half float for some color spaces
For non-raw, non-sRGB color spaces, always use half float even if that uses
more memory. Otherwise the precision loss from conversion to scene linear or
sRGB (as natively understood by the texture sampling) can be too much.
This also required a change to do alpha association ourselves instead of OIIO,
because in OIIO alpha multiplication happens before conversion to half float
and that gives too much precision loss.
Ref T68926
Diffstat (limited to 'intern/cycles/blender/image.cpp')
-rw-r--r-- | intern/cycles/blender/image.cpp | 134 |
1 files changed, 95 insertions, 39 deletions
diff --git a/intern/cycles/blender/image.cpp b/intern/cycles/blender/image.cpp index e802fd39335..535718150b3 100644 --- a/intern/cycles/blender/image.cpp +++ b/intern/cycles/blender/image.cpp @@ -7,6 +7,8 @@ #include "blender/session.h" #include "blender/util.h" +#include "util/half.h" + CCL_NAMESPACE_BEGIN /* Packed Images */ @@ -62,80 +64,134 @@ bool BlenderImageLoader::load_metadata(const ImageDeviceFeatures &, ImageMetaDat } bool BlenderImageLoader::load_pixels(const ImageMetaData &metadata, - void *pixels, - const size_t pixels_size, + void *out_pixels, + const size_t out_pixels_size, const bool associate_alpha) { const size_t num_pixels = ((size_t)metadata.width) * metadata.height; const int channels = metadata.channels; - if (b_image.is_float()) { - /* image data */ - float *image_pixels; - image_pixels = image_get_float_pixels_for_frame(b_image, frame, tile_number); + if (metadata.type == IMAGE_DATA_TYPE_FLOAT || metadata.type == IMAGE_DATA_TYPE_FLOAT4) { + /* Float. */ + float *in_pixels = image_get_float_pixels_for_frame(b_image, frame, tile_number); - if (image_pixels && num_pixels * channels == pixels_size) { - memcpy(pixels, image_pixels, pixels_size * sizeof(float)); + if (in_pixels && num_pixels * channels == out_pixels_size) { + /* Straight copy pixel data. */ + memcpy(out_pixels, in_pixels, out_pixels_size * sizeof(float)); } else { + /* Missing or invalid pixel data. */ if (channels == 1) { - memset(pixels, 0, num_pixels * sizeof(float)); + memset(out_pixels, 0, num_pixels * sizeof(float)); } else { - const size_t num_pixels_safe = pixels_size / channels; - float *fp = (float *)pixels; - for (int i = 0; i < num_pixels_safe; i++, fp += channels) { - fp[0] = 1.0f; - fp[1] = 0.0f; - fp[2] = 1.0f; + const size_t num_pixels_safe = out_pixels_size / channels; + float *out_pixel = (float *)out_pixels; + for (int i = 0; i < num_pixels_safe; i++, out_pixel += channels) { + out_pixel[0] = 1.0f; + out_pixel[1] = 0.0f; + out_pixel[2] = 1.0f; if (channels == 4) { - fp[3] = 1.0f; + out_pixel[3] = 1.0f; } } } } - if (image_pixels) { - MEM_freeN(image_pixels); + if (in_pixels) { + MEM_freeN(in_pixels); } } - else { - unsigned char *image_pixels = image_get_pixels_for_frame(b_image, frame, tile_number); + else if (metadata.type == IMAGE_DATA_TYPE_HALF || metadata.type == IMAGE_DATA_TYPE_HALF4) { + /* Half float. Blender does not have a half type, but in some cases + * we upsample byte to half to avoid precision loss for colorspace + * conversion. */ + unsigned char *in_pixels = image_get_pixels_for_frame(b_image, frame, tile_number); - if (image_pixels && num_pixels * channels == pixels_size) { - memcpy(pixels, image_pixels, pixels_size * sizeof(unsigned char)); + if (in_pixels && num_pixels * channels == out_pixels_size) { + /* Convert uchar to half. */ + const uchar *in_pixel = in_pixels; + half *out_pixel = (half *)out_pixels; + if (associate_alpha && channels == 4) { + for (size_t i = 0; i < num_pixels; i++, in_pixel += 4, out_pixel += 4) { + const float alpha = util_image_cast_to_float(in_pixel[3]); + out_pixel[0] = float_to_half_image(util_image_cast_to_float(in_pixel[0]) * alpha); + out_pixel[1] = float_to_half_image(util_image_cast_to_float(in_pixel[1]) * alpha); + out_pixel[2] = float_to_half_image(util_image_cast_to_float(in_pixel[2]) * alpha); + out_pixel[3] = float_to_half_image(alpha); + } + } + else { + for (size_t i = 0; i < num_pixels; i++) { + for (int c = 0; c < channels; c++, in_pixel++, out_pixel++) { + *out_pixel = float_to_half_image(util_image_cast_to_float(*in_pixel)); + } + } + } } else { + /* Missing or invalid pixel data. */ if (channels == 1) { - memset(pixels, 0, pixels_size * sizeof(unsigned char)); + memset(out_pixels, 0, num_pixels * sizeof(half)); } else { - const size_t num_pixels_safe = pixels_size / channels; - unsigned char *cp = (unsigned char *)pixels; - for (size_t i = 0; i < num_pixels_safe; i++, cp += channels) { - cp[0] = 255; - cp[1] = 0; - cp[2] = 255; + const size_t num_pixels_safe = out_pixels_size / channels; + half *out_pixel = (half *)out_pixels; + for (int i = 0; i < num_pixels_safe; i++, out_pixel += channels) { + out_pixel[0] = float_to_half_image(1.0f); + out_pixel[1] = float_to_half_image(0.0f); + out_pixel[2] = float_to_half_image(1.0f); if (channels == 4) { - cp[3] = 255; + out_pixel[3] = float_to_half_image(1.0f); } } } } - if (image_pixels) { - MEM_freeN(image_pixels); + if (in_pixels) { + MEM_freeN(in_pixels); } + } + else { + /* Byte. */ + unsigned char *in_pixels = image_get_pixels_for_frame(b_image, frame, tile_number); + + if (in_pixels && num_pixels * channels == out_pixels_size) { + /* Straight copy pixel data. */ + memcpy(out_pixels, in_pixels, out_pixels_size * sizeof(unsigned char)); - if (associate_alpha) { - /* Premultiply, byte images are always straight for Blender. */ - unsigned char *cp = (unsigned char *)pixels; - for (size_t i = 0; i < num_pixels; i++, cp += channels) { - cp[0] = (cp[0] * cp[3]) / 255; - cp[1] = (cp[1] * cp[3]) / 255; - cp[2] = (cp[2] * cp[3]) / 255; + if (associate_alpha && channels == 4) { + /* Premultiply, byte images are always straight for Blender. */ + unsigned char *out_pixel = (unsigned char *)out_pixels; + for (size_t i = 0; i < num_pixels; i++, out_pixel += 4) { + out_pixel[0] = (out_pixel[0] * out_pixel[3]) / 255; + out_pixel[1] = (out_pixel[1] * out_pixel[3]) / 255; + out_pixel[2] = (out_pixel[2] * out_pixel[3]) / 255; + } + } + } + else { + /* Missing or invalid pixel data. */ + if (channels == 1) { + memset(out_pixels, 0, out_pixels_size * sizeof(unsigned char)); + } + else { + const size_t num_pixels_safe = out_pixels_size / channels; + unsigned char *out_pixel = (unsigned char *)out_pixels; + for (size_t i = 0; i < num_pixels_safe; i++, out_pixel += channels) { + out_pixel[0] = 255; + out_pixel[1] = 0; + out_pixel[2] = 255; + if (channels == 4) { + out_pixel[3] = 255; + } + } } } + + if (in_pixels) { + MEM_freeN(in_pixels); + } } /* Free image buffers to save memory during render. */ |