Cycles: support loading images from arbitrary OpenColorIO color space
authorLukas Stockner <lukas.stockner@freenet.de>
Thu, 2 May 2019 13:45:31 +0000 (15:45 +0200)
committerBrecht Van Lommel <brechtvanlommel@gmail.com>
Fri, 3 May 2019 13:42:49 +0000 (15:42 +0200)
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

13 files changed:
intern/cycles/blender/blender_mesh.cpp
intern/cycles/blender/blender_session.cpp
intern/cycles/blender/blender_shader.cpp
intern/cycles/kernel/osl/osl_globals.h
intern/cycles/kernel/osl/osl_services.cpp
intern/cycles/kernel/svm/svm_types.h
intern/cycles/render/image.cpp
intern/cycles/render/image.h
intern/cycles/render/nodes.cpp
intern/cycles/render/nodes.h
intern/cycles/render/osl.cpp
intern/cycles/render/osl.h
intern/cycles/render/shader.cpp

index de594f4fb6c4ff8698157a199ffee88beab43c74..1b47c4123e3d18034defcc7a00a53f50a734d78d 100644 (file)
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include "render/colorspace.h"
 #include "render/mesh.h"
 #include "render/object.h"
 #include "render/scene.h"
@@ -301,6 +302,7 @@ static void create_mesh_volume_attribute(
                                                INTERPOLATION_LINEAR,
                                                EXTENSION_CLIP,
                                                use_alpha,
+                                               u_colorspace_raw,
                                                metadata);
 }
 
index 3a7e5f02b1d8b57cf3b278e5ef0a3944b10ce21a..c50dbb6ba558b18c86ceec47cfc0de00b552b761 100644 (file)
 
 #include <stdlib.h>
 
+#include "device/device.h"
 #include "render/background.h"
 #include "render/buffers.h"
 #include "render/camera.h"
-#include "device/device.h"
-#include "render/integrator.h"
+#include "render/colorspace.h"
 #include "render/film.h"
+#include "render/integrator.h"
 #include "render/light.h"
 #include "render/mesh.h"
 #include "render/object.h"
@@ -1158,6 +1159,12 @@ void BlenderSession::builtin_image_info(const string &builtin_name,
     metadata.height = b_image.size()[1];
     metadata.depth = 1;
     metadata.channels = b_image.channels();
+
+    if (metadata.is_float) {
+      /* Float images are already converted on the Blender side,
+       * no need to do anything in Cycles. */
+      metadata.colorspace = u_colorspace_raw;
+    }
   }
   else if (b_id.is_a(&RNA_Object)) {
     /* smoke volume data */
index d1f823bc2b892d6cb26686e213bc87a3d17e5530..e6ec8b22d7a11965ed70a3a6c7b3fa305c91629a 100644 (file)
@@ -15,6 +15,7 @@
  */
 
 #include "render/background.h"
+#include "render/colorspace.h"
 #include "render/graph.h"
 #include "render/light.h"
 #include "render/nodes.h"
@@ -665,7 +666,14 @@ static ShaderNode *add_node(Scene *scene,
       }
 #endif
     }
-    image->color_space = (NodeImageColorSpace)b_image_node.color_space();
+    switch (b_image_node.color_space()) {
+      case BL::ShaderNodeTexImage::color_space_NONE:
+        image->colorspace = u_colorspace_raw;
+        break;
+      case BL::ShaderNodeTexImage::color_space_COLOR:
+        image->colorspace = u_colorspace_auto;
+        break;
+    }
     image->projection = (NodeImageProjection)b_image_node.projection();
     image->interpolation = get_image_interpolation(b_image_node);
     image->extension = get_image_extension(b_image_node);
@@ -710,7 +718,14 @@ static ShaderNode *add_node(Scene *scene,
       }
 #endif
     }
-    env->color_space = (NodeImageColorSpace)b_env_node.color_space();
+    switch (b_env_node.color_space()) {
+      case BL::ShaderNodeTexEnvironment::color_space_NONE:
+        env->colorspace = u_colorspace_raw;
+        break;
+      case BL::ShaderNodeTexEnvironment::color_space_COLOR:
+        env->colorspace = u_colorspace_auto;
+        break;
+    }
     env->interpolation = get_image_interpolation(b_env_node);
     env->projection = (NodeEnvironmentProjection)b_env_node.projection();
     BL::TexMapping b_texture_mapping(b_env_node.texture_mapping());
