[libpng16] fix: Write order of colourspace chunks should conform to PNG v3
cICP was written after PLTE, not before. The other chunks were output
in an order which does not match the new PNG-v3 "priority" order.
This change outputs all chunks in the "priority" order; highest
precedence first. This means that the PNGs so written conform to
PNG v3 (cICP), and allow a streaming app to handle chunks in order,
without buffering data which may later be overridden.
Note that PNG-v3 establishes the idea of dropping ancillary chunks
which are inconveniently ordered in the definition of how APNG chunks
are handled.
This is a cherry-pick of commit 945f2602a0ea05a7938b2f52df9ef16cf3440291
from branch 'libpng18'.
Reviewed-by: Cosmin Truta <ctruta@gmail.com>
Signed-off-by: John Bowler <jbowler@acm.org>
Signed-off-by: Cosmin Truta <ctruta@gmail.com>
diff --git a/contrib/testpngs/png-3/cicp-display-p3_reencoded.png b/contrib/testpngs/png-3/cicp-display-p3_reencoded.png
index 47830bd..91d8e6b 100644
--- a/contrib/testpngs/png-3/cicp-display-p3_reencoded.png
+++ b/contrib/testpngs/png-3/cicp-display-p3_reencoded.png
Binary files differ
diff --git a/pngtest.png b/pngtest.png
index 66df0c4..cf620eb 100644
--- a/pngtest.png
+++ b/pngtest.png
Binary files differ
diff --git a/pngwrite.c b/pngwrite.c
index 6e939cc..8b1b06c 100644
--- a/pngwrite.c
+++ b/pngwrite.c
@@ -1,6 +1,6 @@
/* pngwrite.c - general routines to write a PNG file
*
- * Copyright (c) 2018-2024 Cosmin Truta
+ * Copyright (c) 2018-2025 Cosmin Truta
* Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson
* Copyright (c) 1996-1997 Andreas Dilger
* Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc.
@@ -127,29 +127,61 @@
* the application continues writing the PNG. So check the 'invalid'
* flag here too.
*/
-#ifdef PNG_GAMMA_SUPPORTED
-# ifdef PNG_WRITE_gAMA_SUPPORTED
- if ((info_ptr->colorspace.flags & PNG_COLORSPACE_INVALID) == 0 &&
- (info_ptr->colorspace.flags & PNG_COLORSPACE_FROM_gAMA) != 0 &&
- (info_ptr->valid & PNG_INFO_gAMA) != 0)
- png_write_gAMA_fixed(png_ptr, info_ptr->colorspace.gamma);
-# endif
+#ifdef PNG_WRITE_UNKNOWN_CHUNKS_SUPPORTED
+ /* Write unknown chunks first; PNG v3 establishes a precedence order
+ * for colourspace chunks. It is certain therefore that new
+ * colourspace chunks will have a precedence and very likely it will be
+ * higher than all known so far. Writing the unknown chunks here is
+ * most likely to present the chunks in the most convenient order.
+ *
+ * FUTURE: maybe write chunks in the order the app calls png_set_chnk
+ * to give the app control.
+ */
+ write_unknown_chunks(png_ptr, info_ptr, PNG_HAVE_IHDR);
#endif
+#ifdef PNG_WRITE_sBIT_SUPPORTED
+ /* PNG v3: a streaming app will need to see this before cICP because
+ * the information is helpful in handling HLG encoding (which is
+ * natively 10 bits but gets expanded to 16 in PNG.)
+ *
+ * The app shouldn't care about the order ideally, but it might have
+ * no choice. In PNG v3, apps are allowed to reject PNGs where the
+ * APNG chunks are out of order so it behooves libpng to be nice here.
+ */
+ if ((info_ptr->valid & PNG_INFO_sBIT) != 0)
+ png_write_sBIT(png_ptr, &(info_ptr->sig_bit), info_ptr->color_type);
+#endif
+
+ /* PNG v3: the July 2004 version of the TR introduced the concept of colour
+ * space priority. As above it therefore behooves libpng to write the colour
+ * space chunks in the priority order so that a streaming app need not buffer
+ * them.
+ */
#ifdef PNG_COLORSPACE_SUPPORTED
- /* Write only one of sRGB or an ICC profile. If a profile was supplied
- * and it matches one of the known sRGB ones issue a warning.
+# ifdef PNG_WRITE_cICP_SUPPORTED /* Priority 4 */
+ if ((info_ptr->valid & PNG_INFO_cICP) != 0)
+ {
+ png_write_cICP(png_ptr,
+ info_ptr->cicp_colour_primaries,
+ info_ptr->cicp_transfer_function,
+ info_ptr->cicp_matrix_coefficients,
+ info_ptr->cicp_video_full_range_flag);
+ }
+# endif
+
+ /* PNG v3 change: it is now permitted to write both sRGB and ICC profiles,
+ * however because the libpng code auto-generates an sRGB for the
+ * corresponding ICC profiles and because PNG v2 disallowed this we need
+ * to only write one.
+ *
+ * Remove the PNG v2 warning about writing an sRGB ICC profile as well
+ * because it's invalid with PNG v3.
*/
-# ifdef PNG_WRITE_iCCP_SUPPORTED
+# ifdef PNG_WRITE_iCCP_SUPPORTED /* Priority 3 */
if ((info_ptr->colorspace.flags & PNG_COLORSPACE_INVALID) == 0 &&
(info_ptr->valid & PNG_INFO_iCCP) != 0)
{
-# ifdef PNG_WRITE_sRGB_SUPPORTED
- if ((info_ptr->valid & PNG_INFO_sRGB) != 0)
- png_app_warning(png_ptr,
- "profile matches sRGB but writing iCCP instead");
-# endif
-
png_write_iCCP(png_ptr, info_ptr->iccp_name,
info_ptr->iccp_profile);
}
@@ -158,20 +190,24 @@
# endif
# endif
-# ifdef PNG_WRITE_sRGB_SUPPORTED
+# ifdef PNG_WRITE_sRGB_SUPPORTED /* Priority 2 */
if ((info_ptr->colorspace.flags & PNG_COLORSPACE_INVALID) == 0 &&
(info_ptr->valid & PNG_INFO_sRGB) != 0)
png_write_sRGB(png_ptr, info_ptr->colorspace.rendering_intent);
# endif /* WRITE_sRGB */
#endif /* COLORSPACE */
-#ifdef PNG_WRITE_sBIT_SUPPORTED
- if ((info_ptr->valid & PNG_INFO_sBIT) != 0)
- png_write_sBIT(png_ptr, &(info_ptr->sig_bit), info_ptr->color_type);
+#ifdef PNG_GAMMA_SUPPORTED
+# ifdef PNG_WRITE_gAMA_SUPPORTED /* Priority 1 */
+ if ((info_ptr->colorspace.flags & PNG_COLORSPACE_INVALID) == 0 &&
+ (info_ptr->colorspace.flags & PNG_COLORSPACE_FROM_gAMA) != 0 &&
+ (info_ptr->valid & PNG_INFO_gAMA) != 0)
+ png_write_gAMA_fixed(png_ptr, info_ptr->colorspace.gamma);
+# endif
#endif
#ifdef PNG_COLORSPACE_SUPPORTED
-# ifdef PNG_WRITE_cHRM_SUPPORTED
+# ifdef PNG_WRITE_cHRM_SUPPORTED /* Also priority 1 */
if ((info_ptr->colorspace.flags & PNG_COLORSPACE_INVALID) == 0 &&
(info_ptr->colorspace.flags & PNG_COLORSPACE_FROM_cHRM) != 0 &&
(info_ptr->valid & PNG_INFO_cHRM) != 0)
@@ -179,10 +215,6 @@
# endif
#endif
-#ifdef PNG_WRITE_UNKNOWN_CHUNKS_SUPPORTED
- write_unknown_chunks(png_ptr, info_ptr, PNG_HAVE_IHDR);
-#endif
-
png_ptr->mode |= PNG_WROTE_INFO_BEFORE_PLTE;
}
}
@@ -236,17 +268,6 @@
png_write_bKGD(png_ptr, &(info_ptr->background), info_ptr->color_type);
#endif
-#ifdef PNG_WRITE_cICP_SUPPORTED
- if ((info_ptr->valid & PNG_INFO_cICP) != 0)
- {
- png_write_cICP(png_ptr,
- info_ptr->cicp_colour_primaries,
- info_ptr->cicp_transfer_function,
- info_ptr->cicp_matrix_coefficients,
- info_ptr->cicp_video_full_range_flag);
- }
-#endif
-
#ifdef PNG_WRITE_eXIf_SUPPORTED
if ((info_ptr->valid & PNG_INFO_eXIf) != 0)
{