diff options
Diffstat (limited to '')
-rw-r--r-- | plug-ins/file-exr/openexr-wrapper.cc | 518 |
1 files changed, 518 insertions, 0 deletions
diff --git a/plug-ins/file-exr/openexr-wrapper.cc b/plug-ins/file-exr/openexr-wrapper.cc new file mode 100644 index 0000000..c678646 --- /dev/null +++ b/plug-ins/file-exr/openexr-wrapper.cc @@ -0,0 +1,518 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <string> + +#include <lcms2.h> + +/* These libgimp includes are not needed here at all, but this is a + * convenient place to make sure the public libgimp headers are + * C++-clean. The C++ compiler will choke on stuff like naming + * a struct member or parameter "private". + */ +#include "libgimp/gimp.h" +#include "libgimp/gimpui.h" +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" +#include "libgimpcolor/gimpcolor.h" +#include "libgimpconfig/gimpconfig.h" +#include "libgimpmodule/gimpmodule.h" +#include "libgimpthumb/gimpthumb.h" +#include "libgimpwidgets/gimpwidgets.h" + +#if defined(__MINGW32__) +#ifndef FLT_EPSILON +#define FLT_EPSILON __FLT_EPSILON__ +#endif +#ifndef DBL_EPSILON +#define DBL_EPSILON __DBL_EPSILON__ +#endif +#ifndef LDBL_EPSILON +#define LDBL_EPSILON __LDBL_EPSILON__ +#endif +#endif + +#include <ImfInputFile.h> +#include <ImfChannelList.h> +#include <ImfRgbaFile.h> +#include <ImfRgbaYca.h> +#include <ImfStandardAttributes.h> + +#include "exr-attribute-blob.h" +#include "openexr-wrapper.h" + +using namespace Imf; +using namespace Imf::RgbaYca; +using namespace Imath; + +static bool XYZ_equal(cmsCIEXYZ *a, cmsCIEXYZ *b) +{ + static const double epsilon = 0.0001; + // Y is encoding the luminance, we normalize that for comparison + return fabs ((a->X / a->Y * b->Y) - b->X) < epsilon && + fabs ((a->Y / a->Y * b->Y) - b->Y) < epsilon && + fabs ((a->Z / a->Y * b->Y) - b->Z) < epsilon; +} + +struct _EXRLoader +{ + _EXRLoader(const char* filename) : + refcount_(1), + file_(filename), + data_window_(file_.header().dataWindow()), + channels_(file_.header().channels()) + { + const Channel* chan; + + if (channels_.findChannel("R") || + channels_.findChannel("G") || + channels_.findChannel("B")) + { + format_string_ = "RGB"; + image_type_ = IMAGE_TYPE_RGB; + + if ((chan = channels_.findChannel("R"))) + pt_ = chan->type; + else if ((chan = channels_.findChannel("G"))) + pt_ = chan->type; + else + pt_ = channels_.findChannel("B")->type; + } + else if (channels_.findChannel("Y") && + (channels_.findChannel("RY") || + channels_.findChannel("BY"))) + { + format_string_ = "RGB"; + image_type_ = IMAGE_TYPE_RGB; + + pt_ = channels_.findChannel("Y")->type; + + // FIXME: no chroma handling for now. + throw; + } + else if (channels_.findChannel("Y")) + { + format_string_ = "Y"; + image_type_ = IMAGE_TYPE_GRAY; + + pt_ = channels_.findChannel("Y")->type; + } + else + { + throw; + } + + if (channels_.findChannel("A")) + { + format_string_.append("A"); + has_alpha_ = true; + } + else + { + has_alpha_ = false; + } + + switch (pt_) + { + case UINT: + format_string_.append(" u32"); + bpc_ = 4; + break; + case HALF: + format_string_.append(" half"); + bpc_ = 2; + break; + case FLOAT: + default: + format_string_.append(" float"); + bpc_ = 4; + } + } + + int readPixelRow(char* pixels, + int bpp, + int row) + { + const int actual_row = data_window_.min.y + row; + FrameBuffer fb; + // This is necessary because OpenEXR expects the buffer to begin at + // (0, 0). Though it probably results in some unmapped address, + // hopefully OpenEXR will not make use of it. :/ + char* base = pixels - (data_window_.min.x * bpp); + + switch (image_type_) + { + case IMAGE_TYPE_GRAY: + fb.insert("Y", Slice(pt_, base, bpp, 0, 1, 1, 0.5)); + if (hasAlpha()) + { + fb.insert("A", Slice(pt_, base + bpc_, bpp, 0, 1, 1, 1.0)); + } + break; + + case IMAGE_TYPE_RGB: + default: + fb.insert("R", Slice(pt_, base + (bpc_ * 0), bpp, 0, 1, 1, 0.0)); + fb.insert("G", Slice(pt_, base + (bpc_ * 1), bpp, 0, 1, 1, 0.0)); + fb.insert("B", Slice(pt_, base + (bpc_ * 2), bpp, 0, 1, 1, 0.0)); + if (hasAlpha()) + { + fb.insert("A", Slice(pt_, base + (bpc_ * 3), bpp, 0, 1, 1, 1.0)); + } + } + + file_.setFrameBuffer(fb); + file_.readPixels(actual_row); + + return 0; + } + + int getWidth() const { + return data_window_.max.x - data_window_.min.x + 1; + } + + int getHeight() const { + return data_window_.max.y - data_window_.min.y + 1; + } + + EXRPrecision getPrecision() const { + EXRPrecision prec; + + switch (pt_) + { + case UINT: + prec = PREC_UINT; + break; + case HALF: + prec = PREC_HALF; + break; + case FLOAT: + default: + prec = PREC_FLOAT; + } + + return prec; + } + + EXRImageType getImageType() const { + return image_type_; + } + + int hasAlpha() const { + return has_alpha_ ? 1 : 0; + } + + GimpColorProfile *getProfile() const { + Chromaticities chromaticities; + float whiteLuminance = 1.0; + + GimpColorProfile *linear_srgb_profile; + cmsHPROFILE linear_srgb_lcms; + + GimpColorProfile *profile; + cmsHPROFILE lcms_profile; + + cmsCIEXYZ *gimp_r_XYZ, *gimp_g_XYZ, *gimp_b_XYZ, *gimp_w_XYZ; + cmsCIEXYZ exr_r_XYZ, exr_g_XYZ, exr_b_XYZ, exr_w_XYZ; + + // get the color information from the EXR + if (hasChromaticities (file_.header ())) + chromaticities = Imf::chromaticities (file_.header ()); + else + return NULL; + + if (Imf::hasWhiteLuminance (file_.header ())) + whiteLuminance = Imf::whiteLuminance (file_.header ()); + else + return NULL; + +#if 0 + std::cout << "hasChromaticities: " + << hasChromaticities (file_.header ()) + << std::endl; + std::cout << "hasWhiteLuminance: " + << hasWhiteLuminance (file_.header ()) + << std::endl; + std::cout << whiteLuminance << std::endl; + std::cout << chromaticities.red << std::endl; + std::cout << chromaticities.green << std::endl; + std::cout << chromaticities.blue << std::endl; + std::cout << chromaticities.white << std::endl; + std::cout << std::endl; +#endif + + cmsCIExyY whitePoint = { chromaticities.white.x, + chromaticities.white.y, + whiteLuminance }; + cmsCIExyYTRIPLE CameraPrimaries = { { chromaticities.red.x, + chromaticities.red.y, + whiteLuminance }, + { chromaticities.green.x, + chromaticities.green.y, + whiteLuminance }, + { chromaticities.blue.x, + chromaticities.blue.y, + whiteLuminance } }; + + // get the primaries + wp from GIMP's internal linear sRGB profile + linear_srgb_profile = gimp_color_profile_new_rgb_srgb_linear (); + linear_srgb_lcms = gimp_color_profile_get_lcms_profile (linear_srgb_profile); + + gimp_r_XYZ = (cmsCIEXYZ *) cmsReadTag (linear_srgb_lcms, cmsSigRedColorantTag); + gimp_g_XYZ = (cmsCIEXYZ *) cmsReadTag (linear_srgb_lcms, cmsSigGreenColorantTag); + gimp_b_XYZ = (cmsCIEXYZ *) cmsReadTag (linear_srgb_lcms, cmsSigBlueColorantTag); + gimp_w_XYZ = (cmsCIEXYZ *) cmsReadTag (linear_srgb_lcms, cmsSigMediaWhitePointTag); + + cmsxyY2XYZ(&exr_r_XYZ, &CameraPrimaries.Red); + cmsxyY2XYZ(&exr_g_XYZ, &CameraPrimaries.Green); + cmsxyY2XYZ(&exr_b_XYZ, &CameraPrimaries.Blue); + cmsxyY2XYZ(&exr_w_XYZ, &whitePoint); + + // ... and check if the data stored in the EXR matches GIMP's internal profile + bool exr_is_linear_srgb = XYZ_equal (&exr_r_XYZ, gimp_r_XYZ) && + XYZ_equal (&exr_g_XYZ, gimp_g_XYZ) && + XYZ_equal (&exr_b_XYZ, gimp_b_XYZ) && + XYZ_equal (&exr_w_XYZ, gimp_w_XYZ); + + // using GIMP's linear sRGB profile allows to skip the conversion popup + if (exr_is_linear_srgb) + return linear_srgb_profile; + + // nope, it's something else. Clean up and build a new profile + g_object_unref (linear_srgb_profile); + + // TODO: maybe factor this out into libgimpcolor/gimpcolorprofile.h ? + double Parameters[2] = { 1.0, 0.0 }; + cmsToneCurve *Gamma[3]; + Gamma[0] = Gamma[1] = Gamma[2] = cmsBuildParametricToneCurve(0, + 1, + Parameters); + lcms_profile = cmsCreateRGBProfile (&whitePoint, &CameraPrimaries, Gamma); + cmsFreeToneCurve (Gamma[0]); + if (lcms_profile == NULL) return NULL; + +// cmsSetProfileVersion (lcms_profile, 2.1); + cmsMLU *mlu0 = cmsMLUalloc (NULL, 1); + cmsMLUsetASCII (mlu0, "en", "US", "(GIMP internal)"); + cmsMLU *mlu1 = cmsMLUalloc(NULL, 1); + cmsMLUsetASCII (mlu1, "en", "US", "color profile from EXR chromaticities"); + cmsMLU *mlu2 = cmsMLUalloc(NULL, 1); + cmsMLUsetASCII (mlu2, "en", "US", "color profile from EXR chromaticities"); + cmsWriteTag (lcms_profile, cmsSigDeviceMfgDescTag, mlu0); + cmsWriteTag (lcms_profile, cmsSigDeviceModelDescTag, mlu1); + cmsWriteTag (lcms_profile, cmsSigProfileDescriptionTag, mlu2); + cmsMLUfree (mlu0); + cmsMLUfree (mlu1); + cmsMLUfree (mlu2); + + profile = gimp_color_profile_new_from_lcms_profile (lcms_profile, + NULL); + cmsCloseProfile (lcms_profile); + + return profile; + } + + gchar *getComment() const { + char *result = NULL; + const Imf::StringAttribute *comment = file_.header().findTypedAttribute<Imf::StringAttribute>("comment"); + if (comment) + result = g_strdup (comment->value().c_str()); + return result; + } + + guchar *getExif(guint *size) const { + guchar jpeg_exif[] = "Exif\0\0"; + guchar *exif_data = NULL; + *size = 0; + + const Imf::BlobAttribute *exif = file_.header().findTypedAttribute<Imf::BlobAttribute>("exif"); + + if (exif) + { + exif_data = (guchar *)(exif->value().data.get()); + *size = exif->value().size; + // darktable appends a jpg-compatible exif00 string, so get rid of that again: + if ( ! memcmp (jpeg_exif, exif_data, sizeof(jpeg_exif))) + { + *size -= 6; + exif_data += 6; + } + } + + return (guchar *)g_memdup (exif_data, *size); + } + + guchar *getXmp(guint *size) const { + guchar *result = NULL; + *size = 0; + const Imf::StringAttribute *xmp = file_.header().findTypedAttribute<Imf::StringAttribute>("xmp"); + if (xmp) + { + *size = xmp->value().size(); + result = (guchar *) g_memdup (xmp->value().data(), *size); + } + return result; + } + + size_t refcount_; + InputFile file_; + const Box2i data_window_; + const ChannelList& channels_; + PixelType pt_; + int bpc_; + EXRImageType image_type_; + bool has_alpha_; + std::string format_string_; +}; + +EXRLoader* +exr_loader_new (const char *filename) +{ + EXRLoader* file; + + // Don't let any exceptions propagate to the C layer. + try + { + Imf::BlobAttribute::registerAttributeType(); + file = new EXRLoader(filename); + } + catch (...) + { + file = NULL; + } + + return file; +} + +EXRLoader* +exr_loader_ref (EXRLoader *loader) +{ + ++loader->refcount_; + return loader; +} + +void +exr_loader_unref (EXRLoader *loader) +{ + if (--loader->refcount_ == 0) + { + delete loader; + } +} + +int +exr_loader_get_width (EXRLoader *loader) +{ + int width; + // Don't let any exceptions propagate to the C layer. + try + { + width = loader->getWidth(); + } + catch (...) + { + width = -1; + } + + return width; +} + +int +exr_loader_get_height (EXRLoader *loader) +{ + int height; + // Don't let any exceptions propagate to the C layer. + try + { + height = loader->getHeight(); + } + catch (...) + { + height = -1; + } + + return height; +} + +EXRImageType +exr_loader_get_image_type (EXRLoader *loader) +{ + // This does not throw. + return loader->getImageType(); +} + +EXRPrecision +exr_loader_get_precision (EXRLoader *loader) +{ + // This does not throw. + return loader->getPrecision(); +} + +int +exr_loader_has_alpha (EXRLoader *loader) +{ + // This does not throw. + return loader->hasAlpha(); +} + +GimpColorProfile * +exr_loader_get_profile (EXRLoader *loader) +{ + return loader->getProfile (); +} + +gchar * +exr_loader_get_comment (EXRLoader *loader) +{ + return loader->getComment (); +} + +guchar * +exr_loader_get_exif (EXRLoader *loader, + guint *size) +{ + return loader->getExif (size); +} + +guchar * +exr_loader_get_xmp (EXRLoader *loader, + guint *size) +{ + return loader->getXmp (size); +} + +int +exr_loader_read_pixel_row (EXRLoader *loader, + char *pixels, + int bpp, + int row) +{ + int retval = -1; + // Don't let any exceptions propagate to the C layer. + try + { + retval = loader->readPixelRow(pixels, bpp, row); + } + catch (...) + { + retval = -1; + } + + return retval; +} |