@@ -861,7 +876,8 @@ static ShaderNode *add_node(Scene *scene,
                                              point_density->builtin_data,
                                              point_density->interpolation,
                                              EXTENSION_CLIP,
-                                             true);
+                                             true,
+                                             u_colorspace_raw);
     }
     node = point_density;
 
index 414aaf891dbf6768128f19a305ec8b63820bb48e..51bc5cf81a98ea5970c8cc61e126b64761930b2d 100644 (file)
@@ -37,6 +37,7 @@ using std::isfinite;
 CCL_NAMESPACE_BEGIN
 
 class OSLRenderServices;
+class ColorSpaceProcessor;
 
 /* OSL Texture Handle
  *
@@ -53,21 +54,15 @@ class OSLRenderServices;
 struct OSLTextureHandle : public OIIO::RefCnt {
   enum Type { OIIO, SVM, IES, BEVEL, AO };
 
-  OSLTextureHandle() : type(OIIO), svm_slot(-1), oiio_handle(NULL)
-  {
-  }
-
-  OSLTextureHandle(Type type) : type(type), svm_slot(-1), oiio_handle(NULL)
-  {
-  }
-
-  OSLTextureHandle(Type type, int svm_slot) : type(type), svm_slot(svm_slot), oiio_handle(NULL)
+  OSLTextureHandle(Type type = OIIO, int svm_slot = -1)
+      : type(type), svm_slot(svm_slot), oiio_handle(NULL), processor(NULL)
   {
   }
 
   Type type;
   int svm_slot;
   OSL::TextureSystem::TextureHandle *oiio_handle;
+  ColorSpaceProcessor *processor;
 };
 
 typedef OIIO::intrusive_ptr<OSLTextureHandle> OSLTextureHandleRef;
index 7de596a2c302371f64ac4d7737b2d62c80cb7b5d..0257f569f4a96e3bde0ea8a81efdb742317913cb 100644 (file)
@@ -25,6 +25,7 @@
 
 #include <string.h>
 
+#include "render/colorspace.h"
 #include "render/mesh.h"
 #include "render/object.h"
 #include "render/scene.h"
@@ -1116,6 +1117,9 @@ bool OSLRenderServices::texture(ustring filename,
          * other nasty stuff happening. */
         ts->geterror();
       }
+      else if (handle && handle->processor) {
+        ColorSpaceManager::to_scene_linear(handle->processor, result, nchannels);
+      }
       break;
     }
   }
@@ -1213,6 +1217,9 @@ bool OSLRenderServices::texture3d(ustring filename,
          * other nasty stuff happening. */
         ts->geterror();
       }
+      else if (handle && handle->processor) {
+        ColorSpaceManager::to_scene_linear(handle->processor, result, nchannels);
+      }
       break;
     }
     case OSLTextureHandle::IES:
@@ -1287,6 +1294,9 @@ bool OSLRenderServices::environment(ustring filename,
         result[3] = 1.0f;
     }
   }
+  else if (handle && handle->processor) {
+    ColorSpaceManager::to_scene_linear(handle->processor, result, nchannels);
+  }
 
   return status;
 }
index d31e4f93696dd27a71f665f6a7969c1b48b41096..2e4d0c81b9512b661e02c0782c14c9f8efc6a20a 100644 (file)
@@ -373,11 +373,6 @@ typedef enum NodeNormalMapSpace {
   NODE_NORMAL_MAP_BLENDER_WORLD,
 } NodeNormalMapSpace;
 
-typedef enum NodeImageColorSpace {
-  NODE_COLOR_SPACE_NONE = 0,
-  NODE_COLOR_SPACE_COLOR = 1,
-} NodeImageColorSpace;
-
 typedef enum NodeImageProjection {
   NODE_IMAGE_PROJ_FLAT = 0,
   NODE_IMAGE_PROJ_BOX = 1,
index ae219e912e0e837550040b37f4d9e656509df4d0..dc75dca2d19016805a93c65d32a20861117b072d 100644 (file)
  * 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;
 }
 
index 34f046692f67051f0f27207f442f64fc921bf667..d5bc37e58d7c2483d109767ce3fd6c6b5ad5e4c7 100644 (file)
@@ -20,6 +20,8 @@
 #include "device/device.h"
 #include "device/device_memory.h"
 
