1 /* ***** BEGIN LICENSE BLOCK *****
2  * This file is part of openfx-io <https://github.com/MrKepzie/openfx-io>,
3  * Copyright (C) 2013-2018 INRIA
4  *
5  * openfx-io is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * openfx-io is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with openfx-io.  If not, see <http://www.gnu.org/licenses/gpl-2.0.html>
17  * ***** END LICENSE BLOCK ***** */
18 
19 /*
20  * OFX PNG reader plugin.
21  * Reads an image in the PNG format
22  */
23 
24 #include <cstdio> // fopen, fread...
25 #include <iomanip>
26 #include <locale>
27 #include <cstdlib>
28 #include <cmath>
29 #include <sstream>
30 #include <algorithm>
31 #include <png.h> // includes setjmp.h
32 #include <zlib.h>
33 
34 #include "GenericReader.h"
35 #include "GenericOCIO.h"
36 #include "ofxsMacros.h"
37 #include "ofxsFileOpen.h"
38 
39 using namespace OFX;
40 using namespace OFX::IO;
41 #ifdef OFX_IO_USING_OCIO
42 namespace OCIO = OCIO_NAMESPACE;
43 #endif
44 
45 using std::string;
46 using std::stringstream;
47 using std::vector;
48 using std::map;
49 
50 OFXS_NAMESPACE_ANONYMOUS_ENTER
51 
52 #define kPluginName "ReadPNG"
53 #define kPluginGrouping "Image/Readers"
54 #define kPluginDescription "Read PNG files."
55 #define kPluginIdentifier "fr.inria.openfx.ReadPNG"
56 #define kPluginVersionMajor 1 // Incrementing this number means that you have broken backwards compatibility of the plug-in.
57 #define kPluginVersionMinor 0 // Increment this when you have fixed a bug or made it faster.
58 #define kPluginEvaluation 92 // better than ReadOIIO
59 
60 #define kParamShowMetadata "showMetadata"
61 #define kParamShowMetadataLabel "Image Info..."
62 #define kParamShowMetadataHint "Shows information and metadata from the image at current time."
63 
64 #define kParamLibraryInfo "libraryInfo"
65 #define kParamLibraryInfoLabel "libpng Info...", "Display information about the underlying library."
66 
67 // All PNG images represent RGBA.
68 // Single-channel images are Y
69 // Two-channel images are Y+A
70 // Three-channel images are RGB
71 // Four-channel images are RGBA
72 // RGB may be compacted to Y and A may be removed when writing a PNG image.
73 // User can still use a Shuffle plugin to select just the Alpha channel.
74 //
75 // References:
76 // https://www.w3.org/TR/PNG/#4Concepts.RGBMerging
77 // https://www.w3.org/TR/PNG/#4Concepts.Alpha-indexing
78 // Issue:
79 // https://github.com/MrKepzie/openfx-io/issues/24
80 
81 #define kSupportsRGBA true
82 #define kSupportsRGB true
83 #define kSupportsXY false
84 #define kSupportsAlpha false
85 #define kSupportsTiles false
86 
87 #define OFX_IO_LIBPNG_VERSION (PNG_LIBPNG_VER_MAJOR * 10000 + PNG_LIBPNG_VER_MINOR * 100 + PNG_LIBPNG_VER_RELEASE)
88 
89 // Try to deduce endianness
90 #if (defined(_WIN32) || defined(__i386__) || defined(__x86_64__ ) )
91 #  ifndef __LITTLE_ENDIAN__
92 #    define __LITTLE_ENDIAN__ 1
93 #    undef __BIG_ENDIAN__
94 #  endif
95 #endif
96 
97 inline bool
littleendian(void)98 littleendian (void)
99 {
100 #if defined(__BIG_ENDIAN__)
101 
102     return false;
103 #elif defined(__LITTLE_ENDIAN__)
104 
105     return true;
106 #else
107     // Otherwise, do something quick to compute it
108     int i = 1;
109 
110     return *( (char *) &i );
111 #endif
112 }
113 
114 template<typename T>
115 static inline void
unused(const T &)116 unused(const T&) {}
117 
118 // the following ICC check function is from libpng-1.6.26/png.c
119 
120 /* Information about the known ICC sRGB profiles */
121 static const struct
122 {
123    png_uint_32 adler, crc, length;
124    png_uint_32 md5[4];
125    png_byte    have_md5;
126    png_byte    is_broken;
127    png_uint_16 intent;
128 
129 #  define PNG_MD5(a,b,c,d) { a, b, c, d }, (a!=0)||(b!=0)||(c!=0)||(d!=0)
130 #  define PNG_ICC_CHECKSUM(adler, crc, md5, intent, broke, date, length, fname)\
131       { adler, crc, length, md5, broke, intent },
132 
133 } png_sRGB_checks[] =
134 {
135    /* This data comes from contrib/tools/checksum-icc run on downloads of
136     * all four ICC sRGB profiles from www.color.org.
137     */
138    /* adler32, crc32, MD5[4], intent, date, length, file-name */
139    PNG_ICC_CHECKSUM(0x0a3fd9f6, 0x3b8772b9,
140        PNG_MD5(0x29f83dde, 0xaff255ae, 0x7842fae4, 0xca83390d), 0, 0,
141        "2009/03/27 21:36:31", 3048, "sRGB_IEC61966-2-1_black_scaled.icc")
142 
143    /* ICC sRGB v2 perceptual no black-compensation: */
144    PNG_ICC_CHECKSUM(0x4909e5e1, 0x427ebb21,
145        PNG_MD5(0xc95bd637, 0xe95d8a3b, 0x0df38f99, 0xc1320389), 1, 0,
146        "2009/03/27 21:37:45", 3052, "sRGB_IEC61966-2-1_no_black_scaling.icc")
147 
148    PNG_ICC_CHECKSUM(0xfd2144a1, 0x306fd8ae,
149        PNG_MD5(0xfc663378, 0x37e2886b, 0xfd72e983, 0x8228f1b8), 0, 0,
150        "2009/08/10 17:28:01", 60988, "sRGB_v4_ICC_preference_displayclass.icc")
151 
152    /* ICC sRGB v4 perceptual */
153    PNG_ICC_CHECKSUM(0x209c35d2, 0xbbef7812,
154        PNG_MD5(0x34562abf, 0x994ccd06, 0x6d2c5721, 0xd0d68c5d), 0, 0,
155        "2007/07/25 00:05:37", 60960, "sRGB_v4_ICC_preference.icc")
156 
157    /* The following profiles have no known MD5 checksum. If there is a match
158     * on the (empty) MD5 the other fields are used to attempt a match and
159     * a warning is produced.  The first two of these profiles have a 'cprt' tag
160     * which suggests that they were also made by Hewlett Packard.
161     */
162    PNG_ICC_CHECKSUM(0xa054d762, 0x5d5129ce,
163        PNG_MD5(0x00000000, 0x00000000, 0x00000000, 0x00000000), 1, 0,
164        "2004/07/21 18:57:42", 3024, "sRGB_IEC61966-2-1_noBPC.icc")
165 
166    /* This is a 'mntr' (display) profile with a mediaWhitePointTag that does not
167     * match the D50 PCS illuminant in the header (it is in fact the D65 values,
168     * so the white point is recorded as the un-adapted value.)  The profiles
169     * below only differ in one byte - the intent - and are basically the same as
170     * the previous profile except for the mediaWhitePointTag error and a missing
171     * chromaticAdaptationTag.
172     */
173    PNG_ICC_CHECKSUM(0xf784f3fb, 0x182ea552,
174        PNG_MD5(0x00000000, 0x00000000, 0x00000000, 0x00000000), 0, 1/*broken*/,
175        "1998/02/09 06:49:00", 3144, "HP-Microsoft sRGB v2 perceptual")
176 
177    PNG_ICC_CHECKSUM(0x0398f3fc, 0xf29e526d,
178        PNG_MD5(0x00000000, 0x00000000, 0x00000000, 0x00000000), 1, 1/*broken*/,
179        "1998/02/09 06:49:00", 3144, "HP-Microsoft sRGB v2 media-relative")
180 };
181 
182 static int
png_compare_ICC_profile_with_sRGB(png_structp,png_bytep profile,uLong adler)183 png_compare_ICC_profile_with_sRGB(png_structp /*png_ptr*/,
184                                   png_bytep profile,
185                                   uLong adler)
186 {
187    /* The quick check is to verify just the MD5 signature and trust the
188     * rest of the data.  Because the profile has already been verified for
189     * correctness this is safe.  png_colorspace_set_sRGB will check the 'intent'
190     * field too, so if the profile has been edited with an intent not defined
191     * by sRGB (but maybe defined by a later ICC specification) the read of
192     * the profile will fail at that point.
193     */
194 
195    png_uint_32 length = 0;
196    png_uint_32 intent = 0x10000; /* invalid */
197 #if PNG_sRGB_PROFILE_CHECKS > 1
198    uLong crc = 0; /* the value for 0 length data */
199 #endif
200    unsigned int i;
201 
202 #ifdef PNG_SET_OPTION_SUPPORTED
203    /* First see if PNG_SKIP_sRGB_CHECK_PROFILE has been set to "on" */
204    //if (((png_ptr->options >> PNG_SKIP_sRGB_CHECK_PROFILE) & 3) ==
205    //            PNG_OPTION_ON)
206    //   return 0;
207 #endif
208 
209    for (i=0; i < (sizeof png_sRGB_checks) / (sizeof png_sRGB_checks[0]); ++i)
210    {
211       if (png_get_uint_32(profile+84) == png_sRGB_checks[i].md5[0] &&
212          png_get_uint_32(profile+88) == png_sRGB_checks[i].md5[1] &&
213          png_get_uint_32(profile+92) == png_sRGB_checks[i].md5[2] &&
214          png_get_uint_32(profile+96) == png_sRGB_checks[i].md5[3])
215       {
216          /* This may be one of the old HP profiles without an MD5, in that
217           * case we can only use the length and Adler32 (note that these
218           * are not used by default if there is an MD5!)
219           */
220 #        if PNG_sRGB_PROFILE_CHECKS == 0
221             if (png_sRGB_checks[i].have_md5 != 0)
222                return 1+png_sRGB_checks[i].is_broken;
223 #        endif
224 
225          /* Profile is unsigned or more checks have been configured in. */
226          if (length == 0)
227          {
228             length = png_get_uint_32(profile);
229             intent = png_get_uint_32(profile+64);
230          }
231 
232          /* Length *and* intent must match */
233          if (length == (png_uint_32) png_sRGB_checks[i].length &&
234             intent == (png_uint_32) png_sRGB_checks[i].intent)
235          {
236             /* Now calculate the adler32 if not done already. */
237             if (adler == 0)
238             {
239                adler = adler32(0, NULL, 0);
240                adler = adler32(adler, (const Bytef *)profile, length);
241             }
242 
243             if (adler == png_sRGB_checks[i].adler)
244             {
245                /* These basic checks suggest that the data has not been
246                 * modified, but if the check level is more than 1 perform
247                 * our own crc32 checksum on the data.
248                 */
249 #              if PNG_sRGB_PROFILE_CHECKS > 1
250                   if (crc == 0)
251                   {
252                      crc = crc32(0, NULL, 0);
253                      crc = crc32(crc, profile, length);
254                   }
255 
256                   /* So this check must pass for the 'return' below to happen.
257                    */
258                   if (crc == png_sRGB_checks[i].crc)
259 #              endif
260                {
261                   if (png_sRGB_checks[i].is_broken != 0)
262                   {
263                      /* These profiles are known to have bad data that may cause
264                       * problems if they are used, therefore attempt to
265                       * discourage their use, skip the 'have_md5' warning below,
266                       * which is made irrelevant by this error.
267                       */
268                      //png_chunk_report(png_ptr, "known incorrect sRGB profile",
269                      //    PNG_CHUNK_ERROR);
270                   }
271 
272                   /* Warn that this being done; this isn't even an error since
273                    * the profile is perfectly valid, but it would be nice if
274                    * people used the up-to-date ones.
275                    */
276                   else if (png_sRGB_checks[i].have_md5 == 0)
277                   {
278                      //png_chunk_report(png_ptr,
279                      //    "out-of-date sRGB profile with no signature",
280                      //    PNG_CHUNK_WARNING);
281                   }
282 
283                   return 1+png_sRGB_checks[i].is_broken;
284                }
285             }
286 
287 # if PNG_sRGB_PROFILE_CHECKS > 0
288          /* The signature matched, but the profile had been changed in some
289           * way.  This probably indicates a data error or uninformed hacking.
290           * Fall through to "no match".
291           */
292          //png_chunk_report(png_ptr,
293          //    "Not recognizing known sRGB profile that has been edited",
294          //    PNG_CHUNK_WARNING);
295          break;
296 # endif
297          }
298       }
299    }
300 
301    return 0; /* no match */
302 }
303 
304 /// Helper function - reads background colour.
305 ///
306 inline bool
get_background(png_structp sp,png_infop ip,BitDepthEnum bitdepth,int real_bit_depth,int nChannels,float * red,float * green,float * blue)307 get_background (png_structp sp,
308                 png_infop ip,
309                 BitDepthEnum bitdepth,
310                 int real_bit_depth,
311                 int nChannels,
312                 float *red,
313                 float *green,
314                 float *blue)
315 {
316     if ( setjmp ( png_jmpbuf (sp) ) ) {
317         return false;
318     }
319     if ( !png_get_valid (sp, ip, PNG_INFO_bKGD) ) {
320         return false;
321     }
322 
323     png_color_16p bg;
324     png_get_bKGD (sp, ip, &bg);
325     if (bitdepth == eBitDepthUShort) {
326         *red   = bg->red   * (1.f / 65535);
327         *green = bg->green * (1.f / 65535);
328         *blue  = bg->blue  * (1.f / 65535);
329     } else if ( (nChannels < 3) && (real_bit_depth < 8) ) {
330         if (real_bit_depth == 1) {
331             *red = *green = *blue = (bg->gray ? 1 : 0);
332         } else if (real_bit_depth == 2) {
333             *red = *green = *blue = bg->gray * (1.f / 3);
334         } else { // 4 bits
335             *red = *green = *blue = bg->gray * (1.f / 15);
336         }
337     } else {
338         *red   = bg->red   * (1.f / 255);
339         *green = bg->green * (1.f / 255);
340         *blue  = bg->blue  * (1.f / 255);
341     }
342 
343     return true;
344 }
345 
346 enum PNGColorSpaceEnum
347 {
348     ePNGColorSpaceLinear,
349     ePNGColorSpacesRGB,
350     ePNGColorSpaceRec709,
351     ePNGColorSpaceGammaCorrected
352 };
353 
354 /// Read information from a PNG file and fill the ImageSpec accordingly.
355 ///
356 inline void
getPNGInfo(png_structp sp,png_infop ip,int * x1_p,int * y1_p,int * width_p,int * height_p,double * par_p,int * nChannels_p,BitDepthEnum * bit_depth_p,int * real_bit_depth_p,int * color_type_p,PNGColorSpaceEnum * colorspace_p,double * gamma_p,int * interlace_type_p,OfxRGBColourF * bg_p,unsigned char ** iccprofile_data_p,unsigned int * iccprofile_length_p,bool * isResolutionInches_p,double * xResolution_p,double * yResolution_p,string * date_p,map<string,string> * additionalComments_p)357 getPNGInfo(png_structp sp,
358            png_infop ip,
359            int* x1_p,
360            int* y1_p,
361            int* width_p,
362            int* height_p,
363            double* par_p,
364            int* nChannels_p,
365            BitDepthEnum* bit_depth_p,
366            int* real_bit_depth_p,
367            int* color_type_p,
368            PNGColorSpaceEnum* colorspace_p, // optional
369            double* gamma_p, // optional
370            int* interlace_type_p, // optional
371            OfxRGBColourF* bg_p, // optional
372            unsigned char** iccprofile_data_p, // optional
373            unsigned int* iccprofile_length_p, // optional
374            bool* isResolutionInches_p, // optional
375            double* xResolution_p, // optional
376            double* yResolution_p, // optional
377            string* date_p, // optional
378            map<string, string>* additionalComments_p) // optional
379 {
380     png_read_info (sp, ip);
381 
382     // Auto-convert 1-, 2-, and 4- bit images to 8 bits, palette to RGB,
383     // and transparency to alpha.
384     png_set_expand (sp);
385 
386     /* Expand the grayscale to 24-bit RGB if necessary. */
387     png_set_gray_to_rgb(sp);
388 
389     // PNG files are naturally big-endian
390     if ( littleendian() ) {
391         png_set_swap (sp);
392     }
393     png_set_interlace_handling(sp);
394 
395     png_read_update_info (sp, ip);
396 
397 
398     png_uint_32 width, height;
399     png_get_IHDR (sp, ip, &width, &height,
400                   real_bit_depth_p, color_type_p, NULL, NULL, NULL);
401     *width_p = width;
402     *height_p = height;
403     *bit_depth_p = *real_bit_depth_p == 16 ? eBitDepthUShort : eBitDepthUByte;
404     png_byte channels = png_get_channels (sp, ip);
405 #ifndef NDEBUG
406     png_byte color_type = png_get_color_type(sp, ip);
407     assert( ( channels == 1 && (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE) ) ||
408             ( channels == 2 && (color_type == PNG_COLOR_TYPE_GRAY_ALPHA) ) ||
409             ( channels == 3 && (color_type == PNG_COLOR_TYPE_RGB) ) ||
410             ( channels == 4 && (color_type == PNG_COLOR_TYPE_RGB_ALPHA || color_type == PNG_COLOR_TYPE_RGB) ) );
411 #endif
412     *nChannels_p = channels;
413 
414     *x1_p = png_get_x_offset_pixels (sp, ip);
415     *y1_p = png_get_y_offset_pixels (sp, ip);
416 
417     float aspect = (float)png_get_pixel_aspect_ratio (sp, ip);
418     *par_p = 1.;
419     if ( (aspect != 0) && (aspect != 1) ) {
420         *par_p = aspect;
421     }
422 
423     double file_gamma = 1.;
424 
425     if (colorspace_p) {
426         bool ping_found_sRGB = false;
427         bool ping_found_gAMA = false;
428         bool ping_found_cHRM = false;
429         bool ping_found_sRGB_cHRM = false;
430         bool ping_found_iCCP = false;
431 
432         *colorspace_p = ePNGColorSpaceLinear;
433 
434 #ifdef PNG_iCCP_SUPPORTED
435         // Always check the ICC profile, because it may indicate sRGB
436         if ( png_get_valid(sp, ip, PNG_INFO_iCCP) ) {
437             png_charp profile_name = NULL;
438 #if OFX_IO_LIBPNG_VERSION > 10500   /* PNG function signatures changed */
439             png_bytep profile_data = NULL;
440 #else
441             png_charp profile_data = NULL;
442 #endif
443             png_uint_32 profile_length = 0;
444             int compression_type;
445             png_get_iCCP (sp, ip, &profile_name, &compression_type,
446                           &profile_data, &profile_length);
447             if (profile_length && profile_data) {
448                 ping_found_iCCP = true;
449                 if (iccprofile_data_p) {
450 #if OFX_IO_LIBPNG_VERSION > 10500
451                     *iccprofile_data_p = profile_data;
452 #else
453                     *iccprofile_data_p = reinterpret_cast<unsigned char*>(profile_data);
454 #endif
455                 }
456                 if (iccprofile_length_p) {
457                     *iccprofile_length_p = profile_length;
458                 }
459             }
460 
461             /* Is this profile one of the known ICC sRGB profiles?  If it is, just set
462              * the sRGB information.
463              */
464             if (png_compare_ICC_profile_with_sRGB(sp, (png_bytep)profile_data, 0) != 0) {
465                 *colorspace_p = ePNGColorSpacesRGB;
466                 file_gamma = 2.2;
467             }
468         }
469 #endif
470 
471 #ifdef PNG_GAMMA_SUPPORTED
472         // is there a srgb intent ?
473         if (*colorspace_p == ePNGColorSpaceLinear) {
474             int srgb_intent;
475             if (png_get_sRGB (sp, ip, &srgb_intent) == PNG_INFO_sRGB) {
476                 *colorspace_p = ePNGColorSpacesRGB;
477                 file_gamma = 2.2;
478                 ping_found_sRGB = true;
479             }
480             // srgb_intent possible values:
481             // 0: PerceptualIntent
482             // 1: RelativeIntent
483             // 2: SaturationIntent
484             // 3: AbsoluteIntent
485         }
486 #endif
487 
488         // if not is there a gamma ?
489         if (*colorspace_p == ePNGColorSpaceLinear) {
490             if ( png_get_gAMA (sp, ip, &file_gamma) ) {
491                 // guard against division by zero
492                 file_gamma = file_gamma != 0. ? (1. / file_gamma) : 1.;
493                 ping_found_gAMA = true;
494                 if (file_gamma > 1.) {
495                     *colorspace_p = ePNGColorSpaceGammaCorrected;
496                 }
497             }
498 
499             double white_x, white_y, red_x, red_y, green_x, green_y, blue_x,
500             blue_y;
501 
502 #ifdef PNG_cHRM_SUPPORTED
503             if (png_get_cHRM(sp, ip, &white_x, &white_y, &red_x,
504                              &red_y, &green_x, &green_y, &blue_x, &blue_y) == PNG_INFO_cHRM) {
505                 ping_found_cHRM = true;
506 
507                 if (red_x>0.6399f &&
508                     red_x<0.6401f &&
509                     red_y>0.3299f &&
510                     red_y<0.3301f &&
511                     green_x>0.2999f &&
512                     green_x<0.3001f &&
513                     green_y>0.5999f &&
514                     green_y<0.6001f &&
515                     blue_x>0.1499f &&
516                     blue_x<0.1501f &&
517                     blue_y>0.0599f &&
518                     blue_y<0.0601f &&
519                     white_x>0.3126f &&
520                     white_x<0.3128f &&
521                     white_y>0.3289f &&
522                     white_y<0.3291f)
523                     ping_found_sRGB_cHRM = true;
524             }
525 #endif
526 
527             if (!ping_found_sRGB &&
528                 (!ping_found_gAMA ||
529                  (file_gamma > 1/.46 && file_gamma < 1/.45)) &&
530                 (!ping_found_cHRM ||
531                  ping_found_sRGB_cHRM) &&
532                 !ping_found_iCCP) {
533                 file_gamma = 2.2;
534                 *colorspace_p = ePNGColorSpacesRGB;
535                 ping_found_sRGB = true;
536             }
537         }
538         unused(ping_found_sRGB);
539 
540         // if not, deduce from the bitdepth
541         if ( !ping_found_gAMA && (*colorspace_p == ePNGColorSpaceLinear) ) {
542             *colorspace_p = *bit_depth_p == eBitDepthUByte ? ePNGColorSpacesRGB : ePNGColorSpaceRec709;
543         }
544     }
545     if (gamma_p) {
546         *gamma_p = file_gamma;
547     }
548 
549 
550 #ifdef PNG_tIME_SUPPORTED
551     png_timep mod_time;
552     if ( date_p && png_get_tIME (sp, ip, &mod_time) ) {
553         stringstream ss;
554         ss << std::setfill('0') << std::setw(4) << mod_time->year << ':' << std::setw(2) << mod_time->month << ':' << mod_time->day << ' ';
555         ss << mod_time->hour << ':' << mod_time->minute << ':' << mod_time->second;
556         *date_p = ss.str();
557     }
558 #endif
559 
560 #ifdef PNG_TEXT_SUPPORTED
561     if (additionalComments_p) {
562         png_textp text_ptr;
563         int num_comments = png_get_text (sp, ip, &text_ptr, NULL);
564         if (num_comments) {
565             string comments;
566             for (int i = 0; i < num_comments; ++i) {
567                 (*additionalComments_p)[string(text_ptr[i].key)] = string(text_ptr[i].text);
568             }
569         }
570     }
571 #endif
572 
573 #ifdef PNG_pHYs_SUPPORTED
574     if (xResolution_p && yResolution_p) {
575         int unit;
576         png_uint_32 resx, resy;
577         if ( png_get_pHYs (sp, ip, &resx, &resy, &unit) ) {
578             float scale = 1;
579             if (unit == PNG_RESOLUTION_METER) {
580                 // Convert to inches, to match most other formats
581                 scale = 2.54 / 100.0;
582                 *isResolutionInches_p = true;
583             } else {
584                 *isResolutionInches_p = false;
585             }
586             *xResolution_p = (double)resx * scale;
587             *yResolution_p = (double)resy * scale;
588         }
589     }
590 #endif
591 
592     if (bg_p) {
593         get_background (sp, ip, *bit_depth_p, *real_bit_depth_p, *nChannels_p, &bg_p->r, &bg_p->g, &bg_p->b);
594     }
595     if (interlace_type_p) {
596         *interlace_type_p = png_get_interlace_type (sp, ip);
597     }
598 } // getPNGInfo
599 
600 class ReadPNGPlugin
601     : public GenericReaderPlugin
602 {
603 public:
604 
605     ReadPNGPlugin(OfxImageEffectHandle handle, const vector<string>& extensions);
606 
607     virtual ~ReadPNGPlugin();
608 
609 private:
610 
611     virtual void changedParam(const InstanceChangedArgs &args, const string &paramName) OVERRIDE FINAL;
isVideoStream(const string &)612     virtual bool isVideoStream(const string& /*filename*/) OVERRIDE FINAL { return false; }
613 
614     virtual void decode(const string& filename, OfxTime time, int view, bool isPlayback, const OfxRectI& renderWindow, float *pixelData, const OfxRectI& bounds, PixelComponentEnum pixelComponents, int pixelComponentCount, int rowBytes) OVERRIDE FINAL;
615     virtual bool getFrameBounds(const string& filename, OfxTime time, int view, OfxRectI *bounds, OfxRectI *format, double *par, string *error, int* tile_width, int* tile_height) OVERRIDE FINAL;
616 
617     /**
618      * @brief Called when the input image/video file changed.
619      *
620      * returns true if file exists and parameters successfully guessed, false in case of error.
621      *
622      * This function is only called once: when the filename is first set.
623      *
624      * Besides returning colorspace, premult, components, and componentcount, if it returns true
625      * this function may also set extra format-specific parameters using Param::setValue.
626      * The parameters must not be animated, since their value must remain the same for a whole sequence.
627      *
628      * You shouldn't do any strong processing as this is called on the main thread and
629      * the getRegionOfDefinition() and  decode() should open the file in a separate thread.
630      *
631      * The colorspace may be set if available, else a default colorspace is used.
632      *
633      * You must also return the premultiplication state and pixel components of the image.
634      * When reading an image sequence, this is called only for the first image when the user actually selects the new sequence.
635      **/
636     virtual bool guessParamsFromFilename(const string& filename, string *colorspace, PreMultiplicationEnum *filePremult, PixelComponentEnum *components, int *componentCount) OVERRIDE FINAL;
637     static void openFile(const string& filename,
638                          png_structp* png,
639                          png_infop* info,
640                          std::FILE** file);
641 
642     string metadata(const string& filename);
643 };
644 
645 
ReadPNGPlugin(OfxImageEffectHandle handle,const vector<string> & extensions)646 ReadPNGPlugin::ReadPNGPlugin(OfxImageEffectHandle handle,
647                              const vector<string>& extensions)
648     : GenericReaderPlugin(handle, extensions, kSupportsRGBA, kSupportsRGB, kSupportsXY, kSupportsAlpha, kSupportsTiles, false)
649 {
650 }
651 
~ReadPNGPlugin()652 ReadPNGPlugin::~ReadPNGPlugin()
653 {
654 }
655 
656 #define PNG_BYTES_TO_CHECK 8
657 
658 string
metadata(const string & filename)659 ReadPNGPlugin::metadata(const string& filename)
660 {
661     // Open the file
662     std::FILE *image = fopen_utf8(filename.c_str(), "rb");
663     if (image == NULL) {
664         setPersistentMessage(Message::eMessageError, "", string("ReadPNG: cannot open file ") + filename);
665         throwSuiteStatusException(kOfxStatFailed);
666 
667         return string();
668     }
669 
670     stringstream ss;
671 
672     ss << "file: " << filename << std::endl;
673 
674     {
675         unsigned char sig[8];
676         // Check that it really is a PNG file
677         /* Read in some of the signature bytes */
678         if ( (std::fread(sig, 1, PNG_BYTES_TO_CHECK, image) != PNG_BYTES_TO_CHECK) ||
679              png_sig_cmp(sig, 0, PNG_BYTES_TO_CHECK) ) {
680             ss << "  This file is not a valid PNG file\n";
681             std::fclose (image);
682 
683             return ss.str();
684         }
685     }
686 
687     // Start decompressing
688     png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL,
689                                              NULL, NULL);
690     if (png == NULL) {
691         ss << "Could not create a PNG read structure (out of memory?)\n";
692         std::fclose(image);
693 
694         return ss.str();
695     }
696     png_infop info = png_create_info_struct(png);
697     if (info == NULL) {
698         ss << "Could not create PNG info structure (out of memory?)\n";
699         png_destroy_read_struct(&png, NULL, NULL);
700         std::fclose(image);
701 
702         return ss.str();
703     }
704 
705     if ( setjmp( png_jmpbuf(png) ) ) {
706         png_destroy_read_struct(&png, &info, NULL);
707         ss << "Could not set PNG jump value";
708         std::fclose(image);
709 
710         return ss.str();
711     }
712     // Get ready for IO and tell the API we have already read the image signature
713     png_init_io (png, image);
714     png_set_sig_bytes (png, PNG_BYTES_TO_CHECK);
715     png_read_info (png, info);
716     png_uint_32 width;
717     png_uint_32 height;
718     int bit_depth;
719     int color_type;
720     png_get_IHDR (png, info, &width, &height, &bit_depth, &color_type, NULL, NULL, NULL);
721 
722     ///////////////////////////////////////////////////////////////////////////
723     // Start displaying information
724     ///////////////////////////////////////////////////////////////////////////
725 
726     ss << "  Image Width: " << width << " Image Length: " << height << std::endl;
727     int pixel_depth = bit_depth * png_get_channels(png, info);
728     ss << "  Bitdepth (Bits/Sample): " << bit_depth << std::endl;
729     ss << "  Channels (Samples/Pixel): " << (int)png_get_channels(png, info) << std::endl;
730     ss << "  Pixel depth (Pixel Depth): " << pixel_depth << std::endl;  // Does this add value?
731 
732     // Photometric interp packs a lot of information
733     ss << "  Colour Type (Photometric Interpretation): ";
734 
735     switch (color_type) {
736     case PNG_COLOR_TYPE_GRAY: {
737         ss << "GRAYSCALE ";
738         png_bytep trans = NULL;
739         int num_trans = 0;
740         if (png_get_tRNS(png, info, &trans, &num_trans, NULL) == PNG_INFO_tRNS) {
741             ss << "(" << num_trans << " transparent) ";
742         }
743         break;
744     }
745     case PNG_COLOR_TYPE_PALETTE: {
746         ss << "PALETTED COLOUR ";
747         int num_palette = 0;
748         png_color *palette = NULL;
749         png_get_PLTE(png, info, &palette, &num_palette);
750         int num_trans = 0;
751         png_bytep trans = NULL;
752         ;
753         ss << "(" << num_palette << " colours";
754         if (png_get_tRNS(png, info, &trans, &num_trans, NULL) == PNG_INFO_tRNS) {
755             ss << ", " << num_trans << " transparent";
756         }
757         ss << ")";
758         break;
759     }
760     case PNG_COLOR_TYPE_RGB: {
761         ss << "RGB ";
762         break;
763     }
764     case PNG_COLOR_TYPE_RGB_ALPHA: {
765         ss << "RGB with alpha channel ";
766         break;
767     }
768     case PNG_COLOR_TYPE_GRAY_ALPHA: {
769         ss << "GRAYSCALE with alpha channel ";
770         break;
771     }
772     default: {
773         ss << "Unknown photometric interpretation!";
774         break;
775     }
776     }
777     ss << std::endl;
778 
779     ss << "  Image filter: ";
780     switch ( png_get_filter_type(png, info) ) {
781     case PNG_FILTER_TYPE_BASE:
782         ss << "Single row per byte filter ";
783         break;
784 
785     case PNG_INTRAPIXEL_DIFFERENCING:
786         ss << "Intrapixel differencing (MNG only) ";
787         break;
788 
789     default:
790         ss << "Unknown filter! ";
791         break;
792     }
793     ss << std::endl;
794 
795 #if defined(PNG_READ_INTERLACING_SUPPORTED)
796     ss << "  Interlacing: ";
797     switch ( png_get_interlace_type(png, info) ) {
798     case PNG_INTERLACE_NONE:
799         ss << "No interlacing ";
800         break;
801 
802     case PNG_INTERLACE_ADAM7:
803         ss << "Adam7 interlacing ";
804         break;
805 
806     default:
807         ss << "Unknown interlacing ";
808         break;
809     }
810     ss << std::endl;
811 #endif
812 
813     ss << "  Compression Scheme: ";
814     switch ( png_get_compression_type(png, info) ) {
815     case PNG_COMPRESSION_TYPE_BASE:
816         ss << "Deflate method 8, 32k window";
817         break;
818 
819     default:
820         ss << "Unknown compression scheme!";
821         break;
822     }
823     ss << std::endl;
824 
825 #ifdef PNG_pHYs_SUPPORTED
826     {
827         png_uint_32 res_x, res_y;
828         int unit_type;
829 
830         if (png_get_pHYs(png, info, &res_x, &res_y,
831                          &unit_type) == PNG_INFO_pHYs) {
832             ss << "  Resolution: " << res_x << ", " << res_y << " ";
833             switch (unit_type) {
834             case PNG_RESOLUTION_UNKNOWN:
835                 ss << "(unit unknown)";
836                 break;
837 
838             case PNG_RESOLUTION_METER:
839                 ss << "(pixels per meter)";
840                 break;
841 
842             default:
843                 ss << "(Unknown value for unit stored)";
844                 break;
845             }
846             ss << std::endl;
847         }
848     }
849 #endif
850 
851     // FillOrder is always msb-to-lsb, big endian
852     //ss << "  FillOrder: msb-to-lsb\n  Byte Order: Network (Big Endian)\n";
853 
854 #ifdef PNG_cHRM_SUPPORTED
855     {
856         double white_x, white_y, red_x, red_y, green_x, green_y, blue_x,
857                blue_y;
858 
859         if (png_get_cHRM(png, info, &white_x, &white_y, &red_x,
860                          &red_y, &green_x, &green_y, &blue_x, &blue_y) == PNG_INFO_cHRM) {
861             ss << "  CIE white point: " << white_x  << ", " << white_y  << std::endl;
862             ss << "  CIE chromaticities: ";
863             ss << "red = "  << red_x    << ", " << red_y    << "; ";
864             ss << "green =" << green_x  << ", " << green_y  << "; ";
865             ss << "blue ="  << blue_x   << ", " << blue_y   << std::endl;
866         }
867     }
868 #endif
869 #ifdef PNG_gAMA_SUPPORTED
870     {
871         double gamma;
872 
873         if (png_get_gAMA(png, info, &gamma) == PNG_INFO_gAMA) {
874             ss << "  Gamma: " << gamma  << std::endl;
875         }
876     }
877 #endif
878 #ifdef PNG_iCCP_SUPPORTED
879     {
880         png_charp name;
881 #if PNG_LIBPNG_VER_SONUM >= 15
882         png_bytep profile;
883 #else
884         png_charp profile;
885 #endif
886         png_uint_32 proflen;
887         int compression_type;
888 
889         if (png_get_iCCP(png, info, &name, &compression_type,
890                          &profile, &proflen) == PNG_INFO_iCCP) {
891             ss << "  ICC profile: " << name;
892         }
893     }
894 #endif
895 #ifdef PNG_sRGB_SUPPORTED
896     {
897         int intent;
898 
899         if (png_get_sRGB(png, info, &intent) == PNG_INFO_sRGB) {
900             ss << "  sRGB intent: ";
901             switch (intent) {
902             case PNG_sRGB_INTENT_PERCEPTUAL:
903                 ss << "perceptual";
904                 break;
905             case PNG_sRGB_INTENT_RELATIVE:
906                 ss << "relative";
907                 break;
908             case PNG_sRGB_INTENT_SATURATION:
909                 ss << "saturation";
910                 break;
911             case PNG_sRGB_INTENT_ABSOLUTE:
912                 ss << "absolute";
913                 break;
914             }
915             ss << std::endl;
916         }
917     }
918 #endif
919 #ifdef PNG_bKGD_SUPPORTED
920     {
921         png_color_16p background;
922 
923         if (png_get_bKGD(png, info, &background) == PNG_INFO_bKGD) {
924             ss << "  Background color present\n";
925         }
926     }
927 #endif
928 #ifdef PNG_hIST_SUPPORTED
929     {
930         png_uint_16p hist;
931 
932         if (png_get_hIST(png, info, &hist) == PNG_INFO_hIST) {
933             ss << "  Histogram present\n";
934         }
935     }
936 #endif
937 #ifdef PNG_oFFs_SUPPORTED
938     {
939         png_int_32 offset_x, offset_y;
940         int unit_type;
941 
942         if (png_get_oFFs(png, info, &offset_x, &offset_y, &unit_type) == PNG_INFO_oFFs) {
943             // see http://www.libpng.org/pub/png/spec/register/pngext-1.4.0-pdg.html#C.oFFs
944             const char* unit = (unit_type == PNG_OFFSET_PIXEL) ? "px" : "um";
945             ss << "  Offset: " << offset_x << unit << ", " << offset_y << unit << std::endl;
946         }
947     }
948 #endif
949 #ifdef PNG_pCAL_SUPPORTED
950     {
951         png_charp purpose, units;
952         png_charpp params;
953         png_int_32 X0, X1;
954         int type, nparams;
955 
956         if (png_get_pCAL(png, info, &purpose, &X0, &X1, &type,
957                          &nparams, &units, &params) == PNG_INFO_pCAL) {
958             // see http://www.libpng.org/pub/png/spec/register/pngext-1.4.0-pdg.html#C.pCAL
959             ss << "  Physical calibration chunk present\n";
960         }
961     }
962 #endif
963 #ifdef PNG_sBIT_SUPPORTED
964     {
965         png_color_8p sig_bit;
966 
967         if (png_get_sBIT(png, info, &sig_bit) == PNG_INFO_sBIT) {
968             ss << "  Significant bits per channel: " << sig_bit << std::endl;
969         }
970     }
971 #endif
972 #ifdef PNG_sCAL_SUPPORTED
973     {
974         int unit_type;
975         double scal_width, scal_height;
976 
977         if (png_get_sCAL(png, info, &unit_type, &scal_width,
978                          &scal_height) == PNG_INFO_sCAL) {
979             // see http://www.libpng.org/pub/png/spec/register/pngext-1.4.0-pdg.html#C.sCAL
980             const char* unit = unit_type == PNG_SCALE_UNKNOWN ? "" : (unit_type == PNG_SCALE_METER ? "m" : "rad");
981             ss << "  Scale: " << scal_width << unit << " x " << scal_height << unit << std::endl;
982         }
983     }
984 #endif
985 
986 #if defined(PNG_TEXT_SUPPORTED)
987     {
988         png_textp text;
989         int num_text = 0;
990 
991         if (png_get_text(png, info, &text, &num_text) > 0) {
992             // Text comments
993             ss << "  Number of text strings: " << num_text << std::endl;
994 
995             for (int i = 0; i < num_text; ++i) {
996                 ss << "   " << text[i].key << " ";
997 
998                 switch (text[1].compression) {
999                 case -1:
1000                     ss << "(tEXt uncompressed)";
1001                     break;
1002 
1003                 case 0:
1004                     ss << "(xTXt deflate compressed)";
1005                     break;
1006 
1007                 case 1:
1008                     ss << "(iTXt uncompressed)";
1009                     break;
1010 
1011                 case 2:
1012                     ss << "(iTXt deflate compressed)";
1013                     break;
1014 
1015                 default:
1016                     ss << "(unknown compression)";
1017                     break;
1018                 }
1019 
1020                 ss << ": ";
1021                 int j = 0;
1022                 while (text[i].text[j] != '\0') {
1023                     if (text[i].text[j] == '\n') {
1024                         ss << std::endl;
1025                     } else {
1026                         ss << text[i].text[j];
1027                     }
1028                     j++;
1029                 }
1030 
1031                 ss << std::endl;
1032             }
1033         }
1034     }
1035 #endif // if defined(PNG_TEXT_SUPPORTED)
1036 
1037     // This cleans things up for us in the PNG library
1038     std::fclose(image);
1039     png_destroy_read_struct (&png, &info, NULL);
1040 
1041     return ss.str();
1042 } // ReadPNGPlugin::metadata
1043 
1044 void
changedParam(const InstanceChangedArgs & args,const string & paramName)1045 ReadPNGPlugin::changedParam(const InstanceChangedArgs &args,
1046                             const string &paramName)
1047 {
1048     if (paramName == kParamLibraryInfo) {
1049         string msg = (string() +
1050                       "libpng version (compiled with / running with): " + PNG_LIBPNG_VER_STRING + '/' + png_libpng_ver + '\n' +
1051                       "zlib version (compiled with / running with): " + ZLIB_VERSION + '/' + zlib_version + '\n' +
1052                       png_get_copyright(NULL));
1053         sendMessage(Message::eMessageMessage, "", msg);
1054     } else if (paramName == kParamShowMetadata) {
1055         string filename;
1056         OfxStatus st = getFilenameAtTime(args.time, &filename);
1057         stringstream ss;
1058         if (st == kOfxStatOK) {
1059             ss << metadata(filename);
1060         } else if ( filename.empty() ) {
1061             ss << "Impossible to read image info:\nCould not get filename at time " << args.time << '.';
1062         } else {
1063             ss << "Impossible to read image info:\nCould not read file " << filename << " corresponding to time " << args.time << '.';
1064         }
1065         sendMessage( Message::eMessageMessage, "", ss.str() );
1066     } else {
1067         GenericReaderPlugin::changedParam(args, paramName);
1068     }
1069 }
1070 
1071 void
openFile(const string & filename,png_structp * png,png_infop * info,std::FILE ** file)1072 ReadPNGPlugin::openFile(const string& filename,
1073                         png_structp* png,
1074                         png_infop* info,
1075                         std::FILE** file)
1076 {
1077     *png = NULL;
1078     *info = NULL;
1079     *file = fopen_utf8(filename.c_str(), "rb");
1080     if (!*file) {
1081         throw std::runtime_error("Could not open file: " + filename);
1082     }
1083 
1084     unsigned char sig[8];
1085     if ( std::fread (sig, 1, sizeof(sig), *file) != sizeof(sig) ) {
1086         throw std::runtime_error("Not a PNG file");
1087     }
1088 
1089     if ( png_sig_cmp (sig, 0, 7) ) {
1090         throw std::runtime_error("Not a PNG file");
1091     }
1092 
1093     *png = png_create_read_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
1094     if (!*png) {
1095         std::fclose(*file);
1096         *file = NULL;
1097 
1098         throw std::runtime_error("Could not create PNG read structure");
1099     }
1100 
1101     *info = png_create_info_struct (*png);
1102     if (!*info) {
1103         png_destroy_read_struct (png, NULL, NULL);
1104         std::fclose(*file);
1105         *file = NULL;
1106 
1107         throw std::runtime_error("Could not create PNG info structure");
1108     }
1109     // Must call this setjmp in every function that does PNG reads
1110     if ( setjmp ( png_jmpbuf(*png) ) ) {
1111         png_destroy_read_struct (png, info, NULL);
1112         std::fclose(*file);
1113         *file = NULL;
1114 
1115         throw std::runtime_error("PNG library error");
1116     }
1117 
1118     png_init_io (*png, *file);
1119     png_set_sig_bytes (*png, 8);  // already read 8 bytes
1120 }
1121 
1122 void
decode(const string & filename,OfxTime,int,bool,const OfxRectI & renderWindow,float * pixelData,const OfxRectI & bounds,PixelComponentEnum pixelComponents,int,int rowBytes)1123 ReadPNGPlugin::decode(const string& filename,
1124                       OfxTime /*time*/,
1125                       int /*view*/,
1126                       bool /*isPlayback*/,
1127                       const OfxRectI& renderWindow,
1128                       float *pixelData,
1129                       const OfxRectI& bounds,
1130                       PixelComponentEnum pixelComponents,
1131                       int /*pixelComponentCount*/,
1132                       int rowBytes)
1133 {
1134     if ( (pixelComponents != ePixelComponentRGBA) && (pixelComponents != ePixelComponentRGB) && (pixelComponents != ePixelComponentXY) && (pixelComponents != ePixelComponentAlpha) ) {
1135         setPersistentMessage(Message::eMessageError, "", "PNG: can only read RGBA, RGB or Alpha components images");
1136         throwSuiteStatusException(kOfxStatErrFormat);
1137 
1138         return;
1139     }
1140 
1141     png_structp png;
1142     png_infop info;
1143     FILE* file;
1144 
1145     try {
1146         openFile(filename, &png, &info, &file);
1147     } catch (const std::exception& e) {
1148         setPersistentMessage( Message::eMessageError, "", e.what() );
1149         throwSuiteStatusException(kOfxStatFailed);
1150     }
1151 
1152     int x1, y1, width, height;
1153     int nChannels;
1154     BitDepthEnum bitdepth;
1155     int realbitdepth;
1156     int colorType;
1157     double par;
1158     getPNGInfo(png, info, &x1, &y1, &width, &height, &par, &nChannels, &bitdepth, &realbitdepth, &colorType, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
1159 
1160     assert(renderWindow.x1 >= x1 && renderWindow.y1 >= y1 && renderWindow.x2 <= x1 + width && renderWindow.y2 <= y1 + height);
1161 
1162     std::size_t pngRowBytes = nChannels * width;
1163     if (bitdepth == eBitDepthUShort) {
1164         pngRowBytes *= sizeof(unsigned short);
1165     }
1166 
1167     RamBuffer scratchBuffer(pngRowBytes * height);
1168     unsigned char* tmpData = scratchBuffer.getData();
1169 
1170 
1171     //if (interlace_type != 0) {
1172     vector<unsigned char *> row_pointers(height);
1173     for (int i = 0; i < height; ++i) {
1174         row_pointers[i] = tmpData + i * pngRowBytes;
1175     }
1176 
1177     // Must call this setjmp in every function that does PNG reads
1178     if ( setjmp ( png_jmpbuf (png) ) ) {
1179         png_destroy_read_struct(&png, &info, NULL);
1180         std::fclose(file);
1181         setPersistentMessage(Message::eMessageError, "", "PNG library error");
1182         throwSuiteStatusException(kOfxStatErrFormat);
1183 
1184         return;
1185     }
1186     png_read_image(png, &row_pointers[0]);
1187     png_read_end(png, NULL);
1188     /*
1189        } else {
1190         for (int y = 0; y < height; ++y) {
1191             // Must call this setjmp in every function that does PNG reads
1192             if (setjmp (png_jmpbuf (png))) {
1193                 png_destroy_read_struct(&png, &info, NULL);
1194                 std::fclose(file);
1195                 setPersistentMessage(Message::eMessageError, "", "PNG library error");
1196                 throwSuiteStatusException(kOfxStatErrFormat);
1197 
1198                 return;
1199             }
1200             png_read_row (png, (png_bytep)tmpData + y * pngRowBytes, NULL);
1201         }
1202        }
1203      */
1204 
1205     png_destroy_read_struct(&png, &info, NULL);
1206     std::fclose(file);
1207     file = NULL;
1208 
1209     OfxRectI srcBounds;
1210     srcBounds.x1 = x1;
1211     srcBounds.y1 = y1;
1212     srcBounds.x2 = x1 + width;
1213     srcBounds.y2 = y1 + height;
1214 
1215     PixelComponentEnum srcComponents;
1216     switch (nChannels) {
1217     case 1:
1218         srcComponents = ePixelComponentAlpha;
1219         break;
1220     case 2:
1221         srcComponents = ePixelComponentXY;
1222         break;
1223     case 3:
1224         srcComponents = ePixelComponentRGB;
1225         break;
1226     case 4:
1227         srcComponents = ePixelComponentRGBA;
1228         break;
1229     default:
1230         setPersistentMessage(Message::eMessageError, "", "This plug-in only supports images with 1 to 4 channels");
1231         throwSuiteStatusException(kOfxStatErrFormat);
1232 
1233         return;
1234     }
1235 
1236     convertDepthAndComponents(tmpData, renderWindow, srcBounds, srcComponents, bitdepth, pngRowBytes, pixelData, bounds, pixelComponents, rowBytes);
1237 } // ReadPNGPlugin::decode
1238 
1239 bool
getFrameBounds(const string & filename,OfxTime,int,OfxRectI * bounds,OfxRectI * format,double * par,string * error,int * tile_width,int * tile_height)1240 ReadPNGPlugin::getFrameBounds(const string& filename,
1241                               OfxTime /*time*/,
1242                               int /*view*/,
1243                               OfxRectI *bounds,
1244                               OfxRectI *format,
1245                               double *par,
1246                               string *error,
1247                               int* tile_width,
1248                               int* tile_height)
1249 {
1250     assert(bounds && par);
1251     png_structp png;
1252     png_infop info;
1253     FILE* file;
1254 
1255     try {
1256         openFile(filename, &png, &info, &file);
1257     } catch (const std::exception& e) {
1258         *error = e.what();
1259 
1260         return false;
1261     }
1262 
1263     int x1, y1, width, height;
1264     int nChannels;
1265     BitDepthEnum bitdepth;
1266     int realbitdepth;
1267     int colorType;
1268 
1269     getPNGInfo(png, info, &x1, &y1, &width, &height, par, &nChannels, &bitdepth, &realbitdepth, &colorType, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
1270     png_destroy_read_struct(&png, &info, NULL);
1271     std::fclose(file);
1272     file = NULL;
1273 
1274     bounds->x1 = x1;
1275     bounds->y1 = y1;
1276     bounds->x2 = x1 + width;
1277     bounds->y2 = y1 + height;
1278     *format = *bounds;
1279     *tile_height = *tile_width = 0;
1280 
1281     return true;
1282 }
1283 
1284 /**
1285  * @brief Called when the input image/video file changed.
1286  *
1287  * returns true if file exists and parameters successfully guessed, false in case of error.
1288  *
1289  * This function is only called once: when the filename is first set.
1290  *
1291  * Besides returning colorspace, premult, components, and componentcount, if it returns true
1292  * this function may also set extra format-specific parameters using Param::setValue.
1293  * The parameters must not be animated, since their value must remain the same for a whole sequence.
1294  *
1295  * You shouldn't do any strong processing as this is called on the main thread and
1296  * the getRegionOfDefinition() and  decode() should open the file in a separate thread.
1297  *
1298  * The colorspace may be set if available, else a default colorspace is used.
1299  *
1300  * You must also return the premultiplication state and pixel components of the image.
1301  * When reading an image sequence, this is called only for the first image when the user actually selects the new sequence.
1302  **/
1303 bool
guessParamsFromFilename(const string & filename,string * colorspace,PreMultiplicationEnum * filePremult,PixelComponentEnum * components,int * componentCount)1304 ReadPNGPlugin::guessParamsFromFilename(const string& filename,
1305                                        string *colorspace,
1306                                        PreMultiplicationEnum *filePremult,
1307                                        PixelComponentEnum *components,
1308                                        int *componentCount)
1309 {
1310     assert(colorspace && filePremult && components && componentCount);
1311     png_structp png;
1312     png_infop info;
1313     FILE* file;
1314     try {
1315         openFile(filename, &png, &info, &file);
1316     } catch (const std::exception& e) {
1317         //setPersistentMessage(Message::eMessageError, "", e.what());
1318 
1319         return false;
1320     }
1321 
1322     int x1, y1, width, height;
1323     double par;
1324     int nChannels;
1325     BitDepthEnum bitdepth;
1326     int realbitdepth;
1327     int colorType;
1328     double gamma;
1329     PNGColorSpaceEnum pngColorspace;
1330     getPNGInfo(png, info, &x1, &y1, &width, &height, &par, &nChannels, &bitdepth, &realbitdepth, &colorType, &pngColorspace, &gamma, 0, 0, 0, 0, 0, 0, 0, 0, 0);
1331     png_destroy_read_struct(&png, &info, NULL);
1332     std::fclose(file);
1333     file = NULL;
1334 
1335 #     ifdef OFX_IO_USING_OCIO
1336     switch (pngColorspace) {
1337     case ePNGColorSpaceGammaCorrected:
1338         if (std::fabs(gamma - 1.8) < 0.05) {
1339             if ( _ocio->hasColorspace("Gamma1.8") ) {
1340                 // nuke-default
1341                 *colorspace = "Gamma1.8";
1342             }
1343         } else if (std::fabs(gamma - 2.2) < 0.05) {
1344             if ( _ocio->hasColorspace("Gamma2.2") ) {
1345                 // nuke-default
1346                 *colorspace = "Gamma2.2";
1347             } else if ( _ocio->hasColorspace("VD16") ) {
1348                 // VD16 in blender
1349                 *colorspace = "VD16";
1350             } else if ( _ocio->hasColorspace("vd16") ) {
1351                 // vd16 in spi-anim and spi-vfx
1352                 *colorspace = "vd16";
1353             } else if ( _ocio->hasColorspace("sRGB") ) {
1354                 // nuke-default and blender
1355                 *colorspace = "sRGB";
1356             } else if ( _ocio->hasColorspace("sRGB D65") ) {
1357                 // blender-cycles
1358                 *colorspace = "sRGB D65";
1359             } else if ( _ocio->hasColorspace("sRGB (D60 sim.)") ) {
1360                 // out_srgbd60sim or "sRGB (D60 sim.)" in aces 1.0.0
1361                 *colorspace = "sRGB (D60 sim.)";
1362             } else if ( _ocio->hasColorspace("out_srgbd60sim") ) {
1363                 // out_srgbd60sim or "sRGB (D60 sim.)" in aces 1.0.0
1364                 *colorspace = "out_srgbd60sim";
1365             } else if ( _ocio->hasColorspace("rrt_Gamma2.2") ) {
1366                 // rrt_Gamma2.2 in aces 0.7.1
1367                 *colorspace = "rrt_Gamma2.2";
1368             } else if ( _ocio->hasColorspace("rrt_srgb") ) {
1369                 // rrt_srgb in aces 0.1.1
1370                 *colorspace = "rrt_srgb";
1371             } else if ( _ocio->hasColorspace("srgb8") ) {
1372                 // srgb8 in spi-vfx
1373                 *colorspace = "srgb8";
1374             } else if ( _ocio->hasColorspace("vd16") ) {
1375                 // vd16 in spi-anim
1376                 *colorspace = "vd16";
1377             }
1378         }
1379         break;
1380     case ePNGColorSpacesRGB:
1381         if ( _ocio->hasColorspace("sRGB") ) {
1382             // nuke-default and blender
1383             *colorspace = "sRGB";
1384         } else if ( _ocio->hasColorspace("sRGB D65") ) {
1385             // blender-cycles
1386             *colorspace = "sRGB D65";
1387         } else if ( _ocio->hasColorspace("sRGB (D60 sim.)") ) {
1388             // out_srgbd60sim or "sRGB (D60 sim.)" in aces 1.0.0
1389             *colorspace = "sRGB (D60 sim.)";
1390         } else if ( _ocio->hasColorspace("out_srgbd60sim") ) {
1391             // out_srgbd60sim or "sRGB (D60 sim.)" in aces 1.0.0
1392             *colorspace = "out_srgbd60sim";
1393         } else if ( _ocio->hasColorspace("rrt_Gamma2.2") ) {
1394             // rrt_Gamma2.2 in aces 0.7.1
1395             *colorspace = "rrt_Gamma2.2";
1396         } else if ( _ocio->hasColorspace("rrt_srgb") ) {
1397             // rrt_srgb in aces 0.1.1
1398             *colorspace = "rrt_srgb";
1399         } else if ( _ocio->hasColorspace("srgb8") ) {
1400             // srgb8 in spi-vfx
1401             *colorspace = "srgb8";
1402         } else if ( _ocio->hasColorspace("Gamma2.2") ) {
1403             // nuke-default
1404             *colorspace = "Gamma2.2";
1405         } else if ( _ocio->hasColorspace("srgb8") ) {
1406             // srgb8 in spi-vfx
1407             *colorspace = "srgb8";
1408         } else if ( _ocio->hasColorspace("vd16") ) {
1409             // vd16 in spi-anim
1410             *colorspace = "vd16";
1411         }
1412 
1413         break;
1414     case ePNGColorSpaceRec709:
1415         if ( _ocio->hasColorspace("Rec709") ) {
1416             // nuke-default
1417             *colorspace = "Rec709";
1418         } else if ( _ocio->hasColorspace("nuke_rec709") ) {
1419             // blender
1420             *colorspace = "nuke_rec709";
1421         } else if ( _ocio->hasColorspace("Rec.709 - Full") ) {
1422             // out_rec709full or "Rec.709 - Full" in aces 1.0.0
1423             *colorspace = "Rec.709 - Full";
1424         } else if ( _ocio->hasColorspace("out_rec709full") ) {
1425             // out_rec709full or "Rec.709 - Full" in aces 1.0.0
1426             *colorspace = "out_rec709full";
1427         } else if ( _ocio->hasColorspace("rrt_rec709_full_100nits") ) {
1428             // rrt_rec709_full_100nits in aces 0.7.1
1429             *colorspace = "rrt_rec709_full_100nits";
1430         } else if ( _ocio->hasColorspace("rrt_rec709") ) {
1431             // rrt_rec709 in aces 0.1.1
1432             *colorspace = "rrt_rec709";
1433         } else if ( _ocio->hasColorspace("hd10") ) {
1434             // hd10 in spi-anim and spi-vfx
1435             *colorspace = "hd10";
1436         }
1437         break;
1438     case ePNGColorSpaceLinear:
1439         *colorspace = OCIO::ROLE_SCENE_LINEAR;
1440         break;
1441     } // switch
1442 #     endif // ifdef OFX_IO_USING_OCIO
1443 
1444     switch (nChannels) {
1445     case 1:
1446         assert(false);
1447         *components = ePixelComponentAlpha;
1448         break;
1449     case 2:
1450         assert(false);
1451         *components = ePixelComponentRGBA;
1452         break;
1453     case 3:
1454         *components = ePixelComponentRGB;
1455         break;
1456     case 4:
1457         *components = ePixelComponentRGBA;
1458         break;
1459     default:
1460         break;
1461     }
1462 
1463     *componentCount = nChannels;
1464 
1465     if ( (*components != ePixelComponentRGBA) && (*components != ePixelComponentAlpha) ) {
1466         *filePremult = eImageOpaque;
1467     } else {
1468         // output is always unpremultiplied
1469         *filePremult = eImageUnPreMultiplied;
1470     }
1471 
1472     return true;
1473 } // ReadPNGPlugin::guessParamsFromFilename
1474 
1475 
1476 mDeclareReaderPluginFactory(ReadPNGPluginFactory, {}, false);
1477 void
load()1478 ReadPNGPluginFactory::load()
1479 {
1480     _extensions.clear();
1481     _extensions.push_back("png");
1482 }
1483 
1484 /** @brief The basic describe function, passed a plugin descriptor */
1485 void
describe(ImageEffectDescriptor & desc)1486 ReadPNGPluginFactory::describe(ImageEffectDescriptor &desc)
1487 {
1488     GenericReaderDescribe(desc, _extensions, kPluginEvaluation, kSupportsTiles, false);
1489 
1490     // basic labels
1491     desc.setLabel(kPluginName);
1492     desc.setPluginDescription(kPluginDescription);
1493 }
1494 
1495 /** @brief The describe in context function, passed a plugin descriptor and a context */
1496 void
describeInContext(ImageEffectDescriptor & desc,ContextEnum context)1497 ReadPNGPluginFactory::describeInContext(ImageEffectDescriptor &desc,
1498                                         ContextEnum context)
1499 {
1500     // make some pages and to things in
1501     PageParamDescriptor *page = GenericReaderDescribeInContextBegin(desc, context, isVideoStreamPlugin(),
1502                                                                     kSupportsRGBA, kSupportsRGB, kSupportsXY, kSupportsAlpha, kSupportsTiles, true);
1503 
1504     {
1505         PushButtonParamDescriptor* param = desc.definePushButtonParam(kParamShowMetadata);
1506         param->setLabel(kParamShowMetadataLabel);
1507         param->setHint(kParamShowMetadataHint);
1508         if (page) {
1509             page->addChild(*param);
1510         }
1511     }
1512     {
1513         PushButtonParamDescriptor* param = desc.definePushButtonParam(kParamLibraryInfo);
1514         param->setLabelAndHint(kParamLibraryInfoLabel);
1515         if (page) {
1516             page->addChild(*param);
1517         }
1518     }
1519 
1520     GenericReaderDescribeInContextEnd(desc, context, page, "sRGB", "scene_linear");
1521 }
1522 
1523 /** @brief The create instance function, the plugin must return an object derived from the \ref ImageEffect class */
1524 ImageEffect*
createInstance(OfxImageEffectHandle handle,ContextEnum)1525 ReadPNGPluginFactory::createInstance(OfxImageEffectHandle handle,
1526                                      ContextEnum /*context*/)
1527 {
1528     ReadPNGPlugin* ret =  new ReadPNGPlugin(handle, _extensions);
1529 
1530     ret->restoreStateFromParams();
1531 
1532     return ret;
1533 }
1534 
1535 static ReadPNGPluginFactory p(kPluginIdentifier, kPluginVersionMajor, kPluginVersionMinor);
1536 mRegisterPluginFactoryInstance(p)
1537 
1538 OFXS_NAMESPACE_ANONYMOUS_EXIT
1539