Transform improvements

 - special case x == 1 in approx_powf()
 - skip gamut transforms when src gamut == dst gamut
 - add test of sRGB byte to linear sRGB float

Change-Id: Ib7a41d80afa456fc69924efb4ca4e230e15a6a01
Reviewed-on: https://skia-review.googlesource.com/113718
Commit-Queue: Brian Osman <brianosman@google.com>
Reviewed-by: Brian Osman <brianosman@google.com>
diff --git a/src/Transform.c b/src/Transform.c
index df7fe8d..94e39d7 100644
--- a/src/Transform.c
+++ b/src/Transform.c
@@ -272,8 +272,7 @@
 
     // TODO: The rest of this could perhaps be specialized further knowing 0 <= y < 1.
     assert (0 <= y && y < 1);
-    return r * (F)if_then_else(x == F0, F0
-                                      , approx_pow2(approx_log2(x) * y));
+    return (F)if_then_else((x == F0) | (x == F1), x, r * approx_pow2(approx_log2(x) * y));
 }
 
 // Return tf(x).
@@ -929,7 +928,8 @@
     if (dstProfile != srcProfile ||
         srcAlpha == skcms_AlphaFormat_PremulLinear ||
         dstAlpha == skcms_AlphaFormat_PremulLinear) {
-        // Linearize using TRC curves, either parametric or 16-bit tables.
+
+        // Linearize source using TRC curves, either parametric or 16-bit tables.
         if (srcProfile->has_trc && srcProfile->has_toXYZD50) {
             void*       tf_stages[] = { (void*)      tf_r, (void*)      tf_g, (void*)      tf_b };
             void* table_16_stages[] = { (void*)table_16_r, (void*)table_16_g, (void*)table_16_b };
@@ -950,24 +950,43 @@
             if (srcAlpha == skcms_AlphaFormat_PremulLinear) {
                 *ip++ = (void*)unpremul;
             }
-
-            *ip++   = (void*)matrix_3x3;
-            *args++ = &srcProfile->toXYZD50;
         } else {
             // TODO: A2B
         }
 
-        // Back to dst RGB.  (TODO: support tables here?)
+        // We only support destination gamuts that can be transformed from XYZD50.
+        if (!dstProfile->has_toXYZD50) {
+            return false;
+        }
+
+        // Sources with a toXYZD50 matrix still need to be transformed.
+        // Others (A2B) should already be in XYZD50 at this point.
+        static const skcms_Matrix3x3 I = {{
+            { 1.0f, 0.0f, 0.0f },
+            { 0.0f, 1.0f, 0.0f },
+            { 0.0f, 0.0f, 1.0f },
+        }};
+        const skcms_Matrix3x3* to_xyz = srcProfile->has_toXYZD50 ? &srcProfile->toXYZD50 : &I;
+
+        // There's a chance the source and destination gamuts are identical,
+        // in which case we can skip the gamut transform.
+        if (0 != memcmp(&dstProfile->toXYZD50, to_xyz, sizeof(skcms_Matrix3x3))) {
+            if (!skcms_Matrix3x3_invert(&dstProfile->toXYZD50, &from_xyz)) {
+                return false;
+            }
+            // TODO: concat these here and only append one matrix_3x3 stage.
+            *ip++ = (void*)matrix_3x3; *args++ =    to_xyz;
+            *ip++ = (void*)matrix_3x3; *args++ = &from_xyz;
+        }
+
+        // Encode back to dst RGB using its parametric transfer functions.
         if (dstProfile->has_trc &&
             dstProfile->trc[0].table_entries == 0 &&
             dstProfile->trc[1].table_entries == 0 &&
             dstProfile->trc[2].table_entries == 0 &&
             skcms_TransferFunction_invert(&dstProfile->trc[0].parametric, &inv_dst_tf_r) &&
             skcms_TransferFunction_invert(&dstProfile->trc[1].parametric, &inv_dst_tf_g) &&
-            skcms_TransferFunction_invert(&dstProfile->trc[2].parametric, &inv_dst_tf_b) &&
-            dstProfile->has_toXYZD50 &&
-            skcms_Matrix3x3_invert(&dstProfile->toXYZD50,  &from_xyz)) {
-            *ip++ = (void*)matrix_3x3; *args++ = &from_xyz;
+            skcms_TransferFunction_invert(&dstProfile->trc[2].parametric, &inv_dst_tf_b)) {
 
             if (dstAlpha == skcms_AlphaFormat_PremulLinear) {
                 *ip++ = (void*)premul;
diff --git a/tests.c b/tests.c
index 33fa6a5..aada0c7 100644
--- a/tests.c
+++ b/tests.c
@@ -1028,6 +1028,38 @@
     free(ptr);
 }
 
+static void test_ByteToLinearFloat() {
+    uint32_t src[1] = { 0xFFFFFFFF };
+    float dst[4];
+
+    void*  srgb_ptr;
+    size_t srgb_len;
+    expect(load_file("profiles/mobile/sRGB_parametric.icc", &srgb_ptr, &srgb_len));
+
+    skcms_ICCProfile srgb, srgb_linear;
+    expect(skcms_Parse(srgb_ptr, srgb_len, &srgb));
+    srgb_linear = srgb;
+    for (int i = 0; i < 3; ++i) {
+        srgb_linear.trc[i].parametric.g = 1.0f;
+        srgb_linear.trc[i].parametric.a = 1.0f;
+        srgb_linear.trc[i].parametric.b = 0.0f;
+        srgb_linear.trc[i].parametric.c = 0.0f;
+        srgb_linear.trc[i].parametric.d = 0.0f;
+        srgb_linear.trc[i].parametric.e = 0.0f;
+        srgb_linear.trc[i].parametric.f = 0.0f;
+    }
+
+    skcms_Transform(src, skcms_PixelFormat_BGRA_8888, skcms_AlphaFormat_Unpremul, &srgb,
+                    dst, skcms_PixelFormat_RGBA_ffff, skcms_AlphaFormat_Unpremul, &srgb_linear, 1);
+
+    expect(dst[0] == 1.0f);
+    expect(dst[1] == 1.0f);
+    expect(dst[2] == 1.0f);
+    expect(dst[3] == 1.0f);
+
+    free(srgb_ptr);
+}
+
 int main(int argc, char** argv) {
     bool regenTestData = false;
     for (int i = 1; i < argc; ++i) {
@@ -1052,6 +1084,7 @@
     test_SimpleRoundTrip();
     test_FloatRoundTrips();
     test_sRGB_AllBytes();
+    test_ByteToLinearFloat();
     test_TRC_Table16();
     test_Premul();