+#include "render/colorspace.h"
+
 #include "util/util_image.h"
 #include "util/util_string.h"
 #include "util/util_thread.h"
@@ -32,6 +34,7 @@ class Device;
 class Progress;
 class RenderStats;
 class Scene;
+class ColorSpaceProcessor;
 
 class ImageMetaData {
  public:
@@ -43,13 +46,29 @@ class ImageMetaData {
 
   /* Automatically set. */
   ImageDataType type;
-  bool is_linear;
+  ustring colorspace;
+  bool compress_as_srgb;
+
+  ImageMetaData()
+      : is_float(false),
+        is_half(false),
+        channels(0),
+        width(0),
+        height(0),
+        depth(0),
+        builtin_free_cache(NULL),
+        type(IMAGE_DATA_NUM_TYPES),
+        colorspace(u_colorspace_raw),
+        compress_as_srgb(false)
+  {
+  }
 
   bool operator==(const ImageMetaData &other) const
   {
     return is_float == other.is_float && is_half == other.is_half && channels == other.channels &&
            width == other.width && height == other.height && depth == other.depth &&
-           type == other.type && is_linear == other.is_linear;
+           type == other.type && colorspace == other.colorspace &&
+           compress_as_srgb == other.compress_as_srgb;
   }
 };
 
@@ -65,19 +84,25 @@ class ImageManager {
                 InterpolationType interpolation,
                 ExtensionType extension,
                 bool use_alpha,
+                ustring colorspace,
                 ImageMetaData &metadata);
   void remove_image(int flat_slot);
   void remove_image(const string &filename,
                     void *builtin_data,
                     InterpolationType interpolation,
                     ExtensionType extension,
-                    bool use_alpha);
+                    bool use_alpha,
+                    ustring colorspace);
   void tag_reload_image(const string &filename,
                         void *builtin_data,
                         InterpolationType interpolation,
                         ExtensionType extension,
-                        bool use_alpha);
-  bool get_image_metadata(const string &filename, void *builtin_data, ImageMetaData &metadata);
+                        bool use_alpha,
+                        ustring colorspace);
+  bool get_image_metadata(const string &filename,
+                          void *builtin_data,
+                          ustring colorspace,
+                          ImageMetaData &metadata);
   bool get_image_metadata(int flat_slot, ImageMetaData &metadata);
 
   void device_update(Device *device, Scene *scene, Progress &progress);
@@ -120,6 +145,7 @@ class ImageManager {
     void *builtin_data;
     ImageMetaData metadata;
 
+    ustring colorspace;
     bool use_alpha;
     bool need_load;
     bool animated;
@@ -152,6 +178,8 @@ class ImageManager {
                        int texture_limit,
                        device_vector<DeviceType> &tex_img);
 
+  void metadata_detect_colorspace(ImageMetaData &metadata, const char *file_format);
+
   void device_load_image(
       Device *device, Scene *scene, ImageDataType type, int slot, Progress *progress);
   void device_free_image(Device *device, ImageDataType type, int slot);
index 35e9f8df5a8407b248284f2eac76786b8f397173..6e86643cc2bffc03e790204f736015b5f36b430f 100644 (file)
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include "render/colorspace.h"
 #include "render/film.h"
 #include "render/image.h"
 #include "render/integrator.h"
@@ -207,11 +208,7 @@ NODE_DEFINE(ImageTextureNode)
   TEXTURE_MAPPING_DEFINE(ImageTextureNode);
 
   SOCKET_STRING(filename, "Filename", ustring());
-
-  static NodeEnum color_space_enum;
-  color_space_enum.insert("none", NODE_COLOR_SPACE_NONE);
-  color_space_enum.insert("color", NODE_COLOR_SPACE_COLOR);
-  SOCKET_ENUM(color_space, "Color Space", color_space_enum, NODE_COLOR_SPACE_COLOR);
+  SOCKET_STRING(colorspace, "Colorspace", u_colorspace_auto);
 
   SOCKET_BOOLEAN(use_alpha, "Use Alpha", true);
 
@@ -250,7 +247,8 @@ ImageTextureNode::ImageTextureNode() : ImageSlotTextureNode(node_type)
   image_manager = NULL;
   slot = -1;
   is_float = -1;
-  is_linear = false;
+  compress_as_srgb = false;
+  colorspace = u_colorspace_raw;
   builtin_data = NULL;
   animated = false;
 }
