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