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