diff --git a/release/datafiles/colormanagement/icc/README.md b/release/datafiles/colormanagement/icc/README.md new file mode 100644 index 00000000000..aa83b88715f --- /dev/null +++ b/release/datafiles/colormanagement/icc/README.md @@ -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 diff --git a/release/datafiles/colormanagement/icc/g24_rec2020_display.icc b/release/datafiles/colormanagement/icc/g24_rec2020_display.icc new file mode 100644 index 00000000000..82fd93ecb79 --- /dev/null +++ b/release/datafiles/colormanagement/icc/g24_rec2020_display.icc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b03c9218e82ab12fc4fcac7f5a0fb10ff53ebc7ad8387ae68a6d8cf5d4dfbaa3 +size 464 diff --git a/release/datafiles/colormanagement/icc/g24_rec709_display.icc b/release/datafiles/colormanagement/icc/g24_rec709_display.icc new file mode 100644 index 00000000000..0c7819abae0 --- /dev/null +++ b/release/datafiles/colormanagement/icc/g24_rec709_display.icc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4d10744483b3448daceacc880566b004cd90aa0ca535f651c8c88ba61646c965 +size 596 diff --git a/release/datafiles/colormanagement/icc/g26_xyzd65_display.icc b/release/datafiles/colormanagement/icc/g26_xyzd65_display.icc new file mode 100644 index 00000000000..40b1b2ac6bf --- /dev/null +++ b/release/datafiles/colormanagement/icc/g26_xyzd65_display.icc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:58ac463ea778e9b70692971dd4ba9071363a03aba77043894162b1ad6c051da7 +size 464 diff --git a/release/datafiles/colormanagement/icc/srgb_p3d65_display.icc b/release/datafiles/colormanagement/icc/srgb_p3d65_display.icc new file mode 100644 index 00000000000..84946657cff --- /dev/null +++ b/release/datafiles/colormanagement/icc/srgb_p3d65_display.icc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cb51de38e482ee974c0c76b9689e16aad04bad16e226fed2f30c842d15ff3a3d +size 480 diff --git a/source/blender/blenkernel/intern/image_save.cc b/source/blender/blenkernel/intern/image_save.cc index 03864e70941..5e8ca3dbb52 100644 --- a/source/blender/blenkernel/intern/image_save.cc +++ b/source/blender/blenkernel/intern/image_save.cc @@ -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) diff --git a/source/blender/editors/space_image/image_buttons.cc b/source/blender/editors/space_image/image_buttons.cc index 5f7a236de7e..ab26d7885a1 100644 --- a/source/blender/editors/space_image/image_buttons.cc +++ b/source/blender/editors/space_image/image_buttons.cc @@ -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", diff --git a/source/blender/imbuf/IMB_colormanagement.hh b/source/blender/imbuf/IMB_colormanagement.hh index 8de4739ef0f..77b733aea95 100644 --- a/source/blender/imbuf/IMB_colormanagement.hh +++ b/source/blender/imbuf/IMB_colormanagement.hh @@ -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 IMB_colormanagement_space_icc_profile(const ColorSpace *colorspace); + BLI_INLINE void IMB_colormanagement_get_luminance_coefficients(float r_rgb[3]); /** diff --git a/source/blender/imbuf/intern/colormanagement.cc b/source/blender/imbuf/intern/colormanagement.cc index 334f328fd8d..1756d4e8a09 100644 --- a/source/blender/imbuf/intern/colormanagement.cc +++ b/source/blender/imbuf/intern/colormanagement.cc @@ -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 IMB_colormanagement_space_icc_profile(const ColorSpace *colorspace) +{ + /* ICC profiles shipped with Blender are named after the OpenColorIO interop ID. */ + blender::Vector icc_profile; + + const StringRefNull interop_id = colorspace->interop_id(); + if (interop_id.is_empty()) { + return icc_profile; + } + + const std::optional 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); diff --git a/source/blender/imbuf/intern/format_jpeg.cc b/source/blender/imbuf/intern/format_jpeg.cc index f5c04a8fe2e..25d2943aed8 100644 --- a/source/blender/imbuf/intern/format_jpeg.cc +++ b/source/blender/imbuf/intern/format_jpeg.cc @@ -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 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(icc_profile.data()), + icc_profile.size()); + } + } + row_pointer[0] = MEM_malloc_arrayN>( size_t(cinfo->input_components) * size_t(cinfo->image_width), "jpeg row_pointer"); diff --git a/source/blender/imbuf/intern/format_webp.cc b/source/blender/imbuf/intern/format_webp.cc index f53d676fb5c..33ba64d8224 100644 --- a/source/blender/imbuf/intern/format_webp.cc +++ b/source/blender/imbuf/intern/format_webp.cc @@ -17,6 +17,7 @@ #include #include #include +#include #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 icc_profile = IMB_colormanagement_space_icc_profile(colorspace); + if (!icc_profile.is_empty()) { + WebPData icc_chunk = {reinterpret_cast(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; } diff --git a/source/blender/imbuf/intern/oiio/openimageio_support.cc b/source/blender/imbuf/intern/oiio/openimageio_support.cc index 0ac455b945a..ae99801240a 100644 --- a/source/blender/imbuf/intern/oiio/openimageio_support.cc +++ b/source/blender/imbuf/intern/oiio/openimageio_support.cc @@ -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 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; }