Add `skcms_PixelFormat_GA_88` support for `skcms_Transform`.

Bug: chromium:40278281
Change-Id: I5dec2c12fe09ae0d66365e901100430cce5e9e2a
Reviewed-on: https://skia-review.googlesource.com/c/skcms/+/880812
Reviewed-by: Brian Osman <brianosman@google.com>
Commit-Queue: Ɓukasz Anforowicz <lukasza@google.com>
Reviewed-by: Leon Scroggins <scroggo@google.com>
diff --git a/ninja/clang b/ninja/clang
index 1a4b7e6..ed85687 100644
--- a/ninja/clang
+++ b/ninja/clang
@@ -1,6 +1,6 @@
 cc     = clang
 cxx    = clang++
-cflags = -fcolor-diagnostics -Weverything -ffp-contract=off
+cflags = -fcolor-diagnostics -Weverything -Wno-unsafe-buffer-usage -ffp-contract=off
 out    = out/clang$mode
 
 include ninja/local
diff --git a/skcms.cc b/skcms.cc
index 047f21b..cc1b699 100644
--- a/skcms.cc
+++ b/skcms.cc
@@ -2450,6 +2450,7 @@
     switch (fmt >> 1) {   // ignore rgb/bgr
         case skcms_PixelFormat_A_8              >> 1: return  1;
         case skcms_PixelFormat_G_8              >> 1: return  1;
+        case skcms_PixelFormat_GA_88            >> 1: return  2;
         case skcms_PixelFormat_ABGR_4444        >> 1: return  2;
         case skcms_PixelFormat_RGB_565          >> 1: return  2;
         case skcms_PixelFormat_RGB_888          >> 1: return  3;
@@ -2562,6 +2563,7 @@
         default: return false;
         case skcms_PixelFormat_A_8              >> 1: add_op(Op::load_a8);          break;
         case skcms_PixelFormat_G_8              >> 1: add_op(Op::load_g8);          break;
+        case skcms_PixelFormat_GA_88            >> 1: add_op(Op::load_ga88);        break;
         case skcms_PixelFormat_ABGR_4444        >> 1: add_op(Op::load_4444);        break;
         case skcms_PixelFormat_RGB_565          >> 1: add_op(Op::load_565);         break;
         case skcms_PixelFormat_RGB_888          >> 1: add_op(Op::load_888);         break;
@@ -2593,12 +2595,17 @@
         add_op(Op::swap_rb);
     }
     skcms_ICCProfile gray_dst_profile;