@@ -259,7 +257,7 @@ ImageTextureNode::~ImageTextureNode()
 {
   if (image_manager) {
     image_manager->remove_image(
-        filename.string(), builtin_data, interpolation, extension, use_alpha);
+        filename.string(), builtin_data, interpolation, extension, use_alpha, colorspace);
   }
 }
 
@@ -269,7 +267,8 @@ ShaderNode *ImageTextureNode::clone() const
   node->image_manager = NULL;
   node->slot = -1;
   node->is_float = -1;
-  node->is_linear = false;
+  node->compress_as_srgb = false;
+  node->colorspace = u_colorspace_raw;
   return node;
 }
 
@@ -304,13 +303,14 @@ void ImageTextureNode::compile(SVMCompiler &compiler)
                                     interpolation,
                                     extension,
                                     use_alpha,
+                                    colorspace,
                                     metadata);
     is_float = metadata.is_float;
-    is_linear = metadata.is_linear;
+    compress_as_srgb = metadata.compress_as_srgb;
+    colorspace = metadata.colorspace;
   }
 
   if (slot != -1) {
-    int srgb = (is_linear || color_space != NODE_COLOR_SPACE_COLOR) ? 0 : 1;
     int vector_offset = tex_mapping.compile_begin(compiler, vector_in);
 
     if (projection != NODE_IMAGE_PROJ_BOX) {
@@ -319,7 +319,7 @@ void ImageTextureNode::compile(SVMCompiler &compiler)
                         compiler.encode_uchar4(vector_offset,
                                                compiler.stack_assign_if_linked(color_out),
                                                compiler.stack_assign_if_linked(alpha_out),
-                                               srgb),
+                                               compress_as_srgb),
                         projection);
     }
     else {
@@ -328,7 +328,7 @@ void ImageTextureNode::compile(SVMCompiler &compiler)
                         compiler.encode_uchar4(vector_offset,
                                                compiler.stack_assign_if_linked(color_out),
                                                compiler.stack_assign_if_linked(alpha_out),
-                                               srgb),
+                                               compress_as_srgb),
                         __float_as_int(projection_blend));
     }
 
@@ -358,7 +358,7 @@ void ImageTextureNode::compile(OSLCompiler &compiler)
   if (is_float == -1) {
     ImageMetaData metadata;
     if (builtin_data == NULL) {
-      image_manager->get_image_metadata(filename.string(), NULL, metadata);
+      image_manager->get_image_metadata(filename.string(), NULL, colorspace, metadata);
     }
     else {
       slot = image_manager->add_image(filename.string(),
@@ -368,17 +368,22 @@ void ImageTextureNode::compile(OSLCompiler &compiler)
                                       interpolation,
                                       extension,
                                       use_alpha,
+                                      colorspace,
                                       metadata);
     }
     is_float = metadata.is_float;
-    is_linear = metadata.is_linear;
+    compress_as_srgb = metadata.compress_as_srgb;
+    colorspace = metadata.colorspace;
   }
 
-  compiler.parameter_texture("filename", filename, slot);
-  if (is_linear || color_space != NODE_COLOR_SPACE_COLOR)
-    compiler.parameter("color_space", "linear");
-  else
-    compiler.parameter("color_space", "sRGB");
+  if (slot == -1) {
+    compiler.parameter_texture("filename", filename, colorspace);
+  }
+  else {
+    compiler.parameter_texture("filename", slot);
+  }
+
+  compiler.parameter("color_space", (compress_as_srgb) ? "sRGB" : "linear");
   compiler.parameter(this, "projection");
   compiler.parameter(this, "projection_blend");
   compiler.parameter("is_float", is_float);
@@ -398,11 +403,7 @@ NODE_DEFINE(EnvironmentTextureNode)
   TEXTURE_MAPPING_DEFINE(EnvironmentTextureNode);
 
   SOCKET_STRING(filename, "Filename", ustring());
-
-  static NodeEnum color_space_enum;
-  color_space_enum.insert("none", NODE_COLOR_SPACE_NONE);
-  color_space_enum.insert("color", NODE_COLOR_SPACE_COLOR);
-  SOCKET_ENUM(color_space, "Color Space", color_space_enum, NODE_COLOR_SPACE_COLOR);
+  SOCKET_STRING(colorspace, "Colorspace", u_colorspace_auto);
 
   SOCKET_BOOLEAN(use_alpha, "Use Alpha", true);
 
