add _sRGB 8888 formats

Trickiest bits were remembering which side gets _Inverse_,
and ensuring our test scenarios always run all transform steps.

Change-Id: Ib4acfa097eb0e9213c27777828026a1f1f9ac015
Reviewed-on: https://skia-review.googlesource.com/c/skcms/+/278800
Reviewed-by: Brian Osman <brianosman@google.com>
Commit-Queue: Mike Klein <mtklein@google.com>
diff --git a/skcms.cc b/skcms.cc
index 8ae3329..ae5ba4f 100644
--- a/skcms.cc
+++ b/skcms.cc
@@ -2236,6 +2236,7 @@
         case skcms_PixelFormat_RGB_565            >> 1: return  2;
         case skcms_PixelFormat_RGB_888            >> 1: return  3;
         case skcms_PixelFormat_RGBA_8888          >> 1: return  4;
+        case skcms_PixelFormat_RGBA_8888_sRGB     >> 1: return  4;
         case skcms_PixelFormat_RGBA_1010102       >> 1: return  4;
         case skcms_PixelFormat_RGB_161616LE       >> 1: return  6;
         case skcms_PixelFormat_RGBA_16161616LE    >> 1: return  8;
@@ -2357,6 +2358,12 @@
         case skcms_PixelFormat_RGBA_8888_Palette8 >> 1: *ops++  = Op_load_8888_palette8;
                                                         *args++ = palette;
                                                         break;
+        case skcms_PixelFormat_RGBA_8888_sRGB >> 1:
+            *ops++ = Op_load_8888;
+            *ops++ = Op_tf_r;       *args++ = skcms_sRGB_TransferFunction();
+            *ops++ = Op_tf_g;       *args++ = skcms_sRGB_TransferFunction();
+            *ops++ = Op_tf_b;       *args++ = skcms_sRGB_TransferFunction();
+            break;
     }
     if (srcFmt == skcms_PixelFormat_RGB_hhh_Norm ||
         srcFmt == skcms_PixelFormat_RGBA_hhhh_Norm) {
@@ -2529,6 +2536,13 @@
         case skcms_PixelFormat_RGBA_hhhh       >> 1: *ops++ = Op_store_hhhh;       break;
         case skcms_PixelFormat_RGB_fff         >> 1: *ops++ = Op_store_fff;        break;
         case skcms_PixelFormat_RGBA_ffff       >> 1: *ops++ = Op_store_ffff;       break;
+
+        case skcms_PixelFormat_RGBA_8888_sRGB >> 1:
+            *ops++ = Op_tf_r;       *args++ = skcms_sRGB_Inverse_TransferFunction();
+            *ops++ = Op_tf_g;       *args++ = skcms_sRGB_Inverse_TransferFunction();
+            *ops++ = Op_tf_b;       *args++ = skcms_sRGB_Inverse_TransferFunction();
+            *ops++ = Op_store_8888;
+            break;
     }
 
     auto run = baseline::run_program;
diff --git a/skcms.h b/skcms.h
index 165264c..486844a 100644
--- a/skcms.h
+++ b/skcms.h
@@ -209,6 +209,8 @@
     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_1010102,
     skcms_PixelFormat_BGRA_1010102,
diff --git a/tests.c b/tests.c
index 957a054..4bb2c46 100644
--- a/tests.c
+++ b/tests.c
@@ -985,9 +985,8 @@
     expect( skcms_Parse(ptr, len, &sRGB) );
 
     skcms_ICCProfile linear_sRGB = sRGB;
-    linear_sRGB.trc[0].parametric = (skcms_TransferFunction){ 1,1,0,0,0,0,0 };
-    linear_sRGB.trc[1].parametric = (skcms_TransferFunction){ 1,1,0,0,0,0,0 };
-    linear_sRGB.trc[2].parametric = (skcms_TransferFunction){ 1,1,0,0,0,0,0 };
+    skcms_TransferFunction linearTF = { 1,1,0,0,0,0,0 };
+    skcms_SetTransferFunction(&linear_sRGB, &linearTF);
 
     // Enough to hit all distinct bytes when interpreted as RGB 888.
     uint8_t src[258],