-    if ((dstFmt >> 1) == (skcms_PixelFormat_G_8 >> 1)) {
-        // When transforming to gray, stop at XYZ (by setting toXYZ to identity), then transform
-        // luminance (Y) by the destination transfer function.
-        gray_dst_profile = *dstProfile;
-        skcms_SetXYZD50(&gray_dst_profile, &skcms_XYZD50_profile()->toXYZD50);
-        dstProfile = &gray_dst_profile;
+    switch (dstFmt >> 1) {
+        case skcms_PixelFormat_G_8:
+        case skcms_PixelFormat_GA_88:
+            // When transforming to gray, stop at XYZ (by setting toXYZ to identity), then transform
+            // luminance (Y) by the destination transfer function.
+            gray_dst_profile = *dstProfile;
+            skcms_SetXYZD50(&gray_dst_profile, &skcms_XYZD50_profile()->toXYZD50);
+            dstProfile = &gray_dst_profile;
+            break;
+        default:
+            break;
     }
 
     if (srcProfile->data_color_space == skcms_Signature_CMYK) {
@@ -2760,6 +2767,7 @@
         default: return false;
         case skcms_PixelFormat_A_8             >> 1: add_op(Op::store_a8);         break;
         case skcms_PixelFormat_G_8             >> 1: add_op(Op::store_g8);         break;
+        case skcms_PixelFormat_GA_88           >> 1: add_op(Op::store_ga88);       break;
         case skcms_PixelFormat_ABGR_4444       >> 1: add_op(Op::store_4444);       break;
         case skcms_PixelFormat_RGB_565         >> 1: add_op(Op::store_565);        break;
         case skcms_PixelFormat_RGB_888         >> 1: add_op(Op::store_888);        break;
diff --git a/src/Transform_inl.h b/src/Transform_inl.h
index ab9b94d..99bbbc5 100644
--- a/src/Transform_inl.h
+++ b/src/Transform_inl.h
@@ -837,6 +837,12 @@
     r = g = b = F_from_U8(load<U8>(src + 1*i));
 }
 
+STAGE(load_ga88, NoCtx) {
+    U16 u16 = load<U16>(src + 2 * i);
+    r = g = b = cast<F>((u16 >> 0) & 0xff) * (1 / 255.0f);
+            a = cast<F>((u16 >> 8) & 0xff) * (1 / 255.0f);
+}
+
 STAGE(load_4444, NoCtx) {
     U16 abgr = load<U16>(src + 2*i);
 
@@ -1255,6 +1261,12 @@
     store(dst + 1*i, cast<U8>(to_fixed(g * 255)));
 }
 
+FINAL_STAGE(store_ga88, NoCtx) {
+    // g should be holding luminance (Y) (r,g,b ~~~> X,Y,Z)
+    store<U16>(dst + 2*i, cast<U16>(to_fixed(g * 255) << 0 )
+                        | cast<U16>(to_fixed(a * 255) << 8 ));
+}
+
 FINAL_STAGE(store_4444, NoCtx) {
     store<U16>(dst + 2*i, cast<U16>(to_fixed(r * 15) << 12)
                         | cast<U16>(to_fixed(g * 15) <<  8)
diff --git a/src/skcms_Transform.h b/src/skcms_Transform.h
index 9f02e79..d5e542a 100644
--- a/src/skcms_Transform.h
+++ b/src/skcms_Transform.h
@@ -20,6 +20,7 @@
 #define SKCMS_WORK_OPS(M) \
     M(load_a8)            \
     M(load_g8)            \
+    M(load_ga88)          \
     M(load_4444)          \
     M(load_565)           \
     M(load_888)           \
@@ -89,6 +90,7 @@
 #define SKCMS_STORE_OPS(M) \
     M(store_a8)            \
     M(store_g8)            \
+    M(store_ga88)          \
     M(store_4444)          \
     M(store_565)           \
     M(store_888)           \
diff --git a/src/skcms_public.h b/src/skcms_public.h
index 3510f89..00c51c6 100644
--- a/src/skcms_public.h
+++ b/src/skcms_public.h
@@ -275,6 +275,8 @@
     skcms_PixelFormat_A_8_,
     skcms_PixelFormat_G_8,
     skcms_PixelFormat_G_8_,
+    skcms_PixelFormat_GA_88,  // Grayscale with alpha.
+    skcms_PixelFormat_GA_88_,
 
     skcms_PixelFormat_RGB_565,
     skcms_PixelFormat_BGR_565,
@@ -286,39 +288,39 @@
     skcms_PixelFormat_BGR_888,
     skcms_PixelFormat_RGBA_8888,
     skcms_PixelFormat_BGRA_8888,
-    skcms_PixelFormat_RGBA_8888_sRGB,   // Automatic sRGB encoding / decoding.
-    skcms_PixelFormat_BGRA_8888_sRGB,   // (Generally used with linear transfer functions.)
+    skcms_PixelFormat_RGBA_8888_sRGB,  // Automatic sRGB encoding / decoding.
+    skcms_PixelFormat_BGRA_8888_sRGB,  // (Generally used with linear transfer functions.)
 
     skcms_PixelFormat_RGBA_1010102,
     skcms_PixelFormat_BGRA_1010102,
 
-    skcms_PixelFormat_RGB_161616LE,     // Little-endian.  Pointers must be 16-bit aligned.
+    skcms_PixelFormat_RGB_161616LE,  // Little-endian.  Pointers must be 16-bit aligned.
     skcms_PixelFormat_BGR_161616LE,
     skcms_PixelFormat_RGBA_16161616LE,
     skcms_PixelFormat_BGRA_16161616LE,
 
-    skcms_PixelFormat_RGB_161616BE,     // Big-endian.  Pointers must be 16-bit aligned.
+    skcms_PixelFormat_RGB_161616BE,  // Big-endian.  Pointers must be 16-bit aligned.
     skcms_PixelFormat_BGR_161616BE,
     skcms_PixelFormat_RGBA_16161616BE,
     skcms_PixelFormat_BGRA_16161616BE,
 
-    skcms_PixelFormat_RGB_hhh_Norm,   // 1-5-10 half-precision float in [0,1]
-    skcms_PixelFormat_BGR_hhh_Norm,   // Pointers must be 16-bit aligned.
+    skcms_PixelFormat_RGB_hhh_Norm,  // 1-5-10 half-precision float in [0,1]
+    skcms_PixelFormat_BGR_hhh_Norm,  // Pointers must be 16-bit aligned.
     skcms_PixelFormat_RGBA_hhhh_Norm,
     skcms_PixelFormat_BGRA_hhhh_Norm,
 
-    skcms_PixelFormat_RGB_hhh,        // 1-5-10 half-precision float.
-    skcms_PixelFormat_BGR_hhh,        // Pointers must be 16-bit aligned.
+    skcms_PixelFormat_RGB_hhh,  // 1-5-10 half-precision float.
+    skcms_PixelFormat_BGR_hhh,  // Pointers must be 16-bit aligned.
     skcms_PixelFormat_RGBA_hhhh,
     skcms_PixelFormat_BGRA_hhhh,
 
-    skcms_PixelFormat_RGB_fff,        // 1-8-23 single-precision float (the normal kind).
-    skcms_PixelFormat_BGR_fff,        // Pointers must be 32-bit aligned.
+    skcms_PixelFormat_RGB_fff,  // 1-8-23 single-precision float (the normal kind).
+    skcms_PixelFormat_BGR_fff,  // Pointers must be 32-bit aligned.
     skcms_PixelFormat_RGBA_ffff,
     skcms_PixelFormat_BGRA_ffff,
 
-    skcms_PixelFormat_RGB_101010x_XR,  // Note: This is located here to signal no clamping.
-    skcms_PixelFormat_BGR_101010x_XR,  // Compatible with MTLPixelFormatBGR10_XR.
+    skcms_PixelFormat_RGB_101010x_XR,    // Note: This is located here to signal no clamping.
+    skcms_PixelFormat_BGR_101010x_XR,    // Compatible with MTLPixelFormatBGR10_XR.
     skcms_PixelFormat_RGBA_10101010_XR,  // Note: This is located here to signal no clamping.
     skcms_PixelFormat_BGRA_10101010_XR,  // Compatible with MTLPixelFormatBGRA10_XR.
 } skcms_PixelFormat;
diff --git a/tests.c b/tests.c
index 90bd9dd..2d36cc2 100644
--- a/tests.c
+++ b/tests.c
@@ -440,6 +440,64 @@
     expect(((dst[1] >> 24) & 0xff) == 255);
 }
 
+static void test_FormatConversions_G8(void) {
+    uint8_t src[1 * 256] = {0};
+    for (int i = 0; i < 256; i++) {
+        src[i] = (uint8_t)i;
+    }
+
+    uint8_t dst[4 * 256] = {0};
+    expect(skcms_Transform(src, skcms_PixelFormat_G_8      , skcms_AlphaFormat_Unpremul, NULL,
+                           dst, skcms_PixelFormat_RGBA_8888, skcms_AlphaFormat_Unpremul, NULL,
+                           256));
+    for (int i = 0; i < 256; i++) {
+      expect(dst[i * 4 + 0] == (uint8_t)i);  // red = gray
+      expect(dst[i * 4 + 1] == (uint8_t)i);  // green = gray
+      expect(dst[i * 4 + 2] == (uint8_t)i);  // blue = gray
+      expect(dst[i * 4 + 3] == 0xFF);        // opaque
+    }
+
+    // Let's convert back the other way.
+    uint8_t back[1 * 256] = {0};
+    expect(skcms_Transform(dst,  skcms_PixelFormat_RGBA_8888, skcms_AlphaFormat_Unpremul, NULL,
+                           back, skcms_PixelFormat_G_8      , skcms_AlphaFormat_Unpremul, NULL,
+                           256));
+    for (int i = 0; i < 256; i++) {
+      expect(src[i] == back[i]);
+    }
+}
+
+static void test_FormatConversions_GA88(void) {
+    uint8_t src[2 * 256] = {0};
+    for (int i = 0; i < 256; i++) {
+        // Using a different "gray" and "alpha" value will hopefully catch most
+        // potential LE-vs-BE confusion bugs.
+        src[i * 2 + 0] = (uint8_t)i;
+        src[i * 2 + 1] = (uint8_t)((i * 7) % 256);
+    }
+
+    uint8_t dst[4 * 256] = {0};
+    expect(skcms_Transform(src, skcms_PixelFormat_GA_88    , skcms_AlphaFormat_Unpremul, NULL,
+                           dst, skcms_PixelFormat_RGBA_8888, skcms_AlphaFormat_Unpremul, NULL,
+                           256));
+    for (int i = 0; i < 256; i++) {
+      expect(dst[i * 4 + 0] == (uint8_t)i);  // red = gray
+      expect(dst[i * 4 + 1] == (uint8_t)i);  // green = gray
+      expect(dst[i * 4 + 2] == (uint8_t)i);  // blue = gray
+      expect(dst[i * 4 + 3] == src[i * 2 + 1]);
+    }
+
+    // Let's convert back the other way.
+    uint8_t back[2 * 256] = {0};
+    expect(skcms_Transform(dst,  skcms_PixelFormat_RGBA_8888, skcms_AlphaFormat_Unpremul, NULL,
+                           back, skcms_PixelFormat_GA_88    , skcms_AlphaFormat_Unpremul, NULL,
+                           256));
+    for (int i = 0; i < 256; i++) {
+      expect(src[i * 2 + 0] == back[i * 2 + 0]);
+      expect(src[i * 2 + 1] == back[i * 2 + 1]);
+    }
+}
+
 static void test_FormatConversions_half(void) {
     uint16_t src[] = {
         0x3c00,  // 1.0
@@ -1877,6 +1935,8 @@
     test_FormatConversions_161616LE();
     test_FormatConversions_16161616BE();
     test_FormatConversions_161616BE();
+    test_FormatConversions_G8();
+    test_FormatConversions_GA88();
     test_FormatConversions_half();
     test_FormatConversions_half_norm();
     test_FormatConversions_float();