1 // Copyright 2008-present Contributors to the OpenImageIO project.
2 // SPDX-License-Identifier: BSD-3-Clause
3 // https://github.com/OpenImageIO/oiio/blob/master/LICENSE.md
4 
5 #pragma once
6 
7 #include <png.h>
8 #include <zlib.h>
9 
10 #include <OpenImageIO/dassert.h>
11 #include <OpenImageIO/filesystem.h>
12 #include <OpenImageIO/fmath.h>
13 #include <OpenImageIO/imageio.h>
14 #include <OpenImageIO/strutil.h>
15 #include <OpenImageIO/sysutil.h>
16 #include <OpenImageIO/tiffutils.h>
17 #include <OpenImageIO/typedesc.h>
18 
19 
20 #define OIIO_LIBPNG_VERSION                                    \
21     (PNG_LIBPNG_VER_MAJOR * 10000 + PNG_LIBPNG_VER_MINOR * 100 \
22      + PNG_LIBPNG_VER_RELEASE)
23 
24 
25 /*
26 This code has been extracted from the PNG plugin so as to provide access to PNG
27 images embedded within any container format, without redundant code
28 duplication.
29 
30 It's been done in the course of development of the ICO plugin in order to allow
31 reading and writing Vista-style PNG icons.
32 
33 For further information see the following mailing list threads:
34 http://lists.openimageio.org/pipermail/oiio-dev-openimageio.org/2009-April/000586.html
35 http://lists.openimageio.org/pipermail/oiio-dev-openimageio.org/2009-April/000656.html
36 */
37 
38 OIIO_PLUGIN_NAMESPACE_BEGIN
39 
40 #define ICC_PROFILE_ATTR "ICCProfile"
41 
42 
43 namespace PNG_pvt {
44 
45 static void
rderr_handler(png_structp png,png_const_charp data)46 rderr_handler(png_structp png, png_const_charp data)
47 {
48     ImageInput* inp = (ImageInput*)png_get_error_ptr(png);
49     if (inp && data)
50         inp->errorfmt("PNG read error: {}", data);
51 }
52 
53 
54 static void
wrerr_handler(png_structp png,png_const_charp data)55 wrerr_handler(png_structp png, png_const_charp data)
56 {
57     ImageOutput* outp = (ImageOutput*)png_get_error_ptr(png);
58     if (outp && data)
59         outp->errorfmt("PNG write error: {}", data);
60 }
61 
62 
null_png_handler(png_structp,png_const_charp)63 static void null_png_handler(png_structp /*png*/, png_const_charp /*data*/) {}
64 
65 
66 
67 /// Initializes a PNG read struct.
68 /// \return empty string on success, error message on failure.
69 ///
70 inline const std::string
71 create_read_struct(png_structp& sp, png_infop& ip, ImageInput* inp = nullptr)
72 {
73     sp = png_create_read_struct(PNG_LIBPNG_VER_STRING, inp, rderr_handler,
74                                 null_png_handler);
75     if (!sp)
76         return "Could not create PNG read structure";
77 
78     png_set_error_fn(sp, inp, rderr_handler, null_png_handler);
79     ip = png_create_info_struct(sp);
80     if (!ip)
81         return "Could not create PNG info structure";
82 
83     // Must call this setjmp in every function that does PNG reads
84     if (setjmp(png_jmpbuf(sp)))  // NOLINT(cert-err52-cpp)
85         return "PNG library error";
86 
87     // success
88     return "";
89 }
90 
91 
92 
93 /// Helper function - reads background colour.
94 ///
95 inline bool
get_background(png_structp & sp,png_infop & ip,ImageSpec & spec,int & bit_depth,float * red,float * green,float * blue)96 get_background(png_structp& sp, png_infop& ip, ImageSpec& spec, int& bit_depth,
97                float* red, float* green, float* blue)
98 {
99     if (setjmp(png_jmpbuf(sp)))  // NOLINT(cert-err52-cpp)
100         return false;
101     if (!png_get_valid(sp, ip, PNG_INFO_bKGD))
102         return false;
103 
104     png_color_16p bg;
105     png_get_bKGD(sp, ip, &bg);
106     if (spec.format == TypeDesc::UINT16) {
107         *red   = bg->red / 65535.0;
108         *green = bg->green / 65535.0;
109         *blue  = bg->blue / 65535.0;
110     } else if (spec.nchannels < 3 && bit_depth < 8) {
111         if (bit_depth == 1)
112             *red = *green = *blue = (bg->gray ? 1 : 0);
113         else if (bit_depth == 2)
114             *red = *green = *blue = bg->gray / 3.0;
115         else  // 4 bits
116             *red = *green = *blue = bg->gray / 15.0;
117     } else {
118         *red   = bg->red / 255.0;
119         *green = bg->green / 255.0;
120         *blue  = bg->blue / 255.0;
121     }
122     return true;
123 }
124 
125 
126 
127 inline int
hex2int(char a)128 hex2int(char a)
129 {
130     return a <= '9' ? a - '0' : tolower(a) - 'a' + 10;
131 }
132 
133 
134 
135 // Recent libpng (>= 1.6.32) supports direct Exif chunks. But the old way
136 // is more common, which is to embed it in a Text field (like a comment).
137 // This decodes that raw text data, which is a string,that looks like:
138 //
139 //     <whitespace> exif
140 //     <whitespace> <integer size>
141 //     <72 hex digits>
142 //     ...more lines of 72 hex digits...
143 //
144 static bool
decode_png_text_exif(string_view raw,ImageSpec & spec)145 decode_png_text_exif(string_view raw, ImageSpec& spec)
146 {
147     // Strutil::print("Found exif raw len={} '{}{}'\n", raw.size(),
148     //                raw.substr(0,200), raw.size() > 200 ? "..." : "");
149 
150     Strutil::skip_whitespace(raw);
151     if (!Strutil::parse_prefix(raw, "exif"))
152         return false;
153     int rawlen = 0;
154     if (!Strutil::parse_int(raw, rawlen) || !rawlen)
155         return false;
156     Strutil::skip_whitespace(raw);
157     std::string decoded;  // Converted from hex to bytes goes here
158     decoded.reserve(raw.size() / 2 + 1);
159     while (raw.size() >= 2) {
160         if (!isxdigit(raw.front())) {  // not hex digit? skip
161             raw.remove_prefix(1);
162             continue;
163         }
164         int c = (hex2int(raw[0]) << 4) | hex2int(raw[1]);
165         decoded.append(1, char(c));
166         raw.remove_prefix(2);
167     }
168     if (Strutil::istarts_with(decoded, "Exif")) {
169         decode_exif(decoded, spec);
170     }
171     return false;
172 }
173 
174 
175 
176 /// Read information from a PNG file and fill the ImageSpec accordingly.
177 ///
178 inline bool
read_info(png_structp & sp,png_infop & ip,int & bit_depth,int & color_type,int & interlace_type,Imath::Color3f & bg,ImageSpec & spec,bool keep_unassociated_alpha)179 read_info(png_structp& sp, png_infop& ip, int& bit_depth, int& color_type,
180           int& interlace_type, Imath::Color3f& bg, ImageSpec& spec,
181           bool keep_unassociated_alpha)
182 {
183     // Must call this setjmp in every function that does PNG reads
184     if (setjmp(png_jmpbuf(sp)))  // NOLINT(cert-err52-cpp)
185         return false;
186 
187     bool ok = true;
188     png_read_info(sp, ip);
189 
190     // Auto-convert 1-, 2-, and 4- bit images to 8 bits, palette to RGB,
191     // and transparency to alpha.
192     png_set_expand(sp);
193 
194     // PNG files are naturally big-endian
195     if (littleendian())
196         png_set_swap(sp);
197 
198     png_read_update_info(sp, ip);
199 
200     png_uint_32 width, height;
201     ok &= (bool)png_get_IHDR(sp, ip, &width, &height, &bit_depth, &color_type,
202                              nullptr, nullptr, nullptr);
203 
204     spec = ImageSpec((int)width, (int)height, png_get_channels(sp, ip),
205                      bit_depth == 16 ? TypeDesc::UINT16 : TypeDesc::UINT8);
206 
207     spec.default_channel_names();
208     if (spec.nchannels == 2) {
209         // Special case: PNG spec says 2-channel image is Gray & Alpha
210         spec.channelnames[0] = "Y";
211         spec.channelnames[1] = "A";
212         spec.alpha_channel   = 1;
213     }
214 
215     int srgb_intent;
216     if (png_get_sRGB(sp, ip, &srgb_intent)) {
217         spec.attribute("oiio:ColorSpace", "sRGB");
218     } else {
219         double gamma;
220         if (png_get_gAMA(sp, ip, &gamma)) {
221             // Round gamma to the nearest hundredth to prevent stupid
222             // precision choices and make it easier for apps to make
223             // decisions based on known gamma values. For example, you want
224             // 2.2, not 2.19998.
225             float g = float(1.0 / gamma);
226             g       = roundf(100.0 * g) / 100.0f;
227             spec.attribute("oiio:Gamma", g);
228             if (g == 1.0f)
229                 spec.attribute("oiio:ColorSpace", "linear");
230             else
231                 spec.attribute("oiio:ColorSpace",
232                                Strutil::sprintf("GammaCorrected%.2g", g));
233         }
234     }
235 
236     if (png_get_valid(sp, ip, PNG_INFO_iCCP)) {
237         png_charp profile_name = NULL;
238 #if OIIO_LIBPNG_VERSION > 10500 /* PNG function signatures changed */
239         png_bytep profile_data = NULL;
240 #else
241         png_charp profile_data = NULL;
242 #endif
243         png_uint_32 profile_length = 0;
244         int compression_type;
245         png_get_iCCP(sp, ip, &profile_name, &compression_type, &profile_data,
246                      &profile_length);
247         if (profile_length && profile_data)
248             spec.attribute(ICC_PROFILE_ATTR,
249                            TypeDesc(TypeDesc::UINT8, profile_length),
250                            profile_data);
251     }
252 
253     png_timep mod_time;
254     if (png_get_tIME(sp, ip, &mod_time)) {
255         std::string date = Strutil::sprintf("%4d:%02d:%02d %02d:%02d:%02d",
256                                             mod_time->year, mod_time->month,
257                                             mod_time->day, mod_time->hour,
258                                             mod_time->minute, mod_time->second);
259         spec.attribute("DateTime", date);
260     }
261 
262     png_textp text_ptr;
263     int num_comments = png_get_text(sp, ip, &text_ptr, NULL);
264     if (num_comments) {
265         std::string comments;
266         for (int i = 0; i < num_comments; ++i) {
267             if (Strutil::iequals(text_ptr[i].key, "Description"))
268                 spec.attribute("ImageDescription", text_ptr[i].text);
269             else if (Strutil::iequals(text_ptr[i].key, "Author"))
270                 spec.attribute("Artist", text_ptr[i].text);
271             else if (Strutil::iequals(text_ptr[i].key, "Title"))
272                 spec.attribute("DocumentName", text_ptr[i].text);
273             else if (Strutil::iequals(text_ptr[i].key, "XML:com.adobe.xmp"))
274                 decode_xmp(text_ptr[i].text, spec);
275             else if (Strutil::iequals(text_ptr[i].key,
276                                       "Raw profile type exif")) {
277                 // Most PNG files seem to encode Exif by cramming it into a
278                 // text field, with the key "Raw profile type exif" and then
279                 // a special text encoding that we handle with the following
280                 // function:
281                 decode_png_text_exif(text_ptr[i].text, spec);
282             } else {
283                 spec.attribute(text_ptr[i].key, text_ptr[i].text);
284             }
285         }
286     }
287     spec.x = png_get_x_offset_pixels(sp, ip);
288     spec.y = png_get_y_offset_pixels(sp, ip);
289 
290     int unit;
291     png_uint_32 resx, resy;
292     if (png_get_pHYs(sp, ip, &resx, &resy, &unit)) {
293         float scale = 1;
294         if (unit == PNG_RESOLUTION_METER) {
295             // Convert to inches, to match most other formats
296             scale = 2.54 / 100.0;
297             spec.attribute("ResolutionUnit", "inch");
298         } else
299             spec.attribute("ResolutionUnit", "none");
300         spec.attribute("XResolution", (float)resx * scale);
301         spec.attribute("YResolution", (float)resy * scale);
302     }
303 
304     float aspect = (float)png_get_pixel_aspect_ratio(sp, ip);
305     if (aspect != 0 && aspect != 1)
306         spec.attribute("PixelAspectRatio", aspect);
307 
308     float r, g, b;
309     if (get_background(sp, ip, spec, bit_depth, &r, &g, &b)) {
310         bg = Imath::Color3f(r, g, b);
311         // FIXME -- should we do anything with the background color?
312     }
313 
314     interlace_type = png_get_interlace_type(sp, ip);
315 
316 #ifdef PNG_eXIf_SUPPORTED
317     // Recent version of PNG and libpng (>= 1.6.32, I think) have direct
318     // support for Exif chunks. Older versions don't support it, and I'm not
319     // sure how common it is. Most files use the old way, which is the
320     // text embedding of Exif we handle with decode_png_text_exif.
321     png_uint_32 num_exif = 0;
322     png_bytep exif_data  = nullptr;
323     if (png_get_eXIf_1(sp, ip, &num_exif, &exif_data)) {
324         decode_exif(cspan<uint8_t>(exif_data, num_exif), spec);
325     }
326 #endif
327 
328     // PNG files are always "unassociated alpha" but we convert to associated
329     // unless requested otherwise
330     if (keep_unassociated_alpha)
331         spec.attribute("oiio:UnassociatedAlpha", (int)1);
332 
333     // FIXME -- look for an XMP packet in an iTXt chunk.
334 
335     return ok;
336 }
337 
338 
339 
340 /// Reads from an open PNG file into the indicated buffer.
341 /// \return empty string on success, error message on failure.
342 ///
343 inline const std::string
read_into_buffer(png_structp & sp,png_infop & ip,ImageSpec & spec,std::vector<unsigned char> & buffer)344 read_into_buffer(png_structp& sp, png_infop& ip, ImageSpec& spec,
345                  std::vector<unsigned char>& buffer)
346 {
347     // Must call this setjmp in every function that does PNG reads
348     if (setjmp(png_jmpbuf(sp)))  // NOLINT(cert-err52-cpp)
349         return "PNG library error";
350 
351 #if 0
352     // ?? This doesn't seem necessary, but I don't know why
353     // Make the library handle fewer significant bits
354     // png_color_8p sig_bit;
355     // if (png_get_sBIT (sp, ip, &sig_bit)) {
356     //        png_set_shift (sp, sig_bit);
357     // }
358 #endif
359 
360     OIIO_DASSERT(spec.scanline_bytes() == png_get_rowbytes(sp, ip));
361     buffer.resize(spec.image_bytes());
362 
363     std::vector<unsigned char*> row_pointers(spec.height);
364     for (int i = 0; i < spec.height; ++i)
365         row_pointers[i] = &buffer[0] + i * spec.scanline_bytes();
366 
367     png_read_image(sp, &row_pointers[0]);
368     png_read_end(sp, NULL);
369 
370     // success
371     return "";
372 }
373 
374 
375 
376 /// Reads the next scanline from an open PNG file into the indicated buffer.
377 /// \return empty string on success, error message on failure.
378 ///
379 inline const std::string
read_next_scanline(png_structp & sp,void * buffer)380 read_next_scanline(png_structp& sp, void* buffer)
381 {
382     // Must call this setjmp in every function that does PNG reads
383     if (setjmp(png_jmpbuf(sp)))  // NOLINT(cert-err52-cpp)
384         return "PNG library error";
385 
386     png_read_row(sp, (png_bytep)buffer, NULL);
387 
388     // success
389     return "";
390 }
391 
392 
393 
394 /// Destroys a PNG read struct.
395 ///
396 inline void
destroy_read_struct(png_structp & sp,png_infop & ip)397 destroy_read_struct(png_structp& sp, png_infop& ip)
398 {
399     if (sp && ip) {
400         png_destroy_read_struct(&sp, &ip, NULL);
401         sp = NULL;
402         ip = NULL;
403     }
404 }
405 
406 
407 
408 /// Initializes a PNG write struct.
409 /// \return empty string on success, C-string error message on failure.
410 ///
411 inline const std::string
412 create_write_struct(png_structp& sp, png_infop& ip, int& color_type,
413                     ImageSpec& spec, ImageOutput* outp = nullptr)
414 {
415     // Check for things this format doesn't support
416     if (spec.width < 1 || spec.height < 1)
417         return Strutil::sprintf("Image resolution must be at least 1x1, "
418                                 "you asked for %d x %d",
419                                 spec.width, spec.height);
420     if (spec.depth < 1)
421         spec.depth = 1;
422     if (spec.depth > 1)
423         return "PNG does not support volume images (depth > 1)";
424 
425     switch (spec.nchannels) {
426     case 1:
427         color_type         = PNG_COLOR_TYPE_GRAY;
428         spec.alpha_channel = -1;
429         break;
430     case 2:
431         color_type         = PNG_COLOR_TYPE_GRAY_ALPHA;
432         spec.alpha_channel = 1;
433         break;
434     case 3:
435         color_type         = PNG_COLOR_TYPE_RGB;
436         spec.alpha_channel = -1;
437         break;
438     case 4:
439         color_type         = PNG_COLOR_TYPE_RGB_ALPHA;
440         spec.alpha_channel = 3;
441         break;
442     default:
443         return Strutil::sprintf("PNG only supports 1-4 channels, not %d",
444                                 spec.nchannels);
445     }
446     // N.B. PNG is very rigid about the meaning of the channels, so enforce
447     // which channel is alpha, that's the only way PNG can do it.
448 
449     sp = png_create_write_struct(PNG_LIBPNG_VER_STRING, outp, wrerr_handler,
450                                  null_png_handler);
451     if (!sp)
452         return "Could not create PNG write structure";
453 
454     ip = png_create_info_struct(sp);
455     if (!ip)
456         return "Could not create PNG info structure";
457 
458     // Must call this setjmp in every function that does PNG writes
459     if (setjmp(png_jmpbuf(sp)))  // NOLINT(cert-err52-cpp)
460         return "PNG library error";
461 
462     // success
463     return "";
464 }
465 
466 
467 
468 /// Helper function - writes a single parameter.
469 ///
470 inline bool
put_parameter(png_structp & sp,png_infop & ip,const std::string & _name,TypeDesc type,const void * data,std::vector<png_text> & text)471 put_parameter(png_structp& sp, png_infop& ip, const std::string& _name,
472               TypeDesc type, const void* data, std::vector<png_text>& text)
473 {
474     std::string name = _name;
475 
476     // Things to skip
477     if (Strutil::iequals(name, "planarconfig"))  // No choice for PNG files
478         return false;
479     if (Strutil::iequals(name, "compression"))
480         return false;
481     if (Strutil::iequals(name, "ResolutionUnit")
482         || Strutil::iequals(name, "XResolution")
483         || Strutil::iequals(name, "YResolution"))
484         return false;
485 
486     // Remap some names to PNG conventions
487     if (Strutil::iequals(name, "Artist") && type == TypeDesc::STRING)
488         name = "Author";
489     if ((Strutil::iequals(name, "name")
490          || Strutil::iequals(name, "DocumentName"))
491         && type == TypeDesc::STRING)
492         name = "Title";
493     if ((Strutil::iequals(name, "description")
494          || Strutil::iequals(name, "ImageDescription"))
495         && type == TypeDesc::STRING)
496         name = "Description";
497 
498     if (Strutil::iequals(name, "DateTime") && type == TypeDesc::STRING) {
499         png_time mod_time;
500         int year, month, day, hour, minute, second;
501         if (sscanf(*(const char**)data, "%4d:%02d:%02d %02d:%02d:%02d", &year,
502                    &month, &day, &hour, &minute, &second)
503             == 6) {
504             mod_time.year   = year;
505             mod_time.month  = month;
506             mod_time.day    = day;
507             mod_time.hour   = hour;
508             mod_time.minute = minute;
509             mod_time.second = second;
510             png_set_tIME(sp, ip, &mod_time);
511             return true;
512         } else {
513             return false;
514         }
515     }
516 
517 #if 0
518     if (Strutil::iequals(name, "ResolutionUnit") && type == TypeDesc::STRING) {
519         const char *s = *(char**)data;
520         bool ok = true;
521         if (Strutil::iequals (s, "none"))
522             PNGSetField (m_tif, PNGTAG_RESOLUTIONUNIT, RESUNIT_NONE);
523         else if (Strutil::iequals (s, "in") || Strutil::iequals (s, "inch"))
524             PNGSetField (m_tif, PNGTAG_RESOLUTIONUNIT, RESUNIT_INCH);
525         else if (Strutil::iequals (s, "cm"))
526             PNGSetField (m_tif, PNGTAG_RESOLUTIONUNIT, RESUNIT_CENTIMETER);
527         else ok = false;
528         return ok;
529     }
530     if (Strutil::iequals(name, "ResolutionUnit") && type == TypeDesc::UINT) {
531         PNGSetField (m_tif, PNGTAG_RESOLUTIONUNIT, *(unsigned int *)data);
532         return true;
533     }
534     if (Strutil::iequals(name, "XResolution") && type == TypeDesc::FLOAT) {
535         PNGSetField (m_tif, PNGTAG_XRESOLUTION, *(float *)data);
536         return true;
537     }
538     if (Strutil::iequals(name, "YResolution") && type == TypeDesc::FLOAT) {
539         PNGSetField (m_tif, PNGTAG_YRESOLUTION, *(float *)data);
540         return true;
541     }
542 #endif
543     if (type == TypeDesc::STRING) {
544         png_text t;
545         t.compression = PNG_TEXT_COMPRESSION_NONE;
546         t.key         = (char*)ustring(name).c_str();
547         t.text        = *(char**)data;  // Already uniquified
548         text.push_back(t);
549     }
550 
551     return false;
552 }
553 
554 
555 
556 /// Writes PNG header according to the ImageSpec.
557 ///
558 inline void
write_info(png_structp & sp,png_infop & ip,int & color_type,ImageSpec & spec,std::vector<png_text> & text,bool & convert_alpha,float & gamma)559 write_info(png_structp& sp, png_infop& ip, int& color_type, ImageSpec& spec,
560            std::vector<png_text>& text, bool& convert_alpha, float& gamma)
561 {
562     // Force either 16 or 8 bit integers
563     if (spec.format == TypeDesc::UINT8 || spec.format == TypeDesc::INT8)
564         spec.set_format(TypeDesc::UINT8);
565     else
566         spec.set_format(TypeDesc::UINT16);  // best precision available
567 
568     png_set_IHDR(sp, ip, spec.width, spec.height, spec.format.size() * 8,
569                  color_type, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
570                  PNG_FILTER_TYPE_DEFAULT);
571 
572     png_set_oFFs(sp, ip, spec.x, spec.y, PNG_OFFSET_PIXEL);
573 
574     // PNG specifically dictates unassociated (un-"premultiplied") alpha
575     convert_alpha = spec.alpha_channel != -1
576                     && !spec.get_int_attribute("oiio:UnassociatedAlpha", 0);
577 
578     gamma = spec.get_float_attribute("oiio:Gamma", 1.0);
579 
580     std::string colorspace = spec.get_string_attribute("oiio:ColorSpace");
581     if (Strutil::iequals(colorspace, "Linear")) {
582         png_set_gAMA(sp, ip, 1.0);
583     } else if (Strutil::istarts_with(colorspace, "GammaCorrected")) {
584         float g = Strutil::from_string<float>(colorspace.c_str() + 14);
585         if (g >= 0.01f && g <= 10.0f /* sanity check */)
586             gamma = g;
587         png_set_gAMA(sp, ip, 1.0f / gamma);
588     } else if (Strutil::iequals(colorspace, "sRGB")) {
589         png_set_sRGB_gAMA_and_cHRM(sp, ip, PNG_sRGB_INTENT_ABSOLUTE);
590     }
591 
592     // Write ICC profile, if we have anything
593     const ParamValue* icc_profile_parameter = spec.find_attribute(
594         ICC_PROFILE_ATTR);
595     if (icc_profile_parameter != NULL) {
596         unsigned int length = icc_profile_parameter->type().size();
597 #if OIIO_LIBPNG_VERSION > 10500 /* PNG function signatures changed */
598         unsigned char* icc_profile
599             = (unsigned char*)icc_profile_parameter->data();
600         if (icc_profile && length)
601             png_set_iCCP(sp, ip, "Embedded Profile", 0, icc_profile, length);
602 #else
603         char* icc_profile = (char*)icc_profile_parameter->data();
604         if (icc_profile && length)
605             png_set_iCCP(sp, ip, (png_charp) "Embedded Profile", 0, icc_profile,
606                          length);
607 #endif
608     }
609 
610     if (false && !spec.find_attribute("DateTime")) {
611         time_t now;
612         time(&now);
613         struct tm mytm;
614         Sysutil::get_local_time(&now, &mytm);
615         std::string date = Strutil::sprintf("%4d:%02d:%02d %02d:%02d:%02d",
616                                             mytm.tm_year + 1900,
617                                             mytm.tm_mon + 1, mytm.tm_mday,
618                                             mytm.tm_hour, mytm.tm_min,
619                                             mytm.tm_sec);
620         spec.attribute("DateTime", date);
621     }
622 
623     string_view unitname = spec.get_string_attribute("ResolutionUnit");
624     float xres           = spec.get_float_attribute("XResolution");
625     float yres           = spec.get_float_attribute("YResolution");
626     float paspect        = spec.get_float_attribute("PixelAspectRatio");
627     if (xres || yres || paspect || unitname.size()) {
628         int unittype = PNG_RESOLUTION_UNKNOWN;
629         float scale  = 1;
630         if (Strutil::iequals(unitname, "meter")
631             || Strutil::iequals(unitname, "m"))
632             unittype = PNG_RESOLUTION_METER;
633         else if (Strutil::iequals(unitname, "cm")) {
634             unittype = PNG_RESOLUTION_METER;
635             scale    = 100;
636         } else if (Strutil::iequals(unitname, "inch")
637                    || Strutil::iequals(unitname, "in")) {
638             unittype = PNG_RESOLUTION_METER;
639             scale    = 100.0 / 2.54;
640         }
641         if (paspect) {
642             // If pixel aspect is given, allow resolution to be reset
643             if (xres)
644                 yres = 0.0f;
645             else
646                 xres = 0.0f;
647         }
648         if (xres == 0.0f && yres == 0.0f) {
649             xres = 100.0f;
650             yres = xres * (paspect ? paspect : 1.0f);
651         } else if (xres == 0.0f) {
652             xres = yres / (paspect ? paspect : 1.0f);
653         } else if (yres == 0.0f) {
654             yres = xres * (paspect ? paspect : 1.0f);
655         }
656         png_set_pHYs(sp, ip, (png_uint_32)(xres * scale),
657                      (png_uint_32)(yres * scale), unittype);
658     }
659 
660     // Deal with all other params
661     for (size_t p = 0; p < spec.extra_attribs.size(); ++p)
662         put_parameter(sp, ip, spec.extra_attribs[p].name().string(),
663                       spec.extra_attribs[p].type(),
664                       spec.extra_attribs[p].data(), text);
665 
666     if (text.size())
667         png_set_text(sp, ip, &text[0], text.size());
668 
669     png_write_info(sp, ip);
670     png_set_packing(sp);  // Pack 1, 2, 4 bit into bytes
671 }
672 
673 
674 
675 /// Writes a scanline.
676 ///
677 inline bool
write_row(png_structp & sp,png_byte * data)678 write_row(png_structp& sp, png_byte* data)
679 {
680     if (setjmp(png_jmpbuf(sp))) {  // NOLINT(cert-err52-cpp)
681         //error ("PNG library error");
682         return false;
683     }
684     png_write_row(sp, data);
685     return true;
686 }
687 
688 
689 
690 /// Helper function - finalizes writing the image and destroy the write
691 /// struct.
692 inline void
finish_image(png_structp & sp,png_infop & ip)693 finish_image(png_structp& sp, png_infop& ip)
694 {
695     // Must call this setjmp in every function that does PNG writes
696     if (setjmp(png_jmpbuf(sp))) {  // NOLINT(cert-err52-cpp)
697         //error ("PNG library error");
698         return;
699     }
700     png_write_end(sp, ip);
701     png_destroy_write_struct(&sp, &ip);
702     sp = nullptr;
703     ip = nullptr;
704 }
705 
706 
707 }  // namespace PNG_pvt
708 
709 OIIO_PLUGIN_NAMESPACE_END
710