diff options
author | Lukas Stockner <lukas.stockner@freenet.de> | 2019-05-02 16:45:31 +0300 |
---|---|---|
committer | Brecht Van Lommel <brechtvanlommel@gmail.com> | 2019-05-03 16:42:49 +0300 |
commit | 68b15fc3ad4f74be192150d3a2fb35e7ef2d4edd (patch) | |
tree | 26c2aa7b37c3520417acf13b4a80c3023346b1c2 /intern/cycles/render/image.cpp | |
parent | dba4684f82a179a5c8abdae5940bc7c35e65a1a7 (diff) |
Cycles: support loading images from arbitrary OpenColorIO color space
These are the internal changes to Cycles, for Blender integration there are no
functional changes in this commit.
Images are converted to scene linear color space on file load, and on reading
from the OpenImageIO texture cache. 8-bit images are compressed with the sRGB
transfer function to avoid precision loss while keeping memory usages low. This
also means that for common cases of 8-bit sRGB images no conversion happens at
all on image loading.
Initial patch by Lukas, completed by Brecht.
Differential Revision: https://developer.blender.org/D3491
Diffstat (limited to 'intern/cycles/render/image.cpp')
-rw-r--r-- | intern/cycles/render/image.cpp | 137 |
1 files changed, 95 insertions, 42 deletions
diff --git a/intern/cycles/render/image.cpp b/intern/cycles/render/image.cpp index ae219e912e0..dc75dca2d19 100644 --- a/intern/cycles/render/image.cpp +++ b/intern/cycles/render/image.cpp @@ -14,12 +14,14 @@ * limitations under the License. */ -#include "device/device.h" #include "render/image.h" +#include "device/device.h" +#include "render/colorspace.h" #include "render/scene.h" #include "render/stats.h" #include "util/util_foreach.h" +#include "util/util_image_impl.h" #include "util/util_logging.h" #include "util/util_path.h" #include "util/util_progress.h" @@ -164,11 +166,36 @@ bool ImageManager::get_image_metadata(int flat_slot, ImageMetaData &metadata) return false; } +void ImageManager::metadata_detect_colorspace(ImageMetaData &metadata, const char *file_format) +{ + /* Convert used specified color spaces to one we know how to handle. */ + metadata.colorspace = ColorSpaceManager::detect_known_colorspace( + metadata.colorspace, file_format, metadata.is_float || metadata.is_half); + + if (metadata.colorspace == u_colorspace_raw) { + /* Nothing to do. */ + } + else if (metadata.colorspace == u_colorspace_srgb) { + /* Keep sRGB colorspace stored as sRGB, to save memory and/or loading time + * for the common case of 8bit sRGB images like PNG. */ + metadata.compress_as_srgb = true; + } + else { + /* Always compress non-raw 8bit images as scene linear + sRGB, as a + * heuristic to keep memory usage the same without too much data loss + * due to quantization in common cases. */ + metadata.compress_as_srgb = (metadata.type == IMAGE_DATA_TYPE_BYTE || + metadata.type == IMAGE_DATA_TYPE_BYTE4); + } +} + bool ImageManager::get_image_metadata(const string &filename, void *builtin_data, + ustring colorspace, ImageMetaData &metadata) { memset(&metadata, 0, sizeof(metadata)); + metadata.colorspace = colorspace; if (builtin_data) { if (builtin_image_info_cb) { @@ -179,13 +206,14 @@ bool ImageManager::get_image_metadata(const string &filename, } if (metadata.is_float) { - metadata.is_linear = true; metadata.type = (metadata.channels > 1) ? IMAGE_DATA_TYPE_FLOAT4 : IMAGE_DATA_TYPE_FLOAT; } else { metadata.type = (metadata.channels > 1) ? IMAGE_DATA_TYPE_BYTE4 : IMAGE_DATA_TYPE_BYTE; } + metadata_detect_colorspace(metadata, ""); + return true; } @@ -213,20 +241,19 @@ bool ImageManager::get_image_metadata(const string &filename, metadata.width = spec.width; metadata.height = spec.height; metadata.depth = spec.depth; + metadata.compress_as_srgb = false; /* Check the main format, and channel formats. */ size_t channel_size = spec.format.basesize(); if (spec.format.is_floating_point()) { metadata.is_float = true; - metadata.is_linear = true; } for (size_t channel = 0; channel < spec.channelformats.size(); channel++) { channel_size = max(channel_size, spec.channelformats[channel].basesize()); if (spec.channelformats[channel].is_floating_point()) { metadata.is_float = true; - metadata.is_linear = true; } } @@ -235,21 +262,6 @@ bool ImageManager::get_image_metadata(const string &filename, metadata.is_half = true; } - /* basic color space detection, not great but better than nothing - * before we do OpenColorIO integration */ - if (metadata.is_float) { - string colorspace = spec.get_string_attribute("oiio:ColorSpace"); - - metadata.is_linear = !( - colorspace == "sRGB" || colorspace == "GammaCorrected" || - (colorspace == "" && - (strcmp(in->format_name(), "png") == 0 || strcmp(in->format_name(), "tiff") == 0 || - strcmp(in->format_name(), "dpx") == 0 || strcmp(in->format_name(), "jpeg2000") == 0))); - } - else { - metadata.is_linear = false; - } - /* set type and channels */ metadata.channels = spec.nchannels; @@ -266,6 +278,8 @@ bool ImageManager::get_image_metadata(const string &filename, metadata.type = (metadata.channels > 1) ? IMAGE_DATA_TYPE_BYTE4 : IMAGE_DATA_TYPE_BYTE; } + metadata_detect_colorspace(metadata, in->format_name()); + in->close(); return true; @@ -276,11 +290,12 @@ static bool image_equals(ImageManager::Image *image, void *builtin_data, InterpolationType interpolation, ExtensionType extension, - bool use_alpha) + bool use_alpha, + ustring colorspace) { return image->filename == filename && image->builtin_data == builtin_data && image->interpolation == interpolation && image->extension == extension && - image->use_alpha == use_alpha; + image->use_alpha == use_alpha && image->colorspace == colorspace; } int ImageManager::add_image(const string &filename, @@ -290,12 +305,13 @@ int ImageManager::add_image(const string &filename, InterpolationType interpolation, ExtensionType extension, bool use_alpha, + ustring colorspace, ImageMetaData &metadata) { Image *img; size_t slot; - get_image_metadata(filename, builtin_data, metadata); + get_image_metadata(filename, builtin_data, colorspace, metadata); ImageDataType type = metadata.type; thread_scoped_lock device_lock(device_mutex); @@ -313,7 +329,8 @@ int ImageManager::add_image(const string &filename, /* Fnd existing image. */ for (slot = 0; slot < images[type].size(); slot++) { img = images[type][slot]; - if (img && image_equals(img, filename, builtin_data, interpolation, extension, use_alpha)) { + if (img && image_equals( + img, filename, builtin_data, interpolation, extension, use_alpha, colorspace)) { if (img->frame != frame) { img->frame = frame; img->need_load = true; @@ -322,6 +339,10 @@ int ImageManager::add_image(const string &filename, img->use_alpha = use_alpha; img->need_load = true; } + if (img->colorspace != colorspace) { + img->colorspace = colorspace; + img->need_load = true; + } if (!(img->metadata == metadata)) { img->metadata = metadata; img->need_load = true; @@ -370,6 +391,7 @@ int ImageManager::add_image(const string &filename, img->extension = extension; img->users = 1; img->use_alpha = use_alpha; + img->colorspace = colorspace; img->mem = NULL; images[type][slot] = img; @@ -403,15 +425,20 @@ void ImageManager::remove_image(const string &filename, void *builtin_data, InterpolationType interpolation, ExtensionType extension, - bool use_alpha) + bool use_alpha, + ustring colorspace) { size_t slot; for (int type = 0; type < IMAGE_DATA_NUM_TYPES; type++) { for (slot = 0; slot < images[type].size(); slot++) { - if (images[type][slot] && - image_equals( - images[type][slot], filename, builtin_data, interpolation, extension, use_alpha)) { + if (images[type][slot] && image_equals(images[type][slot], + filename, + builtin_data, + interpolation, + extension, + use_alpha, + colorspace)) { remove_image(type_index_to_flattened_slot(slot, (ImageDataType)type)); return; } @@ -427,13 +454,18 @@ void ImageManager::tag_reload_image(const string &filename, void *builtin_data, InterpolationType interpolation, ExtensionType extension, - bool use_alpha) + bool use_alpha, + ustring colorspace) { for (size_t type = 0; type < IMAGE_DATA_NUM_TYPES; type++) { for (size_t slot = 0; slot < images[type].size(); slot++) { - if (images[type][slot] && - image_equals( - images[type][slot], filename, builtin_data, interpolation, extension, use_alpha)) { + if (images[type][slot] && image_equals(images[type][slot], + filename, + builtin_data, + interpolation, + extension, + use_alpha, + colorspace)) { images[type][slot]->need_load = true; break; } @@ -502,14 +534,16 @@ bool ImageManager::file_load_image(Image *img, int depth = img->metadata.depth; int components = img->metadata.channels; - /* Read RGBA pixels. */ + /* Read pixels. */ vector<StorageType> pixels_storage; StorageType *pixels; const size_t max_size = max(max(width, height), depth); if (max_size == 0) { - /* Don't bother with invalid images. */ + /* Don't bother with empty images. */ return false; } + + /* Allocate memory as needed, may be smaller to resize down. */ if (texture_limit > 0 && max_size > texture_limit) { pixels_storage.resize(((size_t)width) * height * depth * 4); pixels = &pixels_storage[0]; @@ -518,19 +552,23 @@ bool ImageManager::file_load_image(Image *img, thread_scoped_lock device_lock(device_mutex); pixels = (StorageType *)tex_img.alloc(width, height, depth); } + if (pixels == NULL) { /* Could be that we've run out of memory. */ return false; } + bool cmyk = false; const size_t num_pixels = ((size_t)width) * height * depth; if (in) { + /* Read pixels through OpenImageIO. */ StorageType *readpixels = pixels; vector<StorageType> tmppixels; if (components > 4) { tmppixels.resize(((size_t)width) * height * components); readpixels = &tmppixels[0]; } + if (depth <= 1) { size_t scanlinesize = ((size_t)width) * components * sizeof(StorageType); in->read_image(FileFormat, @@ -542,6 +580,7 @@ bool ImageManager::file_load_image(Image *img, else { in->read_image(FileFormat, (uchar *)readpixels); } + if (components > 4) { size_t dimensions = ((size_t)width) * height; for (size_t i = dimensions - 1, pixel = 0; pixel < dimensions; pixel++, i--) { @@ -552,10 +591,12 @@ bool ImageManager::file_load_image(Image *img, } tmppixels.clear(); } + cmyk = strcmp(in->format_name(), "jpeg") == 0 && components == 4; in->close(); } else { + /* Read pixels through callback. */ if (FileFormat == TypeDesc::FLOAT) { builtin_image_float_pixels_cb(img->filename, img->builtin_data, @@ -574,16 +615,17 @@ bool ImageManager::file_load_image(Image *img, /* TODO(dingto): Support half for ImBuf. */ } } - /* Check if we actually have a float4 slot, in case components == 1, - * but device doesn't support single channel textures. - */ + + /* The kernel can handle 1 and 4 channel images. Anything that is not a single + * channel image is converted to RGBA format. */ bool is_rgba = (type == IMAGE_DATA_TYPE_FLOAT4 || type == IMAGE_DATA_TYPE_HALF4 || type == IMAGE_DATA_TYPE_BYTE4 || type == IMAGE_DATA_TYPE_USHORT4); + if (is_rgba) { const StorageType one = util_image_cast_from_float<StorageType>(1.0f); if (cmyk) { - /* CMYK */ + /* CMYK to RGBA. */ for (size_t i = num_pixels - 1, pixel = 0; pixel < num_pixels; pixel++, i--) { float c = util_image_cast_to_float(pixels[i * 4 + 0]); float m = util_image_cast_to_float(pixels[i * 4 + 1]); @@ -596,7 +638,7 @@ bool ImageManager::file_load_image(Image *img, } } else if (components == 2) { - /* grayscale + alpha */ + /* Grayscale + alpha to RGBA. */ for (size_t i = num_pixels - 1, pixel = 0; pixel < num_pixels; pixel++, i--) { pixels[i * 4 + 3] = pixels[i * 2 + 1]; pixels[i * 4 + 2] = pixels[i * 2 + 0]; @@ -605,7 +647,7 @@ bool ImageManager::file_load_image(Image *img, } } else if (components == 3) { - /* RGB */ + /* RGB to RGBA. */ for (size_t i = num_pixels - 1, pixel = 0; pixel < num_pixels; pixel++, i--) { pixels[i * 4 + 3] = one; pixels[i * 4 + 2] = pixels[i * 3 + 2]; @@ -614,7 +656,7 @@ bool ImageManager::file_load_image(Image *img, } } else if (components == 1) { - /* grayscale */ + /* Grayscale to RGBA. */ for (size_t i = num_pixels - 1, pixel = 0; pixel < num_pixels; pixel++, i--) { pixels[i * 4 + 3] = one; pixels[i * 4 + 2] = pixels[i]; @@ -622,18 +664,27 @@ bool ImageManager::file_load_image(Image *img, pixels[i * 4 + 0] = pixels[i]; } } + + /* Disable alpha if requested by the user. */ if (img->use_alpha == false) { for (size_t i = num_pixels - 1, pixel = 0; pixel < num_pixels; pixel++, i--) { pixels[i * 4 + 3] = one; } } + + if (img->metadata.colorspace != u_colorspace_raw && + img->metadata.colorspace != u_colorspace_srgb) { + /* Convert to scene linear. */ + ColorSpaceManager::to_scene_linear( + img->metadata.colorspace, pixels, width, height, depth, img->metadata.compress_as_srgb); + } } + /* Make sure we don't have buggy values. */ if (FileFormat == TypeDesc::FLOAT) { /* For RGBA buffers we put all channels to 0 if either of them is not * finite. This way we avoid possible artifacts caused by fully changed - * hue. - */ + * hue. */ if (is_rgba) { for (size_t i = 0; i < num_pixels; i += 4) { StorageType *pixel = &pixels[i * 4]; @@ -655,6 +706,7 @@ bool ImageManager::file_load_image(Image *img, } } } + /* Scale image down if needed. */ if (pixels_storage.size() > 0) { float scale_factor = 1.0f; @@ -684,6 +736,7 @@ bool ImageManager::file_load_image(Image *img, memcpy(texture_pixels, &scaled_pixels[0], scaled_pixels.size() * sizeof(StorageType)); } + return true; } |