@@ -431,7 +432,8 @@ EnvironmentTextureNode::EnvironmentTextureNode() : ImageSlotTextureNode(node_typ
   image_manager = NULL;
   slot = -1;
   is_float = -1;
-  is_linear = false;
+  compress_as_srgb = false;
+  colorspace = u_colorspace_raw;
   builtin_data = NULL;
   animated = false;
 }
@@ -440,7 +442,7 @@ EnvironmentTextureNode::~EnvironmentTextureNode()
 {
   if (image_manager) {
     image_manager->remove_image(
-        filename.string(), builtin_data, interpolation, EXTENSION_REPEAT, use_alpha);
+        filename.string(), builtin_data, interpolation, EXTENSION_REPEAT, use_alpha, colorspace);
   }
 }
 
@@ -450,7 +452,8 @@ ShaderNode *EnvironmentTextureNode::clone() const
   node->image_manager = NULL;
   node->slot = -1;
   node->is_float = -1;
-  node->is_linear = false;
+  node->compress_as_srgb = false;
+  node->colorspace = u_colorspace_raw;
   return node;
 }
 
@@ -483,13 +486,14 @@ void EnvironmentTextureNode::compile(SVMCompiler &compiler)
                                     interpolation,
                                     EXTENSION_REPEAT,
                                     use_alpha,
+                                    colorspace,
                                     metadata);
     is_float = metadata.is_float;
-    is_linear = metadata.is_linear;
+    compress_as_srgb = metadata.compress_as_srgb;
+    colorspace = metadata.colorspace;
   }
 
   if (slot != -1) {
-    int srgb = (is_linear || color_space != NODE_COLOR_SPACE_COLOR) ? 0 : 1;
     int vector_offset = tex_mapping.compile_begin(compiler, vector_in);
 
     compiler.add_node(NODE_TEX_ENVIRONMENT,
@@ -497,7 +501,7 @@ void EnvironmentTextureNode::compile(SVMCompiler &compiler)
                       compiler.encode_uchar4(vector_offset,
                                              compiler.stack_assign_if_linked(color_out),
                                              compiler.stack_assign_if_linked(alpha_out),
-                                             srgb),
+                                             compress_as_srgb),
                       projection);
 
     tex_mapping.compile_end(compiler, vector_in, vector_offset);
@@ -529,7 +533,7 @@ void EnvironmentTextureNode::compile(OSLCompiler &compiler)
   if (is_float == -1) {
     ImageMetaData metadata;
     if (builtin_data == NULL) {
-      image_manager->get_image_metadata(filename.string(), NULL, metadata);
+      image_manager->get_image_metadata(filename.string(), NULL, colorspace, metadata);
     }
     else {
       slot = image_manager->add_image(filename.string(),
@@ -539,19 +543,23 @@ void EnvironmentTextureNode::compile(OSLCompiler &compiler)
                                       interpolation,
                                       EXTENSION_REPEAT,
                                       use_alpha,
+                                      colorspace,
                                       metadata);
     }
     is_float = metadata.is_float;
-    is_linear = metadata.is_linear;
+    compress_as_srgb = metadata.compress_as_srgb;
+    colorspace = metadata.colorspace;
   }
 
-  compiler.parameter_texture("filename", filename, slot);
-  compiler.parameter(this, "projection");
-  if (is_linear || color_space != NODE_COLOR_SPACE_COLOR)
-    compiler.parameter("color_space", "linear");
-  else
-    compiler.parameter("color_space", "sRGB");
+  if (slot == -1) {
+    compiler.parameter_texture("filename", filename, colorspace);
+  }
+  else {
+    compiler.parameter_texture("filename", slot);
+  }
 
+  compiler.parameter(this, "projection");
+  compiler.parameter("color_space", (compress_as_srgb) ? "sRGB" : "linear");
   compiler.parameter(this, "interpolation");
   compiler.parameter("is_float", is_float);
   compiler.parameter("use_alpha", !alpha_out->links.empty());
@@ -1467,7 +1475,7 @@ PointDensityTextureNode::~PointDensityTextureNode()
 {
   if (image_manager) {
     image_manager->remove_image(
-        filename.string(), builtin_data, interpolation, EXTENSION_CLIP, true);
+        filename.string(), builtin_data, interpolation, EXTENSION_CLIP, true, ustring());
   }
 }
 