@@ -1617,6 +1616,69 @@
     expect(skcms_AreApproximateInverses(&inv_curve, &hlg));
 }
 
+static void test_RGBA_8888_sRGB() {
+    // We'll convert sRGB to Display P3 two ways and test they're equivalent.
+
+    // Method A: normal sRGB profile we're used to, paired with RGBA_8888.
+    const skcms_ICCProfile* sRGB = skcms_sRGB_profile();
+
+    // Method B: linear sRGB profile paired with RGBA_8888_sRGB.
+    skcms_ICCProfile linear_sRGB = *sRGB;
+    skcms_TransferFunction linearTF = { 1,1,0,0,0,0,0 };
+    skcms_SetTransferFunction(&linear_sRGB, &linearTF);
+
+    struct {
+        skcms_PixelFormat       fmt;
+        const skcms_ICCProfile* prof;
+        float                   f32[256];
+    } A = { skcms_PixelFormat_RGBA_8888     ,         sRGB, {0} },
+      B = { skcms_PixelFormat_RGBA_8888_sRGB, &linear_sRGB, {0} };
+
+    // We'll skip some bytes as alpha, but this is probably plenty of testing.
+    uint8_t bytes[256];
+    for (int i = 0; i < 256; i++) {
+        bytes[i] = i & 0xff;
+    }
+
+    // We transform to another gamut to make sure both methods go through a full-power transform.
+    void*  ptr;
+    size_t len;
+    expect(load_file("profiles/mobile/Display_P3_parametric.icc", &ptr,&len));
+    skcms_ICCProfile dp3;
+    expect(skcms_Parse(ptr, len, &dp3));
+
+    expect(skcms_Transform(bytes,                       A.fmt, skcms_AlphaFormat_Unpremul, A.prof,
+                           A.f32, skcms_PixelFormat_RGBA_ffff, skcms_AlphaFormat_Unpremul,   &dp3,
+                           64));
+    expect(skcms_Transform(bytes,                       B.fmt, skcms_AlphaFormat_Unpremul, B.prof,
+                           B.f32, skcms_PixelFormat_RGBA_ffff, skcms_AlphaFormat_Unpremul,   &dp3,
+                           64));
+
+    // The two methods should be bit-exact.
+    for (int i = 0; i < 256; i++) {
+        expect(A.f32[i] == B.f32[i]);
+    }
+
+    // Now let's transform both back and test they're round-trip the same.
+    for (int i = 0; i < 256; i++) { bytes[i] = 0; }
+    expect(skcms_Transform(A.f32, skcms_PixelFormat_RGBA_ffff, skcms_AlphaFormat_Unpremul,   &dp3,
+                           bytes,                       A.fmt, skcms_AlphaFormat_Unpremul, A.prof,
+                           64));
+    for (int i = 0; i < 256; i++) {
+        expect(bytes[i] == i);
+    }
+
+    for (int i = 0; i < 256; i++) { bytes[i] = 0; }
+    expect(skcms_Transform(B.f32, skcms_PixelFormat_RGBA_ffff, skcms_AlphaFormat_Unpremul,   &dp3,
+                           bytes,                       B.fmt, skcms_AlphaFormat_Unpremul, B.prof,
+                           64));
+    for (int i = 0; i < 256; i++) {
+        expect(bytes[i] == i);
+    }
+
+    free(ptr);
+}
+
 int main(int argc, char** argv) {
     bool regenTestData = false;
     for (int i = 1; i < argc; ++i) {
@@ -1656,6 +1718,7 @@
     test_HLG();
     test_PQ_invert();
     test_HLG_invert();
+    test_RGBA_8888_sRGB();
 
     // Temporarily disable some tests while getting FP16 compute working.
     if (!kFP16) {