1 /* GIMP - The GNU Image Manipulation Program
2  * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
16  */
17 
18 #include "config.h"
19 
20 #include <string>
21 
22 #include <lcms2.h>
23 
24 /*  These libgimp includes are not needed here at all, but this is a
25  *  convenient place to make sure the public libgimp headers are
26  *  C++-clean. The C++ compiler will choke on stuff like naming
27  *  a struct member or parameter "private".
28  */
29 #include "libgimp/gimp.h"
30 #include "libgimp/gimpui.h"
31 #include "libgimpbase/gimpbase.h"
32 #include "libgimpmath/gimpmath.h"
33 #include "libgimpcolor/gimpcolor.h"
34 #include "libgimpconfig/gimpconfig.h"
35 #include "libgimpmodule/gimpmodule.h"
36 #include "libgimpthumb/gimpthumb.h"
37 #include "libgimpwidgets/gimpwidgets.h"
38 
39 #if defined(__MINGW32__)
40 #ifndef FLT_EPSILON
41 #define FLT_EPSILON  __FLT_EPSILON__
42 #endif
43 #ifndef DBL_EPSILON
44 #define DBL_EPSILON  __DBL_EPSILON__
45 #endif
46 #ifndef LDBL_EPSILON
47 #define LDBL_EPSILON __LDBL_EPSILON__
48 #endif
49 #endif
50 
51 #include <ImfInputFile.h>
52 #include <ImfChannelList.h>
53 #include <ImfRgbaFile.h>
54 #include <ImfRgbaYca.h>
55 #include <ImfStandardAttributes.h>
56 
57 #include "exr-attribute-blob.h"
58 #include "openexr-wrapper.h"
59 
60 using namespace Imf;
61 using namespace Imf::RgbaYca;
62 using namespace Imath;
63 
XYZ_equal(cmsCIEXYZ * a,cmsCIEXYZ * b)64 static bool XYZ_equal(cmsCIEXYZ *a, cmsCIEXYZ *b)
65 {
66   static const double epsilon = 0.0001;
67   // Y is encoding the luminance, we normalize that for comparison
68   return fabs ((a->X / a->Y * b->Y) - b->X) < epsilon &&
69          fabs ((a->Y / a->Y * b->Y) - b->Y) < epsilon &&
70          fabs ((a->Z / a->Y * b->Y) - b->Z) < epsilon;
71 }
72 
73 struct _EXRLoader
74 {
_EXRLoader_EXRLoader75   _EXRLoader(const char* filename) :
76     refcount_(1),
77     file_(filename),
78     data_window_(file_.header().dataWindow()),
79     channels_(file_.header().channels())
80   {
81     const Channel* chan;
82 
83     if (channels_.findChannel("R") ||
84         channels_.findChannel("G") ||
85         channels_.findChannel("B"))
86       {
87         format_string_ = "RGB";
88         image_type_ = IMAGE_TYPE_RGB;
89 
90         if ((chan = channels_.findChannel("R")))
91           pt_ = chan->type;
92         else if ((chan = channels_.findChannel("G")))
93           pt_ = chan->type;
94         else
95           pt_ = channels_.findChannel("B")->type;
96       }
97     else if (channels_.findChannel("Y") &&
98              (channels_.findChannel("RY") ||
99               channels_.findChannel("BY")))
100       {
101         format_string_ = "RGB";
102         image_type_ = IMAGE_TYPE_RGB;
103 
104         pt_ = channels_.findChannel("Y")->type;
105 
106         // FIXME: no chroma handling for now.
107         throw;
108       }
109     else if (channels_.findChannel("Y"))
110       {
111         format_string_ = "Y";
112         image_type_ = IMAGE_TYPE_GRAY;
113 
114         pt_ = channels_.findChannel("Y")->type;
115       }
116     else
117       {
118         throw;
119       }
120 
121     if (channels_.findChannel("A"))
122       {
123         format_string_.append("A");
124         has_alpha_ = true;
125       }
126     else
127       {
128         has_alpha_ = false;
129       }
130 
131     switch (pt_)
132       {
133       case UINT:
134         format_string_.append(" u32");
135         bpc_ = 4;
136         break;
137       case HALF:
138         format_string_.append(" half");
139         bpc_ = 2;
140         break;
141       case FLOAT:
142       default:
143         format_string_.append(" float");
144         bpc_ = 4;
145       }
146   }
147 
readPixelRow_EXRLoader148   int readPixelRow(char* pixels,
149                    int bpp,
150                    int row)
151   {
152     const int actual_row = data_window_.min.y + row;
153     FrameBuffer fb;
154     // This is necessary because OpenEXR expects the buffer to begin at
155     // (0, 0). Though it probably results in some unmapped address,
156     // hopefully OpenEXR will not make use of it. :/
157     char* base = pixels - (data_window_.min.x * bpp);
158 
159     switch (image_type_)
160       {
161       case IMAGE_TYPE_GRAY:
162         fb.insert("Y", Slice(pt_, base, bpp, 0, 1, 1, 0.5));
163         if (hasAlpha())
164           {
165             fb.insert("A", Slice(pt_, base + bpc_, bpp, 0, 1, 1, 1.0));
166           }
167         break;
168 
169       case IMAGE_TYPE_RGB:
170       default:
171         fb.insert("R", Slice(pt_, base + (bpc_ * 0), bpp, 0, 1, 1, 0.0));
172         fb.insert("G", Slice(pt_, base + (bpc_ * 1), bpp, 0, 1, 1, 0.0));
173         fb.insert("B", Slice(pt_, base + (bpc_ * 2), bpp, 0, 1, 1, 0.0));
174         if (hasAlpha())
175           {
176             fb.insert("A", Slice(pt_, base + (bpc_ * 3), bpp, 0, 1, 1, 1.0));
177           }
178       }
179 
180     file_.setFrameBuffer(fb);
181     file_.readPixels(actual_row);
182 
183     return 0;
184   }
185 
getWidth_EXRLoader186   int getWidth() const {
187     return data_window_.max.x - data_window_.min.x + 1;
188   }
189 
getHeight_EXRLoader190   int getHeight() const {
191     return data_window_.max.y - data_window_.min.y + 1;
192   }
193 
getPrecision_EXRLoader194   EXRPrecision getPrecision() const {
195     EXRPrecision prec;
196 
197     switch (pt_)
198       {
199       case UINT:
200         prec = PREC_UINT;
201         break;
202       case HALF:
203         prec = PREC_HALF;
204         break;
205       case FLOAT:
206       default:
207         prec = PREC_FLOAT;
208       }
209 
210     return prec;
211   }
212 
getImageType_EXRLoader213   EXRImageType getImageType() const {
214     return image_type_;
215   }
216 
hasAlpha_EXRLoader217   int hasAlpha() const {
218     return has_alpha_ ? 1 : 0;
219   }
220 
getProfile_EXRLoader221   GimpColorProfile *getProfile() const {
222     Chromaticities chromaticities;
223     float whiteLuminance = 1.0;
224 
225     GimpColorProfile *linear_srgb_profile;
226     cmsHPROFILE linear_srgb_lcms;
227 
228     GimpColorProfile *profile;
229     cmsHPROFILE lcms_profile;
230 
231     cmsCIEXYZ *gimp_r_XYZ, *gimp_g_XYZ, *gimp_b_XYZ, *gimp_w_XYZ;
232     cmsCIEXYZ exr_r_XYZ, exr_g_XYZ, exr_b_XYZ, exr_w_XYZ;
233 
234     // get the color information from the EXR
235     if (hasChromaticities (file_.header ()))
236       chromaticities = Imf::chromaticities (file_.header ());
237     else
238       return NULL;
239 
240     if (Imf::hasWhiteLuminance (file_.header ()))
241       whiteLuminance = Imf::whiteLuminance (file_.header ());
242     else
243       return NULL;
244 
245 #if 0
246     std::cout << "hasChromaticities: "
247               << hasChromaticities (file_.header ())
248               << std::endl;
249     std::cout << "hasWhiteLuminance: "
250               << hasWhiteLuminance (file_.header ())
251               << std::endl;
252     std::cout << whiteLuminance << std::endl;
253     std::cout << chromaticities.red << std::endl;
254     std::cout << chromaticities.green << std::endl;
255     std::cout << chromaticities.blue << std::endl;
256     std::cout << chromaticities.white << std::endl;
257     std::cout << std::endl;
258 #endif
259 
260     cmsCIExyY whitePoint = { chromaticities.white.x,
261                              chromaticities.white.y,
262                              whiteLuminance };
263     cmsCIExyYTRIPLE CameraPrimaries = { { chromaticities.red.x,
264                                           chromaticities.red.y,
265                                           whiteLuminance },
266                                         { chromaticities.green.x,
267                                           chromaticities.green.y,
268                                           whiteLuminance },
269                                         { chromaticities.blue.x,
270                                           chromaticities.blue.y,
271                                           whiteLuminance } };
272 
273     // get the primaries + wp from GIMP's internal linear sRGB profile
274     linear_srgb_profile = gimp_color_profile_new_rgb_srgb_linear ();
275     linear_srgb_lcms = gimp_color_profile_get_lcms_profile (linear_srgb_profile);
276 
277     gimp_r_XYZ = (cmsCIEXYZ *) cmsReadTag (linear_srgb_lcms, cmsSigRedColorantTag);
278     gimp_g_XYZ = (cmsCIEXYZ *) cmsReadTag (linear_srgb_lcms, cmsSigGreenColorantTag);
279     gimp_b_XYZ = (cmsCIEXYZ *) cmsReadTag (linear_srgb_lcms, cmsSigBlueColorantTag);
280     gimp_w_XYZ = (cmsCIEXYZ *) cmsReadTag (linear_srgb_lcms, cmsSigMediaWhitePointTag);
281 
282     cmsxyY2XYZ(&exr_r_XYZ, &CameraPrimaries.Red);
283     cmsxyY2XYZ(&exr_g_XYZ, &CameraPrimaries.Green);
284     cmsxyY2XYZ(&exr_b_XYZ, &CameraPrimaries.Blue);
285     cmsxyY2XYZ(&exr_w_XYZ, &whitePoint);
286 
287     // ... and check if the data stored in the EXR matches GIMP's internal profile
288     bool exr_is_linear_srgb = XYZ_equal (&exr_r_XYZ, gimp_r_XYZ) &&
289                               XYZ_equal (&exr_g_XYZ, gimp_g_XYZ) &&
290                               XYZ_equal (&exr_b_XYZ, gimp_b_XYZ) &&
291                               XYZ_equal (&exr_w_XYZ, gimp_w_XYZ);
292 
293     // using GIMP's linear sRGB profile allows to skip the conversion popup
294     if (exr_is_linear_srgb)
295       return linear_srgb_profile;
296 
297     // nope, it's something else. Clean up and build a new profile
298     g_object_unref (linear_srgb_profile);
299 
300     // TODO: maybe factor this out into libgimpcolor/gimpcolorprofile.h ?
301     double Parameters[2] = { 1.0, 0.0 };
302     cmsToneCurve *Gamma[3];
303     Gamma[0] = Gamma[1] = Gamma[2] = cmsBuildParametricToneCurve(0,
304                                                                  1,
305                                                                  Parameters);
306     lcms_profile = cmsCreateRGBProfile (&whitePoint, &CameraPrimaries, Gamma);
307     cmsFreeToneCurve (Gamma[0]);
308     if (lcms_profile == NULL) return NULL;
309 
310 //     cmsSetProfileVersion (lcms_profile, 2.1);
311     cmsMLU *mlu0 = cmsMLUalloc (NULL, 1);
312     cmsMLUsetASCII (mlu0, "en", "US", "(GIMP internal)");
313     cmsMLU *mlu1 = cmsMLUalloc(NULL, 1);
314     cmsMLUsetASCII (mlu1, "en", "US", "color profile from EXR chromaticities");
315     cmsMLU *mlu2 = cmsMLUalloc(NULL, 1);
316     cmsMLUsetASCII (mlu2, "en", "US", "color profile from EXR chromaticities");
317     cmsWriteTag (lcms_profile, cmsSigDeviceMfgDescTag, mlu0);
318     cmsWriteTag (lcms_profile, cmsSigDeviceModelDescTag, mlu1);
319     cmsWriteTag (lcms_profile, cmsSigProfileDescriptionTag, mlu2);
320     cmsMLUfree (mlu0);
321     cmsMLUfree (mlu1);
322     cmsMLUfree (mlu2);
323 
324     profile = gimp_color_profile_new_from_lcms_profile (lcms_profile,
325                                                         NULL);
326     cmsCloseProfile (lcms_profile);
327 
328     return profile;
329   }
330 
getComment_EXRLoader331   gchar *getComment() const {
332     char *result = NULL;
333     const Imf::StringAttribute *comment = file_.header().findTypedAttribute<Imf::StringAttribute>("comment");
334     if (comment)
335       result = g_strdup (comment->value().c_str());
336     return result;
337   }
338 
getExif_EXRLoader339   guchar *getExif(guint *size) const {
340     guchar jpeg_exif[] = "Exif\0\0";
341     guchar *exif_data = NULL;
342     *size = 0;
343 
344     const Imf::BlobAttribute *exif = file_.header().findTypedAttribute<Imf::BlobAttribute>("exif");
345 
346     if (exif)
347       {
348         exif_data = (guchar *)(exif->value().data.get());
349         *size = exif->value().size;
350         // darktable appends a jpg-compatible exif00 string, so get rid of that again:
351         if ( ! memcmp (jpeg_exif, exif_data, sizeof(jpeg_exif)))
352           {
353             *size -= 6;
354             exif_data += 6;
355           }
356       }
357 
358     return (guchar *)g_memdup (exif_data, *size);
359   }
360 
getXmp_EXRLoader361   guchar *getXmp(guint *size) const {
362     guchar *result = NULL;
363     *size = 0;
364     const Imf::StringAttribute *xmp = file_.header().findTypedAttribute<Imf::StringAttribute>("xmp");
365     if (xmp)
366       {
367         *size = xmp->value().size();
368         result = (guchar *) g_memdup (xmp->value().data(), *size);
369       }
370     return result;
371   }
372 
373   size_t refcount_;
374   InputFile file_;
375   const Box2i data_window_;
376   const ChannelList& channels_;
377   PixelType pt_;
378   int bpc_;
379   EXRImageType image_type_;
380   bool has_alpha_;
381   std::string format_string_;
382 };
383 
384 EXRLoader*
exr_loader_new(const char * filename)385 exr_loader_new (const char *filename)
386 {
387   EXRLoader* file;
388 
389   // Don't let any exceptions propagate to the C layer.
390   try
391     {
392       Imf::BlobAttribute::registerAttributeType();
393       file = new EXRLoader(filename);
394     }
395   catch (...)
396     {
397       file = NULL;
398     }
399 
400   return file;
401 }
402 
403 EXRLoader*
exr_loader_ref(EXRLoader * loader)404 exr_loader_ref (EXRLoader *loader)
405 {
406   ++loader->refcount_;
407   return loader;
408 }
409 
410 void
exr_loader_unref(EXRLoader * loader)411 exr_loader_unref (EXRLoader *loader)
412 {
413   if (--loader->refcount_ == 0)
414     {
415       delete loader;
416     }
417 }
418 
419 int
exr_loader_get_width(EXRLoader * loader)420 exr_loader_get_width (EXRLoader *loader)
421 {
422   int width;
423   // Don't let any exceptions propagate to the C layer.
424   try
425     {
426       width = loader->getWidth();
427     }
428   catch (...)
429     {
430       width = -1;
431     }
432 
433   return width;
434 }
435 
436 int
exr_loader_get_height(EXRLoader * loader)437 exr_loader_get_height (EXRLoader *loader)
438 {
439   int height;
440   // Don't let any exceptions propagate to the C layer.
441   try
442     {
443       height = loader->getHeight();
444     }
445   catch (...)
446     {
447       height = -1;
448     }
449 
450   return height;
451 }
452 
453 EXRImageType
exr_loader_get_image_type(EXRLoader * loader)454 exr_loader_get_image_type (EXRLoader *loader)
455 {
456   // This does not throw.
457   return loader->getImageType();
458 }
459 
460 EXRPrecision
exr_loader_get_precision(EXRLoader * loader)461 exr_loader_get_precision (EXRLoader *loader)
462 {
463   // This does not throw.
464   return loader->getPrecision();
465 }
466 
467 int
exr_loader_has_alpha(EXRLoader * loader)468 exr_loader_has_alpha (EXRLoader *loader)
469 {
470   // This does not throw.
471   return loader->hasAlpha();
472 }
473 
474 GimpColorProfile *
exr_loader_get_profile(EXRLoader * loader)475 exr_loader_get_profile (EXRLoader *loader)
476 {
477   return loader->getProfile ();
478 }
479 
480 gchar *
exr_loader_get_comment(EXRLoader * loader)481 exr_loader_get_comment (EXRLoader *loader)
482 {
483   return loader->getComment ();
484 }
485 
486 guchar *
exr_loader_get_exif(EXRLoader * loader,guint * size)487 exr_loader_get_exif (EXRLoader *loader,
488                      guint *size)
489 {
490   return loader->getExif (size);
491 }
492 
493 guchar *
exr_loader_get_xmp(EXRLoader * loader,guint * size)494 exr_loader_get_xmp (EXRLoader *loader,
495                     guint *size)
496 {
497   return loader->getXmp (size);
498 }
499 
500 int
exr_loader_read_pixel_row(EXRLoader * loader,char * pixels,int bpp,int row)501 exr_loader_read_pixel_row (EXRLoader *loader,
502                            char *pixels,
503                            int bpp,
504                            int row)
505 {
506   int retval = -1;
507   // Don't let any exceptions propagate to the C layer.
508   try
509     {
510       retval = loader->readPixelRow(pixels, bpp, row);
511     }
512   catch (...)
513     {
514       retval = -1;
515     }
516 
517   return retval;
518 }
519