@@ -1491,8 +1499,15 @@ void PointDensityTextureNode::add_image()
 {
   if (slot == -1) {
     ImageMetaData metadata;
-    slot = image_manager->add_image(
-        filename.string(), builtin_data, false, 0, interpolation, EXTENSION_CLIP, true, metadata);
+    slot = image_manager->add_image(filename.string(),
+                                    builtin_data,
+                                    false,
+                                    0,
+                                    interpolation,
+                                    EXTENSION_CLIP,
+                                    true,
+                                    u_colorspace_raw,
+                                    metadata);
   }
 }
 
@@ -1551,7 +1566,7 @@ void PointDensityTextureNode::compile(OSLCompiler &compiler)
   if (use_density || use_color) {
     add_image();
 
-    compiler.parameter_texture("filename", ustring(), slot);
+    compiler.parameter_texture("filename", slot);
     if (space == NODE_TEX_VOXEL_SPACE_WORLD) {
       compiler.parameter("mapping", tfm);
       compiler.parameter("use_mapping", 1);
index 7796711115e9606b1f1368826cae054993ae784f..88fa728ecd177fe5eac6f4f166c3ff25dbaafe26 100644 (file)
@@ -92,13 +92,18 @@ class ImageTextureNode : public ImageSlotTextureNode {
     return true;
   }
 
-  ImageManager *image_manager;
-  int is_float;
-  bool is_linear;
+  virtual bool equals(const ShaderNode &other)
+  {
+    const ImageTextureNode &image_node = (const ImageTextureNode &)other;
+    return ImageSlotTextureNode::equals(other) && builtin_data == image_node.builtin_data &&
+           animated == image_node.animated;
+  }
+
+  /* Parameters. */
   bool use_alpha;
   ustring filename;
   void *builtin_data;
-  NodeImageColorSpace color_space;
+  ustring colorspace;
   NodeImageProjection projection;
   InterpolationType interpolation;
   ExtensionType extension;
@@ -106,12 +111,11 @@ class ImageTextureNode : public ImageSlotTextureNode {
   bool animated;
   float3 vector;
 
-  virtual bool equals(const ShaderNode &other)
-  {
-    const ImageTextureNode &image_node = (const ImageTextureNode &)other;
-    return ImageSlotTextureNode::equals(other) && builtin_data == image_node.builtin_data &&
-           animated == image_node.animated;
-  }
+  /* Runtime. */
+  ImageManager *image_manager;
+  int is_float;
+  bool compress_as_srgb;
+  ustring known_colorspace;
 };
 
 class EnvironmentTextureNode : public ImageSlotTextureNode {
@@ -129,24 +133,28 @@ class EnvironmentTextureNode : public ImageSlotTextureNode {
     return NODE_GROUP_LEVEL_2;
   }
 
-  ImageManager *image_manager;
-  int is_float;
-  bool is_linear;
+  virtual bool equals(const ShaderNode &other)
+  {
+    const EnvironmentTextureNode &env_node = (const EnvironmentTextureNode &)other;
+    return ImageSlotTextureNode::equals(other) && builtin_data == env_node.builtin_data &&
+           animated == env_node.animated;
+  }
+
+  /* Parameters. */
   bool use_alpha;
   ustring filename;
   void *builtin_data;
-  NodeImageColorSpace color_space;
+  ustring colorspace;
   NodeEnvironmentProjection projection;
   InterpolationType interpolation;
   bool animated;
   float3 vector;
 
-  virtual bool equals(const ShaderNode &other)
-  {
-    const EnvironmentTextureNode &env_node = (const EnvironmentTextureNode &)other;
-    return ImageSlotTextureNode::equals(other) && builtin_data == env_node.builtin_data &&
-           animated == env_node.animated;
-  }
+  /* Runtime. */
+  ImageManager *image_manager;
+  int is_float;
+  bool compress_as_srgb;
+  ustring known_colorspace;
 };
 
 class SkyTextureNode : public TextureNode {
@@ -319,15 +327,17 @@ class PointDensityTextureNode : public ShaderNode {
 
   void add_image();
 
+  /* Parameters. */
   ustring filename;
   NodeTexVoxelSpace space;
   InterpolationType interpolation;
   Transform tfm;
   float3 vector;
+  void *builtin_data;
 
+  /* Runtime. */
   ImageManager *image_manager;
   int slot;
-  void *builtin_data;
 
   virtual bool equals(const ShaderNode &other)
   {
index 5ee453275b9c101aeaf2c4be7a98127d19b90672..a65c8e9f338f2716ec2840528b5af8cef39e3b98 100644 (file)
@@ -16,6 +16,7 @@
 
 #include "device/device.h"
 
+#include "render/colorspace.h"
 #include "render/graph.h"
 #include "render/light.h"
 #include "render/osl.h"
@@ -1205,25 +1206,29 @@ void OSLCompiler::compile(Scene *scene, Shader *shader)
   osl_globals->bump_state.push_back(shader->osl_surface_bump_ref);
 }
 
-void OSLCompiler::parameter_texture(const char *name, ustring filename, int svm_slot)
+void OSLCompiler::parameter_texture(const char *name, ustring filename, ustring colorspace)
 {
-  if (svm_slot != -1) {
-    /* It's not so simple to pass custom attribute to the texture() function
-     * in order to make builtin images support more clear. So we use special
-     * file name which is "@i<slot_number>" and use that for lookup in
-     * in OSLRenderServices::texture(). */
-    filename = string_printf("@i%d", svm_slot).c_str();
-    osl_globals->textures.insert(filename, new OSLTextureHandle(OSLTextureHandle::SVM, svm_slot));
-  }
-  else {
-    osl_globals->textures.insert(filename, new OSLTextureHandle(OSLTextureHandle::OIIO));
-  }
+  /* Textured loaded through the OpenImageIO texture cache. For this
+   * case we need to do runtime color space conversion. */
+  OSLTextureHandle *handle = new OSLTextureHandle(OSLTextureHandle::OIIO);
+  handle->processor = ColorSpaceManager::get_processor(colorspace);
+  osl_globals->textures.insert(filename, handle);
+  parameter(name, filename);
+}
 
+void OSLCompiler::parameter_texture(const char *name, int svm_slot)
+{
+  /* Texture loaded through SVM image texture system. We generate a unique
+   * name, which ends up being used in OSLRenderServices::get_texture_handle
+   * to get handle again. */
+  ustring filename(string_printf("@i%d", svm_slot).c_str());
+  osl_globals->textures.insert(filename, new OSLTextureHandle(OSLTextureHandle::SVM, svm_slot));
   parameter(name, filename);
 }
 
 void OSLCompiler::parameter_texture_ies(const char *name, int svm_slot)
 {
+  /* IES light textures stored in SVM. */
   ustring filename(string_printf("@l%d", svm_slot).c_str());
   osl_globals->textures.insert(filename, new OSLTextureHandle(OSLTextureHandle::IES, svm_slot));
   parameter(name, filename);
@@ -1285,7 +1290,11 @@ void OSLCompiler::parameter_color_array(const char * /*name*/, const array<float
 
 void OSLCompiler::parameter_texture(const char * /* name */,
                                     ustring /* filename */,
-                                    int /* svm_slot */)
+                                    ustring /* colorspace */)
+{
+}
+
+void OSLCompiler::parameter_texture(const char * /* name */, int /* svm_slot */)
 {
 }
 
index 773252ce9dc766064de139e1787382909cccd05b..ac73f1d3c2482d291d9991a7e59dc178dbab9f0c 100644 (file)
@@ -153,7 +153,8 @@ class OSLCompiler {
 
   void parameter_attribute(const char *name, ustring s);
 
-  void parameter_texture(const char *name, ustring filename, int svm_slot);
+  void parameter_texture(const char *name, ustring filename, ustring colorspace);
+  void parameter_texture(const char *name, int svm_slot);
   void parameter_texture_ies(const char *name, int svm_slot);
 
   ShaderType output_type()
index ac3303cbfebef21d1abef3f37057ed229c856160..ec85e5168320dc2269f6d38ed751188070f1d95e 100644 (file)
  * limitations under the License.
  */
 
+#include "device/device.h"
+
 #include "render/background.h"
 #include "render/camera.h"
-#include "device/device.h"
+#include "render/colorspace.h"
 #include "render/graph.h"
 #include "render/integrator.h"
 #include "render/light.h"
@@ -717,6 +719,8 @@ void ShaderManager::free_memory()
 #ifdef WITH_OSL
   OSLShaderManager::free_memory();
 #endif
+
+  ColorSpaceManager::free_memory();
 }
 
 float ShaderManager::linear_rgb_to_gray(float3 c)