Color Management: Support for saving wide gamut images
* Bundled ICC profiles for display spaces supported by Blender, and embed them in the image file when saving. * Verified to work for PNG, TIFF, JPEG and WebP, but not all file formats support this. * No ICC profile is written for sRGB currently. It would be a matter of adding an icc file, however this may be a breaking change for some use cases. * Fix save as render of EXR files not properly changing the image colorspace to match. Uses CC0 licensed ICC files from the Compact ICC Profiles project. This does not include support for saving HDR images. While there exist ICC profiles for PQ, they are not well supported and the preferred method for HDR is to write CICP tags. However OpenImageIO support for this is still under development. Ref #144911 Pull Request: https://projects.blender.org/blender/blender/pulls/144565
This commit is contained in:
6
release/datafiles/colormanagement/icc/README.md
Normal file
6
release/datafiles/colormanagement/icc/README.md
Normal file
@@ -0,0 +1,6 @@
|
||||
ICC profiles to embed when writing images.
|
||||
|
||||
The names match the ASWF Color Interop Forum Recommendation.
|
||||
|
||||
From the Compact ICC Profiles project, with CC0-1.0 license.
|
||||
https://github.com/saucecontrol/Compact-ICC-Profiles
|
||||
BIN
release/datafiles/colormanagement/icc/g24_rec2020_display.icc
(Stored with Git LFS)
Normal file
BIN
release/datafiles/colormanagement/icc/g24_rec2020_display.icc
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
release/datafiles/colormanagement/icc/g24_rec709_display.icc
(Stored with Git LFS)
Normal file
BIN
release/datafiles/colormanagement/icc/g24_rec709_display.icc
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
release/datafiles/colormanagement/icc/g26_xyzd65_display.icc
(Stored with Git LFS)
Normal file
BIN
release/datafiles/colormanagement/icc/g26_xyzd65_display.icc
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
release/datafiles/colormanagement/icc/srgb_p3d65_display.icc
(Stored with Git LFS)
Normal file
BIN
release/datafiles/colormanagement/icc/srgb_p3d65_display.icc
(Stored with Git LFS)
Normal file
Binary file not shown.
@@ -13,6 +13,7 @@
|
||||
#include "BLI_index_range.hh"
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_path_utils.hh"
|
||||
#include "BLI_string_ref.hh"
|
||||
#include "BLI_string_utf8.h"
|
||||
#include "BLI_task.hh"
|
||||
#include "BLI_vector.hh"
|
||||
@@ -282,6 +283,24 @@ static void image_save_post(ReportList *reports,
|
||||
*r_colorspace_changed = true;
|
||||
}
|
||||
}
|
||||
else if (opts->save_as_render) {
|
||||
/* Set the display colorspace that we converted to. */
|
||||
const ColorSpace *colorspace = IMB_colormangement_display_get_color_space(
|
||||
&opts->im_format.display_settings);
|
||||
if (colorspace) {
|
||||
blender::StringRefNull colorspace_name = IMB_colormanagement_colorspace_get_name(colorspace);
|
||||
if (colorspace_name != ima->colorspace_settings.name) {
|
||||
STRNCPY(ima->colorspace_settings.name, colorspace_name.c_str());
|
||||
*r_colorspace_changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* View transform is now baked in, so don't apply it a second time for viewing. */
|
||||
if (ima->flag & IMA_VIEW_AS_RENDER) {
|
||||
ima->flag &= ~IMA_VIEW_AS_RENDER;
|
||||
*r_colorspace_changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void imbuf_save_post(ImBuf *ibuf, ImBuf *colormanaged_ibuf)
|
||||
|
||||
@@ -1066,6 +1066,7 @@ void uiTemplateImageSettings(uiLayout *layout,
|
||||
|
||||
/* Override color management */
|
||||
if (color_management) {
|
||||
col->separator_spacer();
|
||||
|
||||
if (uiLayout *panel = col->panel(C,
|
||||
panel_idname ? panel_idname : "settings_color_management",
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
*/
|
||||
|
||||
#include "BLI_compiler_compat.h"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include "BLI_math_matrix_types.hh"
|
||||
|
||||
@@ -81,6 +82,8 @@ bool IMB_colormanagement_space_name_is_data(const char *name);
|
||||
bool IMB_colormanagement_space_name_is_scene_linear(const char *name);
|
||||
bool IMB_colormanagement_space_name_is_srgb(const char *name);
|
||||
|
||||
blender::Vector<char> IMB_colormanagement_space_icc_profile(const ColorSpace *colorspace);
|
||||
|
||||
BLI_INLINE void IMB_colormanagement_get_luminance_coefficients(float r_rgb[3]);
|
||||
|
||||
/**
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
#include "BLI_fileops.hh"
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_math_color.h"
|
||||
#include "BLI_math_color.hh"
|
||||
@@ -1290,6 +1291,58 @@ const char *IMB_colormanagement_srgb_colorspace_name_get()
|
||||
return global_role_default_byte;
|
||||
}
|
||||
|
||||
blender::Vector<char> IMB_colormanagement_space_icc_profile(const ColorSpace *colorspace)
|
||||
{
|
||||
/* ICC profiles shipped with Blender are named after the OpenColorIO interop ID. */
|
||||
blender::Vector<char> icc_profile;
|
||||
|
||||
const StringRefNull interop_id = colorspace->interop_id();
|
||||
if (interop_id.is_empty()) {
|
||||
return icc_profile;
|
||||
}
|
||||
|
||||
const std::optional<std::string> dir = BKE_appdir_folder_id(BLENDER_DATAFILES,
|
||||
"colormanagement");
|
||||
if (!dir.has_value()) {
|
||||
return icc_profile;
|
||||
}
|
||||
|
||||
char icc_filename[FILE_MAX];
|
||||
STRNCPY(icc_filename, (interop_id + ".icc").c_str());
|
||||
BLI_path_make_safe_filename(icc_filename);
|
||||
|
||||
char icc_filepath[FILE_MAX];
|
||||
BLI_path_join(icc_filepath, sizeof(icc_filepath), dir->c_str(), "icc", icc_filename);
|
||||
|
||||
blender::fstream f(icc_filepath, std::ios::binary | std::ios::in | std::ios::ate);
|
||||
if (!f.is_open()) {
|
||||
/* If we can't find a scene referred filename, try display referred. */
|
||||
blender::StringRef icc_filepath_ref = icc_filepath;
|
||||
if (icc_filepath_ref.endswith("_scene.icc")) {
|
||||
std::string icc_filepath_display = icc_filepath_ref.drop_suffix(strlen("_scene.icc")) +
|
||||
"_display.icc";
|
||||
f.open(icc_filepath_display, std::ios::binary | std::ios::in | std::ios::ate);
|
||||
}
|
||||
|
||||
if (!f.is_open()) {
|
||||
return icc_profile;
|
||||
}
|
||||
}
|
||||
|
||||
std::streamsize size = f.tellg();
|
||||
if (size <= 0) {
|
||||
return icc_profile;
|
||||
}
|
||||
icc_profile.resize(size);
|
||||
|
||||
f.seekg(0, std::ios::beg);
|
||||
if (!f.read(icc_profile.data(), icc_profile.size())) {
|
||||
icc_profile.clear();
|
||||
}
|
||||
|
||||
return icc_profile;
|
||||
}
|
||||
|
||||
blender::float3x3 IMB_colormanagement_get_xyz_to_scene_linear()
|
||||
{
|
||||
return blender::float3x3(imbuf_xyz_to_scene_linear);
|
||||
|
||||
@@ -621,6 +621,19 @@ static void write_jpeg(jpeg_compress_struct *cinfo, ImBuf *ibuf)
|
||||
}
|
||||
}
|
||||
|
||||
/* Write ICC profile if there is one associated with the colorspace. */
|
||||
const ColorSpace *colorspace = ibuf->byte_buffer.colorspace;
|
||||
if (colorspace) {
|
||||
blender::Vector<char> icc_profile = IMB_colormanagement_space_icc_profile(colorspace);
|
||||
if (!icc_profile.is_empty()) {
|
||||
icc_profile.prepend({'I', 'C', 'C', '_', 'P', 'R', 'O', 'F', 'I', 'L', 'E', 0, 0, 1});
|
||||
jpeg_write_marker(cinfo,
|
||||
JPEG_APP0 + 2,
|
||||
reinterpret_cast<const JOCTET *>(icc_profile.data()),
|
||||
icc_profile.size());
|
||||
}
|
||||
}
|
||||
|
||||
row_pointer[0] = MEM_malloc_arrayN<std::remove_pointer_t<JSAMPROW>>(
|
||||
size_t(cinfo->input_components) * size_t(cinfo->image_width), "jpeg row_pointer");
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <fcntl.h>
|
||||
#include <webp/decode.h>
|
||||
#include <webp/encode.h>
|
||||
#include <webp/mux.h>
|
||||
|
||||
#include "BLI_fileops.h"
|
||||
#include "BLI_mmap.h"
|
||||
@@ -212,17 +213,53 @@ bool imb_savewebp(ImBuf *ibuf, const char *filepath, int /*flags*/)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (encoded_data != nullptr) {
|
||||
FILE *fp = BLI_fopen(filepath, "wb");
|
||||
if (!fp) {
|
||||
free(encoded_data);
|
||||
CLOG_ERROR(&LOG, "Cannot open file for writing: '%s'", filepath);
|
||||
return false;
|
||||
}
|
||||
fwrite(encoded_data, encoded_data_size, 1, fp);
|
||||
free(encoded_data);
|
||||
fclose(fp);
|
||||
if (encoded_data == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
WebPMux *mux = WebPMuxNew();
|
||||
WebPData image_data = {encoded_data, encoded_data_size};
|
||||
WebPMuxSetImage(mux, &image_data, false /* Don't copy data */);
|
||||
|
||||
/* Write ICC profile if there is one associated with the colorspace. */
|
||||
const ColorSpace *colorspace = ibuf->byte_buffer.colorspace;
|
||||
if (colorspace) {
|
||||
blender::Vector<char> icc_profile = IMB_colormanagement_space_icc_profile(colorspace);
|
||||
if (!icc_profile.is_empty()) {
|
||||
WebPData icc_chunk = {reinterpret_cast<const uint8_t *>(icc_profile.data()),
|
||||
size_t(icc_profile.size())};
|
||||
WebPMuxSetChunk(mux, "ICCP", &icc_chunk, true /* copy data */);
|
||||
}
|
||||
}
|
||||
|
||||
/* Assemble image and metadata. */
|
||||
WebPData output_data;
|
||||
if (WebPMuxAssemble(mux, &output_data) != WEBP_MUX_OK) {
|
||||
CLOG_ERROR(&LOG, "Error in mux assemble writing file: '%s'", filepath);
|
||||
WebPMuxDelete(mux);
|
||||
WebPFree(encoded_data);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Write to file. */
|
||||
bool ok = true;
|
||||
FILE *fp = BLI_fopen(filepath, "wb");
|
||||
if (fp) {
|
||||
if (fwrite(output_data.bytes, output_data.size, 1, fp) != 1) {
|
||||
CLOG_ERROR(&LOG, "Unknown error writing file: '%s'", filepath);
|
||||
ok = false;
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
}
|
||||
else {
|
||||
ok = false;
|
||||
CLOG_ERROR(&LOG, "Cannot open file for writing: '%s'", filepath);
|
||||
}
|
||||
|
||||
WebPMuxDelete(mux);
|
||||
WebPFree(encoded_data);
|
||||
WebPDataClear(&output_data);
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
@@ -452,6 +452,19 @@ ImageSpec imb_create_write_spec(const WriteContext &ctx, int file_channels, Type
|
||||
}
|
||||
}
|
||||
|
||||
/* Write ICC profile if there is one associated with the colorspace. */
|
||||
const ColorSpace *colorspace = (ctx.mem_spec.format == TypeDesc::FLOAT) ?
|
||||
ctx.ibuf->float_buffer.colorspace :
|
||||
ctx.ibuf->byte_buffer.colorspace;
|
||||
if (colorspace) {
|
||||
Vector<char> icc_profile = IMB_colormanagement_space_icc_profile(colorspace);
|
||||
if (!icc_profile.is_empty()) {
|
||||
file_spec.attribute("ICCProfile",
|
||||
OIIO::TypeDesc(OIIO::TypeDesc::UINT8, icc_profile.size()),
|
||||
icc_profile.data());
|
||||
}
|
||||
}
|
||||
|
||||
return file_spec;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user