google / wuffs

Wrangling Untrusted File Formats Safely
Other
4.07k stars 129 forks source link

Image decode API for color spaces and gamma correction. #39

Closed nigeltao closed 1 year ago

nigeltao commented 3 years ago

wuffs_base should have some way to represent or process a PNG image's gAMA, iCCP or similar chunks.

nigeltao commented 2 years ago

Code has landed to decode PNG's cHRM, gAMA, iCCP and sRGB chunks. The example/imageviewer program demonstrates basic gamma correction: it has a -screen_gamma=N.N flag and test/data/red-blue-gradient.*.png is a very basic test suite.

This is all admittedly very rough and under-documented. This is relatively new code, a work in progress, and in terms of prioritizing limited work time, documentation competes with other feature work.

That example/imageviewer program uses the higher level wuffs_aux::DecodeImage C++ API. If you're curious to follow that rabbit hole to the lower level C/C++ API, look for the DecodeImageHandleMetadata and HandleMetadata implementations in internal/cgen/auxiliary/*.cc. Again, this is not well documented yet, but the lower level APIs might make a little more sense if you keep in mind:

  1. Wuffs' image, image metadata and color correction APIs span many file formats and ICC profiles aren't just about PNG. I'd like to avoid those APIs having tens of little, format-specific methods, so there's some abstraction that might look weird at first glance.
  2. Those APIs also support streaming input and cannot assume that the entire input resides in contiguous memory.
  3. Those APIs cannot allocate memory.

Currently, the example/imageviewer program code (instead of the Wuffs library per se) has to explicitly apply gamma correction (look for lut in example/imageviewer/imageviewer.cc). Long term, this (and color correction in general) may move into the Wuffs library for "out of the box" support, but that would need some substantial API design work, implementation work, and maybe SIMD-related compiler work too.

For chunks other than gAMA, this patch below demonstrates how to get the raw data but, today, you will need additional third party code (e.g. Skia's Color Management System, skcms) to do something with that data (other than printf it). For manual testing, test/data/bricks-*.png have some cHRM and sRGB chunks.

diff --git a/example/imageviewer/imageviewer.cc b/example/imageviewer/imageviewer.cc
index 96938453..21b12ff9 100644
--- a/example/imageviewer/imageviewer.cc
+++ b/example/imageviewer/imageviewer.cc
@@ -167,14 +167,33 @@ class MyDecodeImageCallbacks : public wuffs_aux::DecodeImageCallbacks {
   std::string  //
   HandleMetadata(const wuffs_base__more_information& minfo,
                  wuffs_base__slice_u8 raw) override {
-    if (minfo.flavor == WUFFS_BASE__MORE_INFORMATION__FLAVOR__METADATA_PARSED) {
-      switch (minfo.metadata__fourcc()) {
-        case WUFFS_BASE__FOURCC__GAMA:
-          // metadata_parsed__gama returns the inverse gamma scaled by 1e5.
-          m_combined_gamma =
-              1e5 / (g_flags.screen_gamma * minfo.metadata_parsed__gama());
-          break;
-      }
+    switch (minfo.metadata__fourcc()) {
+      case WUFFS_BASE__FOURCC__CHRM:
+        printf(
+            "  HandleMetadata chrm_wx=%u\n",
+            minfo.metadata_parsed__chrm(
+                WUFFS_BASE__MORE_INFORMATION__METADATA_PARSED__CHRM__WHITE_X));
+        printf(
+            "  HandleMetadata chrm_wy=%u\n",
+            minfo.metadata_parsed__chrm(
+                WUFFS_BASE__MORE_INFORMATION__METADATA_PARSED__CHRM__WHITE_Y));
+        printf("  HandleMetadata chrm_rx=%u\n",
+               minfo.metadata_parsed__chrm(
+                   WUFFS_BASE__MORE_INFORMATION__METADATA_PARSED__CHRM__RED_X));
+        // Etcetera.
+        break;
+      case WUFFS_BASE__FOURCC__GAMA:
+        printf("  HandleMetadata gama=%u\n", minfo.metadata_parsed__gama());
+        // metadata_parsed__gama returns the inverse gamma scaled by 1e5.
+        m_combined_gamma =
+            1e5 / (g_flags.screen_gamma * minfo.metadata_parsed__gama());
+        break;
+      case WUFFS_BASE__FOURCC__ICCP:
+        printf("  HandleMetadata iccp ptr=%p len=%zu\n", raw.ptr, raw.len);
+        break;
+      case WUFFS_BASE__FOURCC__SRGB:
+        printf("  HandleMetadata srgb=%u\n", minfo.metadata_parsed__srgb());
+        break;
     }
     return wuffs_aux::DecodeImageCallbacks::HandleMetadata(minfo, raw);
   }
@@ -243,7 +262,10 @@ load_image(const char* filename) {

   uint64_t dia_flags = 0;
   if (g_flags.screen_gamma > 0) {
+    dia_flags |= wuffs_aux::DecodeImageArgFlags::REPORT_METADATA_CHRM;
     dia_flags |= wuffs_aux::DecodeImageArgFlags::REPORT_METADATA_GAMA;
+    dia_flags |= wuffs_aux::DecodeImageArgFlags::REPORT_METADATA_ICCP;
+    dia_flags |= wuffs_aux::DecodeImageArgFlags::REPORT_METADATA_SRGB;
   }

   MyDecodeImageCallbacks callbacks;
nigeltao commented 2 years ago

Code has landed to decode PNG's cHRM, gAMA, iCCP and sRGB chunks.

I forgot to mention: this has landed in release/c/wuffs-unsupported-snapshot.c but it won't hit release/c/wuffs-v0.3.c (or the wuffs-mirror-release-c repo) until the next 0.3 beta gets tagged.

nigeltao commented 2 years ago

v0.3.0-beta.11 has been tagged.

pjanx commented 2 years ago

Thanks, I have a terrible backlog that includes learning OpenGL, but I'll try to integrate and test it soon-ish.

pjanx commented 2 years ago

As far as I can tell, it works very well and there is nothing to add, except for, perhaps, documentation of all the possibilities within supported formats. Luckily, so far I haven't felt a need to special-case any format.

One ugly detail: wuffs_base__frame_config__struct::background_color needs to be unpremultiplied, colour corrected, and remultiplied for any sort of accurate processing. But it will likely be either fully opaque or fully transparent.