diff --git a/source/blender/imbuf/CMakeLists.txt b/source/blender/imbuf/CMakeLists.txt index e8e8bc580c1..abf111961bd 100644 --- a/source/blender/imbuf/CMakeLists.txt +++ b/source/blender/imbuf/CMakeLists.txt @@ -18,6 +18,7 @@ set(INC_SYS ${JPEG_INCLUDE_DIR} ${PNG_INCLUDE_DIRS} ${ZLIB_INCLUDE_DIRS} + ${OPENIMAGEIO_INCLUDE_DIRS} ) set(SRC @@ -29,6 +30,7 @@ set(SRC intern/divers.c intern/filetype.c intern/filter.c + intern/format_psd.cc intern/imageprocess.c intern/indexer.c intern/iris.c diff --git a/source/blender/imbuf/IMB_imbuf.h b/source/blender/imbuf/IMB_imbuf.h index 79be739a205..2e38de62390 100644 --- a/source/blender/imbuf/IMB_imbuf.h +++ b/source/blender/imbuf/IMB_imbuf.h @@ -678,7 +678,7 @@ void IMB_sampleImageAtLocation( * \attention defined in readimage.c */ struct ImBuf *IMB_loadifffile( - int file, const char *filepath, int flags, char colorspace[IM_MAX_SPACE], const char *descr); + int file, int flags, char colorspace[IM_MAX_SPACE], const char *descr); /** * \attention defined in scaling.c diff --git a/source/blender/imbuf/IMB_imbuf_types.h b/source/blender/imbuf/IMB_imbuf_types.h index 5d4b8e453b3..c5bd60358b0 100644 --- a/source/blender/imbuf/IMB_imbuf_types.h +++ b/source/blender/imbuf/IMB_imbuf_types.h @@ -318,9 +318,6 @@ extern const char *imb_ext_image[]; extern const char *imb_ext_movie[]; extern const char *imb_ext_audio[]; -/** Image formats that can only be loaded via filepath. */ -extern const char *imb_ext_image_filepath_only[]; - /* -------------------------------------------------------------------- */ /** \name Imbuf Color Management Flag * diff --git a/source/blender/imbuf/intern/IMB_filetype.h b/source/blender/imbuf/intern/IMB_filetype.h index 8252b0dd0b3..c1a19688081 100644 --- a/source/blender/imbuf/intern/IMB_filetype.h +++ b/source/blender/imbuf/intern/IMB_filetype.h @@ -256,3 +256,16 @@ struct ImBuf *imb_load_filepath_thumbnail_webp(const char *filepath, bool imb_savewebp(struct ImBuf *ibuf, const char *name, int flags); /** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Format: PSD (#IMB_FTYPE_PSD) + * \{ */ + +bool imb_is_a_psd(const unsigned char *buf, size_t size); + +struct ImBuf *imb_load_psd(const uchar *mem, + size_t size, + int flags, + char colorspace[IM_MAX_SPACE]); + +/** \} */ diff --git a/source/blender/imbuf/intern/filetype.c b/source/blender/imbuf/intern/filetype.c index 06b70d354a9..aef1088efd4 100644 --- a/source/blender/imbuf/intern/filetype.c +++ b/source/blender/imbuf/intern/filetype.c @@ -184,9 +184,9 @@ const ImFileType IMB_FILE_TYPES[] = { { .init = NULL, .exit = NULL, - .is_a = imb_is_a_photoshop, - .load = NULL, - .load_filepath = imb_load_photoshop, + .is_a = imb_is_a_psd, + .load = imb_load_psd, + .load_filepath = NULL, .load_filepath_thumbnail = NULL, .save = NULL, .flag = IM_FTYPE_FLOAT, diff --git a/source/blender/imbuf/intern/format_psd.cc b/source/blender/imbuf/intern/format_psd.cc new file mode 100644 index 00000000000..40b863a6166 --- /dev/null +++ b/source/blender/imbuf/intern/format_psd.cc @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "oiio/openimageio_support.hh" + +#include "IMB_imbuf_types.h" + +OIIO_NAMESPACE_USING +using namespace blender::imbuf; + +extern "C" { + +bool imb_is_a_psd(const uchar *mem, size_t size) +{ + return imb_oiio_check(mem, size, "psd"); +} + +ImBuf *imb_load_psd(const uchar *mem, size_t size, int flags, char colorspace[IM_MAX_SPACE]) +{ + ImageSpec config, spec; + config.attribute("oiio:UnassociatedAlpha", 1); + + ReadContext ctx{mem, size, "psd", IMB_FTYPE_PSD, flags}; + + /* PSD should obey color space information embedded in the file. */ + ctx.use_embedded_colorspace = true; + + return imb_oiio_read(ctx, config, colorspace, spec); +} +} diff --git a/source/blender/imbuf/intern/oiio/CMakeLists.txt b/source/blender/imbuf/intern/oiio/CMakeLists.txt index 03a38928537..ef505cb85e9 100644 --- a/source/blender/imbuf/intern/oiio/CMakeLists.txt +++ b/source/blender/imbuf/intern/oiio/CMakeLists.txt @@ -18,8 +18,10 @@ set(INC_SYS set(SRC openimageio_api.h + openimageio_support.hh openimageio_api.cpp + openimageio_support.cc ) set(LIB diff --git a/source/blender/imbuf/intern/oiio/openimageio_api.cpp b/source/blender/imbuf/intern/oiio/openimageio_api.cpp index 7ed084b7144..b803eff36b0 100644 --- a/source/blender/imbuf/intern/oiio/openimageio_api.cpp +++ b/source/blender/imbuf/intern/oiio/openimageio_api.cpp @@ -5,278 +5,16 @@ * \ingroup openimageio */ -#include - -#if defined(WIN32) -# include "utfconv.h" -# define _USE_MATH_DEFINES -#endif - -/* NOTE: Keep first, #BLI_path_util conflicts with OIIO's format. */ #include "openimageio_api.h" #include -#include - -#include "MEM_guardedalloc.h" - -#include "BLI_blenlib.h" - -#include "IMB_allocimbuf.h" -#include "IMB_colormanagement.h" -#include "IMB_colormanagement_intern.h" -#include "IMB_imbuf.h" -#include "IMB_imbuf_types.h" OIIO_NAMESPACE_USING -using std::string; -using std::unique_ptr; - -using uchar = uchar; - -template -static void fill_all_channels(T *pixels, int width, int height, int components, Q alpha) -{ - if (components == 2) { - for (int i = width * height - 1; i >= 0; i--) { - pixels[i * 4 + 3] = pixels[i * 2 + 1]; - pixels[i * 4 + 2] = pixels[i * 2 + 0]; - pixels[i * 4 + 1] = pixels[i * 2 + 0]; - pixels[i * 4 + 0] = pixels[i * 2 + 0]; - } - } - else if (components == 3) { - for (int i = width * height - 1; i >= 0; i--) { - pixels[i * 4 + 3] = alpha; - pixels[i * 4 + 2] = pixels[i * 3 + 2]; - pixels[i * 4 + 1] = pixels[i * 3 + 1]; - pixels[i * 4 + 0] = pixels[i * 3 + 0]; - } - } - else if (components == 1) { - for (int i = width * height - 1; i >= 0; i--) { - pixels[i * 4 + 3] = alpha; - pixels[i * 4 + 2] = pixels[i]; - pixels[i * 4 + 1] = pixels[i]; - pixels[i * 4 + 0] = pixels[i]; - } - } -} - -static ImBuf *imb_oiio_load_image( - ImageInput *in, int width, int height, int components, int flags, bool is_alpha) -{ - ImBuf *ibuf; - int scanlinesize = width * components * sizeof(uchar); - - /* allocate the memory for the image */ - ibuf = IMB_allocImBuf(width, height, is_alpha ? 32 : 24, flags | IB_rect); - - try { - if (!in->read_image(0, - 0, - 0, - components, - TypeDesc::UINT8, - (uchar *)ibuf->rect + (height - 1) * scanlinesize, - AutoStride, - -scanlinesize, - AutoStride)) { - std::cerr << __func__ << ": ImageInput::read_image() failed:" << std::endl - << in->geterror() << std::endl; - - if (ibuf) { - IMB_freeImBuf(ibuf); - } - - return nullptr; - } - } - catch (const std::exception &exc) { - std::cerr << exc.what() << std::endl; - if (ibuf) { - IMB_freeImBuf(ibuf); - } - - return nullptr; - } - - /* ImBuf always needs 4 channels */ - fill_all_channels((uchar *)ibuf->rect, width, height, components, 0xFF); - - return ibuf; -} - -static ImBuf *imb_oiio_load_image_float( - ImageInput *in, int width, int height, int components, int flags, bool is_alpha) -{ - ImBuf *ibuf; - int scanlinesize = width * components * sizeof(float); - - /* allocate the memory for the image */ - ibuf = IMB_allocImBuf(width, height, is_alpha ? 32 : 24, flags | IB_rectfloat); - - try { - if (!in->read_image(0, - 0, - 0, - components, - TypeDesc::FLOAT, - (uchar *)ibuf->rect_float + (height - 1) * scanlinesize, - AutoStride, - -scanlinesize, - AutoStride)) { - std::cerr << __func__ << ": ImageInput::read_image() failed:" << std::endl - << in->geterror() << std::endl; - - if (ibuf) { - IMB_freeImBuf(ibuf); - } - - return nullptr; - } - } - catch (const std::exception &exc) { - std::cerr << exc.what() << std::endl; - if (ibuf) { - IMB_freeImBuf(ibuf); - } - - return nullptr; - } - - /* ImBuf always needs 4 channels */ - fill_all_channels((float *)ibuf->rect_float, width, height, components, 1.0f); - - /* NOTE: Photoshop 16 bit files never has alpha with it, - * so no need to handle associated/unassociated alpha. */ - return ibuf; -} - extern "C" { -bool imb_is_a_photoshop(const uchar *mem, size_t size) -{ - const uchar magic[4] = {'8', 'B', 'P', 'S'}; - if (size < sizeof(magic)) { - return false; - } - return memcmp(magic, mem, sizeof(magic)) == 0; -} - -int imb_save_photoshop(struct ImBuf *ibuf, const char * /*name*/, int flags) -{ - if (flags & IB_mem) { - std::cerr << __func__ << ": Photoshop PSD-save: Create PSD in memory" - << " currently not supported" << std::endl; - imb_addencodedbufferImBuf(ibuf); - ibuf->encodedsize = 0; - return 0; - } - - return 0; -} - -struct ImBuf *imb_load_photoshop(const char *filename, int flags, char colorspace[IM_MAX_SPACE]) -{ - struct ImBuf *ibuf = nullptr; - int width, height, components; - bool is_float, is_alpha, is_half; - int basesize; - char file_colorspace[IM_MAX_SPACE]; - const bool is_colorspace_manually_set = (colorspace[0] != '\0'); - - /* load image from file through OIIO */ - if (IMB_ispic_type_matches(filename, IMB_FTYPE_PSD) == 0) { - return nullptr; - } - - colorspace_set_default_role(colorspace, IM_MAX_SPACE, COLOR_ROLE_DEFAULT_BYTE); - - unique_ptr in(ImageInput::create(filename)); - if (!in) { - std::cerr << __func__ << ": ImageInput::create() failed:" << std::endl - << OIIO_NAMESPACE::geterror() << std::endl; - return nullptr; - } - - ImageSpec spec, config; - config.attribute("oiio:UnassociatedAlpha", int(1)); - - if (!in->open(filename, spec, config)) { - std::cerr << __func__ << ": ImageInput::open() failed:" << std::endl - << in->geterror() << std::endl; - return nullptr; - } - - if (!is_colorspace_manually_set) { - string ics = spec.get_string_attribute("oiio:ColorSpace"); - BLI_strncpy(file_colorspace, ics.c_str(), IM_MAX_SPACE); - - /* Only use color-spaces exist. */ - if (colormanage_colorspace_get_named(file_colorspace)) { - strcpy(colorspace, file_colorspace); - } - else { - std::cerr << __func__ << ": The embed colorspace (\"" << file_colorspace - << "\") not supported in existent OCIO configuration file. Fallback " - << "to system default colorspace (\"" << colorspace << "\")." << std::endl; - } - } - - width = spec.width; - height = spec.height; - components = spec.nchannels; - is_alpha = spec.alpha_channel != -1; - basesize = spec.format.basesize(); - is_float = basesize > 1; - is_half = spec.format == TypeDesc::HALF; - - /* we only handle certain number of components */ - if (!(components >= 1 && components <= 4)) { - if (in) { - in->close(); - } - return nullptr; - } - - if (is_float) { - ibuf = imb_oiio_load_image_float(in.get(), width, height, components, flags, is_alpha); - } - else { - ibuf = imb_oiio_load_image(in.get(), width, height, components, flags, is_alpha); - } - - if (in) { - in->close(); - } - - if (!ibuf) { - return nullptr; - } - - /* ImBuf always needs 4 channels */ - ibuf->ftype = IMB_FTYPE_PSD; - ibuf->channels = 4; - ibuf->planes = (3 + (is_alpha ? 1 : 0)) * 4 << basesize; - ibuf->flags |= (is_float && is_half) ? IB_halffloat : 0; - - try { - return ibuf; - } - catch (const std::exception &exc) { - std::cerr << exc.what() << std::endl; - if (ibuf) { - IMB_freeImBuf(ibuf); - } - - return nullptr; - } -} - int OIIO_getVersionHex(void) { return openimageio_version(); } -} /* export "C" */ +} /* extern "C" */ diff --git a/source/blender/imbuf/intern/oiio/openimageio_api.h b/source/blender/imbuf/intern/oiio/openimageio_api.h index 523c28ddeab..daae3f15f20 100644 --- a/source/blender/imbuf/intern/oiio/openimageio_api.h +++ b/source/blender/imbuf/intern/oiio/openimageio_api.h @@ -7,20 +7,10 @@ #pragma once -#include - #ifdef __cplusplus extern "C" { #endif -struct ImBuf; - -bool imb_is_a_photoshop(const unsigned char *mem, size_t size); - -int imb_save_photoshop(struct ImBuf *ibuf, const char *name, int flags); - -struct ImBuf *imb_load_photoshop(const char *name, int flags, char *colorspace); - int OIIO_getVersionHex(void); #ifdef __cplusplus diff --git a/source/blender/imbuf/intern/oiio/openimageio_support.cc b/source/blender/imbuf/intern/oiio/openimageio_support.cc new file mode 100644 index 00000000000..bda4027c613 --- /dev/null +++ b/source/blender/imbuf/intern/oiio/openimageio_support.cc @@ -0,0 +1,398 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "openimageio_support.hh" + +#include "BLI_blenlib.h" + +#include "BKE_idprop.h" +#include "DNA_ID.h" /* ID property definitions. */ + +#include "IMB_allocimbuf.h" +#include "IMB_colormanagement.h" +#include "IMB_metadata.h" + +OIIO_NAMESPACE_USING + +using std::string; +using std::unique_ptr; + +namespace blender::imbuf { + +/* An OIIO IOProxy used during file packing to write into an in-memory #ImBuf buffer. */ +class ImBufMemWriter : public Filesystem::IOProxy { + public: + ImBufMemWriter(ImBuf *ibuf) : IOProxy("", Write), ibuf_(ibuf) + { + } + + const char *proxytype() const override + { + return "ImBufMemWriter"; + } + + size_t write(const void *buf, size_t size) override + { + size = pwrite(buf, size, m_pos); + m_pos += size; + return size; + } + + size_t pwrite(const void *buf, size_t size, int64_t offset) override + { + /* If buffer is too small increase it. */ + size_t end = offset + size; + while (end > ibuf_->encodedbuffersize) { + if (!imb_enlargeencodedbufferImBuf(ibuf_)) { + /* Out of memory. */ + return 0; + } + } + + memcpy(ibuf_->encodedbuffer + offset, buf, size); + + if (end > ibuf_->encodedsize) { + ibuf_->encodedsize = end; + } + + return size; + } + + size_t size() const override + { + return ibuf_->encodedsize; + } + + private: + ImBuf *ibuf_; +}; + +/* Utility to in-place expand an n-component pixel buffer into a 4-component buffer. */ +template +static void fill_all_channels(T *pixels, int width, int height, int components, T alpha) +{ + const int64_t pixel_count = int64_t(width) * height; + if (components == 3) { + for (int64_t i = 0; i < pixel_count; i++) { + pixels[i * 4 + 3] = alpha; + } + } + else if (components == 1) { + for (int64_t i = 0; i < pixel_count; i++) { + pixels[i * 4 + 3] = alpha; + pixels[i * 4 + 2] = pixels[i * 4 + 0]; + pixels[i * 4 + 1] = pixels[i * 4 + 0]; + } + } + else if (components == 2) { + for (int64_t i = 0; i < pixel_count; i++) { + pixels[i * 4 + 3] = pixels[i * 4 + 1]; + pixels[i * 4 + 2] = pixels[i * 4 + 0]; + pixels[i * 4 + 1] = pixels[i * 4 + 0]; + } + } +} + +template +static ImBuf *load_pixels( + ImageInput *in, int width, int height, int channels, int flags, bool use_all_planes) +{ + /* Allocate the ImBuf for the image. */ + constexpr bool is_float = sizeof(T) > 1; + const uint format_flag = is_float ? IB_rectfloat : IB_rect; + const uint ibuf_flags = (flags & IB_test) ? 0 : format_flag; + const int planes = use_all_planes ? 32 : 8 * channels; + ImBuf *ibuf = IMB_allocImBuf(width, height, planes, ibuf_flags); + if (!ibuf) { + return nullptr; + } + + /* No need to load actual pixel data during the test phase. */ + if (flags & IB_test) { + return ibuf; + } + + /* Calculate an appropriate stride to read n-channels directly into + * the ImBuf 4-channel layout. */ + const stride_t ibuf_xstride = sizeof(T) * 4; + const stride_t ibuf_ystride = ibuf_xstride * width; + const TypeDesc format = is_float ? TypeDesc::FLOAT : TypeDesc::UINT8; + uchar *rect = is_float ? reinterpret_cast(ibuf->rect_float) : + reinterpret_cast(ibuf->rect); + void *ibuf_data = rect + ((stride_t(height) - 1) * ibuf_ystride); + + bool ok = in->read_image( + 0, 0, 0, channels, format, ibuf_data, ibuf_xstride, -ibuf_ystride, AutoStride); + if (!ok) { + fprintf(stderr, "ImageInput::read_image() failed: %s\n", in->geterror().c_str()); + + IMB_freeImBuf(ibuf); + return nullptr; + } + + /* ImBuf always needs 4 channels */ + const T alpha_fill = is_float ? 1.0f : 0xFF; + fill_all_channels(reinterpret_cast(rect), width, height, channels, alpha_fill); + + return ibuf; +} + +static void set_colorspace_name(char colorspace[IM_MAX_SPACE], + const ReadContext &ctx, + const ImageSpec &spec, + bool is_float) +{ + const bool is_colorspace_set = (colorspace[0] != '\0'); + if (is_colorspace_set) { + return; + } + + /* Use a default role unless otherwise specified. */ + if (ctx.use_colorspace_role >= 0) { + colorspace_set_default_role(colorspace, IM_MAX_SPACE, ctx.use_colorspace_role); + } + else if (is_float) { + colorspace_set_default_role(colorspace, IM_MAX_SPACE, COLOR_ROLE_DEFAULT_FLOAT); + } + else { + colorspace_set_default_role(colorspace, IM_MAX_SPACE, COLOR_ROLE_DEFAULT_BYTE); + } + + /* Override if necessary. */ + if (ctx.use_embedded_colorspace) { + string ics = spec.get_string_attribute("oiio:ColorSpace"); + char file_colorspace[IM_MAX_SPACE]; + BLI_strncpy(file_colorspace, ics.c_str(), IM_MAX_SPACE); + + /* Only use color-spaces that exist. */ + if (colormanage_colorspace_get_named(file_colorspace)) { + BLI_strncpy(colorspace, file_colorspace, IM_MAX_SPACE); + } + } +} + +/** + * Get an #ImBuf filled in with pixel data and associated metadata using the provided ImageInput. + */ +static ImBuf *get_oiio_ibuf(ImageInput *in, const ReadContext &ctx, char colorspace[IM_MAX_SPACE]) +{ + const ImageSpec &spec = in->spec(); + const int width = spec.width; + const int height = spec.height; + const int channels = spec.nchannels; + const bool has_alpha = spec.alpha_channel != -1; + const bool is_float = spec.format.basesize() > 1; + + if (channels < 1 || channels > 4) { + return nullptr; + } + + const bool use_all_planes = has_alpha || ctx.use_all_planes; + + ImBuf *ibuf = nullptr; + if (is_float) { + ibuf = load_pixels(in, width, height, channels, ctx.flags, use_all_planes); + ibuf->channels = 4; + } + else { + ibuf = load_pixels(in, width, height, channels, ctx.flags, use_all_planes); + } + + /* Fill in common ibuf properties. */ + if (ibuf) { + ibuf->ftype = ctx.file_type; + ibuf->flags |= (spec.format == TypeDesc::HALF) ? IB_halffloat : 0; + + set_colorspace_name(colorspace, ctx, spec, is_float); + + float x_res = spec.get_float_attribute("XResolution", 0.0f); + float y_res = spec.get_float_attribute("YResolution", 0.0f); + if (x_res > 0.0f && y_res > 0.0f) { + double scale = 1.0; + auto unit = spec.get_string_attribute("ResolutionUnit", ""); + if (unit == "in" || unit == "inch") { + scale = 100.0 / 2.54; + } + else if (unit == "cm") { + scale = 100.0; + } + ibuf->ppm[0] = scale * x_res; + ibuf->ppm[1] = scale * y_res; + } + + /* Transfer metadata to the ibuf if necessary. */ + if (ctx.flags & IB_metadata) { + IMB_metadata_ensure(&ibuf->metadata); + ibuf->flags |= (spec.extra_attribs.empty()) ? 0 : IB_metadata; + + for (const auto &attrib : spec.extra_attribs) { + IMB_metadata_set_field(ibuf->metadata, attrib.name().c_str(), attrib.get_string().c_str()); + } + } + } + + return ibuf; +} + +/** + * Returns an ImageInput for the precise `format` requested using the provided IOMemReader. + * If successful, the ImageInput will be opened and ready for operations. Null will be returned if + * the format was not found or if the open call fails. + */ +static unique_ptr get_oiio_reader(const char *format, + const ImageSpec &config, + Filesystem::IOMemReader &mem_reader, + ImageSpec &r_newspec) +{ + /* Attempt to create a reader based on the passed in format. */ + unique_ptr in = ImageInput::create(format); + if (!in) { + return nullptr; + } + + /* Open the reader using the ioproxy. */ + in->set_ioproxy(&mem_reader); + bool ok = in->open("", r_newspec, config); + if (!ok) { + in.reset(); + } + + return in; +} + +bool imb_oiio_check(const uchar *mem, size_t mem_size, const char *file_format) +{ + ImageSpec config, spec; + + /* This memory proxy must remain alive for the full duration of the read. */ + Filesystem::IOMemReader mem_reader(cspan(mem, mem_size)); + unique_ptr in = get_oiio_reader(file_format, config, mem_reader, spec); + return in ? true : false; +} + +ImBuf *imb_oiio_read(const ReadContext &ctx, + const ImageSpec &config, + char colorspace[IM_MAX_SPACE], + ImageSpec &r_newspec) +{ + /* This memory proxy must remain alive for the full duration of the read. */ + Filesystem::IOMemReader mem_reader(cspan(ctx.mem_start, ctx.mem_size)); + unique_ptr in = get_oiio_reader(ctx.file_format, config, mem_reader, r_newspec); + if (!in) { + return nullptr; + } + + return get_oiio_ibuf(in.get(), ctx, colorspace); +} + +bool imb_oiio_write(const WriteContext &ctx, const char *filepath, const ImageSpec &file_spec) +{ + unique_ptr out = ImageOutput::create(ctx.file_format); + if (!out) { + return false; + } + + auto write_op = [&out, &ctx]() { + return out->write_image( + ctx.mem_format, ctx.mem_start, ctx.mem_xstride, -ctx.mem_ystride, AutoStride); + }; + + bool ok = false; + if (ctx.flags & IB_mem) { + /* This memory proxy must remain alive for the full duration of the write. */ + ImBufMemWriter writer(ctx.ibuf); + + imb_addencodedbufferImBuf(ctx.ibuf); + out->set_ioproxy(&writer); + out->open("", file_spec); + ok = write_op(); + } + else { + out->open(filepath, file_spec); + ok = write_op(); + } + + out->close(); + return ok; +} + +WriteContext imb_create_write_context(const char *file_format, + ImBuf *ibuf, + int flags, + bool prefer_float) +{ + WriteContext ctx{}; + ctx.file_format = file_format; + ctx.ibuf = ibuf; + ctx.flags = flags; + + const int width = ibuf->x; + const int height = ibuf->y; + const bool use_float = prefer_float && (ibuf->rect_float != nullptr); + if (use_float) { + const int mem_channels = ibuf->channels ? ibuf->channels : 4; + ctx.mem_xstride = sizeof(float) * mem_channels; + ctx.mem_ystride = width * ctx.mem_xstride; + ctx.mem_format = TypeDesc::FLOAT; + ctx.mem_start = reinterpret_cast(ibuf->rect_float); + } + else { + const int mem_channels = 4; + ctx.mem_xstride = sizeof(uchar) * mem_channels; + ctx.mem_ystride = width * ctx.mem_xstride; + ctx.mem_format = TypeDesc::UINT8; + ctx.mem_start = reinterpret_cast(ibuf->rect); + } + + /* We always write using a negative y-stride so ensure we start at the end. */ + ctx.mem_start = ctx.mem_start + ((stride_t(height) - 1) * ctx.mem_ystride); + + return ctx; +} + +ImageSpec imb_create_write_spec(const WriteContext &ctx, int file_channels, TypeDesc data_format) +{ + const int width = ctx.ibuf->x; + const int height = ctx.ibuf->y; + ImageSpec file_spec(width, height, file_channels, data_format); + + /* Populate the spec with all common attributes. + * + * Care must be taken with the metadata: + * - It should be processed first, before the "Resolution" metadata below, to + * ensure the proper values end up in the ImageSpec + * - It needs to filter format-specific metadata that may no longer apply to + * the current format being written (e.g. metadata for tiff being written to a png) + */ + + if (ctx.ibuf->metadata) { + for (IDProperty *prop = static_cast(ctx.ibuf->metadata->data.group.first); prop; + prop = prop->next) { + if (prop->type == IDP_STRING) { + /* If this property has a prefixed name (oiio:, tiff:, etc.) and it belongs to + * oiio or a different format, then skip. */ + if (char *colon = strchr(prop->name, ':')) { + std::string prefix(prop->name, colon); + Strutil::to_lower(prefix); + if (prefix == "oiio" || + (!STREQ(prefix.c_str(), ctx.file_format) && OIIO::is_imageio_format_name(prefix))) { + /* Skip this attribute. */ + continue; + } + } + + file_spec.attribute(prop->name, IDP_String(prop)); + } + } + } + + if (ctx.ibuf->ppm[0] > 0.0 && ctx.ibuf->ppm[1] > 0.0) { + /* More OIIO formats support inch than meter. */ + file_spec.attribute("ResolutionUnit", "in"); + file_spec.attribute("XResolution", float(ctx.ibuf->ppm[0] * 0.0254)); + file_spec.attribute("YResolution", float(ctx.ibuf->ppm[1] * 0.0254)); + } + + return file_spec; +} + +} // namespace blender::imbuf diff --git a/source/blender/imbuf/intern/oiio/openimageio_support.hh b/source/blender/imbuf/intern/oiio/openimageio_support.hh new file mode 100644 index 00000000000..2000e7460bb --- /dev/null +++ b/source/blender/imbuf/intern/oiio/openimageio_support.hh @@ -0,0 +1,104 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include + +#include +#include + +#include "BLI_sys_types.h" + +#include "IMB_imbuf.h" +#include "IMB_imbuf_types.h" + +namespace blender::imbuf { + +/** + * Parameters and settings used while reading image formats. + */ +struct ReadContext { + const uchar *mem_start; + const size_t mem_size; + const char *file_format; + const eImbFileType file_type; + const int flags; + + /* Override the automatic color-role choice with the value specified here. */ + int use_colorspace_role = -1; + + /* Allocate and use all ImBuf image planes even if the image has fewer. */ + bool use_all_planes = false; + + /* Use the colorspace provided in the image metadata when available. */ + bool use_embedded_colorspace = false; +}; + +/** + * Parameters and settings used while writing image formats. + */ +struct WriteContext { + const char *file_format; + ImBuf *ibuf; + + OIIO::stride_t mem_xstride; + OIIO::stride_t mem_ystride; + OIIO::TypeDesc mem_format; + uchar *mem_start; + + int flags; +}; + +/** + * Check to see if we can load and open the given file format. + */ +bool imb_oiio_check(const uchar *mem, size_t mem_size, const char *file_format); + +/** + * The primary method for reading data into an #ImBuf. + * + * During the `IB_test` phase of loading, the `colorspace` parameter will be populated + * with the appropriate colorspace name. + * + * Upon return, the `r_newspec` parameter will contain image format information + * which can be inspected afterwards if necessary. + */ +ImBuf *imb_oiio_read(const ReadContext &ctx, + const OIIO::ImageSpec &config, + char colorspace[IM_MAX_SPACE], + OIIO::ImageSpec &r_newspec); + +/** + * The primary method for writing data from an #ImBuf to either a physical or in-memory + * destination. + * + * The `file_spec` parameter will typically come from #imb_create_write_spec. + */ +bool imb_oiio_write(const WriteContext &ctx, + const char *filepath, + const OIIO::ImageSpec &file_spec); + +/** + * Create a #WriteContext based on the provided #ImBuf and format information. + * + * If the provided #ImBuf contains both byte and float buffers, the `prefer_float` + * flag controls which buffer to use. By default, if a float buffer exists it will + * be used. + */ +WriteContext imb_create_write_context(const char *file_format, + ImBuf *ibuf, + int flags, + bool prefer_float = true); + +/** + * Returns an ImageSpec filled in with all common attributes associated with the #ImBuf + * provided as part of the #WriteContext. + * + * This includes optional metadata that has been attached to the #ImBuf and which should be + * written to the new file as necessary. + */ +OIIO::ImageSpec imb_create_write_spec(const WriteContext &ctx, + int file_channels, + OIIO::TypeDesc data_format); + +} // namespace blender::imbuf diff --git a/source/blender/imbuf/intern/openexr/openexr_api.cpp b/source/blender/imbuf/intern/openexr/openexr_api.cpp index e7d7ae973e4..244a02dcd3c 100644 --- a/source/blender/imbuf/intern/openexr/openexr_api.cpp +++ b/source/blender/imbuf/intern/openexr/openexr_api.cpp @@ -441,7 +441,7 @@ static void openexr_header_metadata(Header *header, struct ImBuf *ibuf) IDProperty *prop; for (prop = (IDProperty *)ibuf->metadata->data.group.first; prop; prop = prop->next) { - if (prop->type == IDP_STRING) { + if (prop->type == IDP_STRING && !STREQ(prop->name, "compression")) { header->insert(prop->name, StringAttribute(IDP_String(prop))); } } diff --git a/source/blender/imbuf/intern/readimage.c b/source/blender/imbuf/intern/readimage.c index 4a662ffba8c..bc472457896 100644 --- a/source/blender/imbuf/intern/readimage.c +++ b/source/blender/imbuf/intern/readimage.c @@ -114,44 +114,7 @@ ImBuf *IMB_ibImageFromMemory( return NULL; } -static ImBuf *IMB_ibImageFromFile(const char *filepath, - int flags, - char colorspace[IM_MAX_SPACE], - const char *descr) -{ - ImBuf *ibuf; - const ImFileType *type; - char effective_colorspace[IM_MAX_SPACE] = ""; - - if (colorspace) { - BLI_strncpy(effective_colorspace, colorspace, sizeof(effective_colorspace)); - } - - for (type = IMB_FILE_TYPES; type < IMB_FILE_TYPES_LAST; type++) { - if (type->load_filepath) { - ibuf = type->load_filepath(filepath, flags, effective_colorspace); - if (ibuf) { - imb_handle_alpha(ibuf, flags, colorspace, effective_colorspace); - return ibuf; - } - } - } - - if ((flags & IB_test) == 0) { - fprintf(stderr, "%s: unknown fileformat (%s)\n", __func__, descr); - } - - return NULL; -} - -static bool imb_is_filepath_format(const char *filepath) -{ - /* return true if this is one of the formats that can't be loaded from memory */ - return BLI_path_extension_check_array(filepath, imb_ext_image_filepath_only); -} - -ImBuf *IMB_loadifffile( - int file, const char *filepath, int flags, char colorspace[IM_MAX_SPACE], const char *descr) +ImBuf *IMB_loadifffile(int file, int flags, char colorspace[IM_MAX_SPACE], const char *descr) { ImBuf *ibuf; uchar *mem; @@ -161,10 +124,6 @@ ImBuf *IMB_loadifffile( return NULL; } - if (imb_is_filepath_format(filepath)) { - return IMB_ibImageFromFile(filepath, flags, colorspace, descr); - } - size = BLI_file_descriptor_size(file); imb_mmap_lock(); @@ -198,7 +157,7 @@ ImBuf *IMB_loadiffname(const char *filepath, int flags, char colorspace[IM_MAX_S return NULL; } - ibuf = IMB_loadifffile(file, filepath, flags, colorspace, filepath); + ibuf = IMB_loadifffile(file, flags, colorspace, filepath); if (ibuf) { BLI_strncpy(ibuf->name, filepath, sizeof(ibuf->name)); @@ -277,7 +236,7 @@ ImBuf *IMB_testiffname(const char *filepath, int flags) return NULL; } - ibuf = IMB_loadifffile(file, filepath, flags | IB_test | IB_multilayer, colorspace, filepath); + ibuf = IMB_loadifffile(file, flags | IB_test | IB_multilayer, colorspace, filepath); if (ibuf) { BLI_strncpy(ibuf->name, filepath, sizeof(ibuf->name)); diff --git a/source/blender/imbuf/intern/util.c b/source/blender/imbuf/intern/util.c index 7967c7c0fa5..138a933e6a3 100644 --- a/source/blender/imbuf/intern/util.c +++ b/source/blender/imbuf/intern/util.c @@ -67,13 +67,6 @@ const char *imb_ext_image[] = { NULL, }; -const char *imb_ext_image_filepath_only[] = { - ".psd", - ".pdd", - ".psb", - NULL, -}; - const char *imb_ext_movie[] = { ".avi", ".flc", ".mov", ".movie", ".mp4", ".m4v", ".m2v", ".m2t", ".m2ts", ".mts", ".ts", ".mv", ".avs", ".wmv", ".ogv", ".ogg", ".r3d", ".dv", ".mpeg", ".mpg", diff --git a/source/blender/python/generic/imbuf_py_api.c b/source/blender/python/generic/imbuf_py_api.c index f17a9c07f3c..925e47afaf5 100644 --- a/source/blender/python/generic/imbuf_py_api.c +++ b/source/blender/python/generic/imbuf_py_api.c @@ -480,7 +480,7 @@ static PyObject *M_imbuf_load(PyObject *UNUSED(self), PyObject *args, PyObject * return NULL; } - ImBuf *ibuf = IMB_loadifffile(file, filepath, IB_rect, NULL, filepath); + ImBuf *ibuf = IMB_loadifffile(file, IB_rect, NULL, filepath); close(file);