Improve R+G+B op fusing logic.

Previously, we checked for R+G+B ops on every iteration through
the `select_curve_ops` loop because we needed to substitute the
fused RGB op before the A op was added. Now, we process the
channels in reverse order--yielding A/B/G/R instead of R/G/B/A.
With RGB at the end of the list, it is easy to substitute out
a fused op (simply move the cursor back and change the op) at
the end of the loop instead of checking on each loop iteration.

This isn't a big deal but the RGB check inside of the curve-
picking inner loop never felt like a good solution to me.

Change-Id: I978346ceb1b12e116a64af06e909e5adbfd7eedc
Reviewed-on: https://skia-review.googlesource.com/c/skcms/+/780816
Commit-Queue: John Stiles <johnstiles@google.com>
Auto-Submit: John Stiles <johnstiles@google.com>
Reviewed-by: Brian Osman <brianosman@google.com>
diff --git a/skcms.cc b/skcms.cc
index 2d85bd7..576c4f8 100644
--- a/skcms.cc
+++ b/skcms.cc
@@ -2399,42 +2399,47 @@
 }
 
 static int select_curve_ops(const skcms_Curve* curves, int numChannels, OpAndArg* ops) {
-    int position = 0;
-    for (int index = 0; index < numChannels; ++index) {
-        ops[position] = select_curve_op(&curves[index], index);
-        if (ops[position].arg) {
-            ++position;
+    // We process the channels in reverse order, yielding ops in ABGR order.
+    // (Working backwards allows us to fuse trailing B+G+R ops into a single RGB op.)
+    int cursor = 0;
+    for (int index = numChannels; index-- > 0; ) {
+        ops[cursor] = select_curve_op(&curves[index], index);
+        if (ops[cursor].arg) {
+            ++cursor;
         }
+    }
 
-        // Identify separate R/G/B functions which can be fused into a single op.
-        // (We do this check inside the loop in order to allow R+G+B+A to be fused into RGB+A.)
-        if (index == 2 && position == 3) {
-            struct FusableOps {
-                Op r, g, b, rgb;
-            };
-            static constexpr FusableOps kFusableOps[] = {
-                {Op::gamma_r,  Op::gamma_g,  Op::gamma_b,  Op::gamma_rgb},
-                {Op::tf_r,     Op::tf_g,     Op::tf_b,     Op::tf_rgb},
-                {Op::pq_r,     Op::pq_g,     Op::pq_b,     Op::pq_rgb},
-                {Op::hlg_r,    Op::hlg_g,    Op::hlg_b,    Op::hlg_rgb},
-                {Op::hlginv_r, Op::hlginv_g, Op::hlginv_b, Op::hlginv_rgb},
-            };
-            for (const FusableOps& fusableOp : kFusableOps) {
-                if (ops[0].op == fusableOp.r &&
-                    ops[1].op == fusableOp.g &&
-                    ops[2].op == fusableOp.b &&
-                    (0 == memcmp(ops[0].arg, ops[1].arg, sizeof(skcms_TransferFunction))) &&
-                    (0 == memcmp(ops[0].arg, ops[2].arg, sizeof(skcms_TransferFunction)))) {
+    // Identify separate B+G+R ops and fuse them into a single RGB op.
+    if (cursor >= 3) {
+        struct FusableOps {
+            Op r, g, b, rgb;
+        };
+        static constexpr FusableOps kFusableOps[] = {
+            {Op::gamma_r,  Op::gamma_g,  Op::gamma_b,  Op::gamma_rgb},
+            {Op::tf_r,     Op::tf_g,     Op::tf_b,     Op::tf_rgb},
+            {Op::pq_r,     Op::pq_g,     Op::pq_b,     Op::pq_rgb},
+            {Op::hlg_r,    Op::hlg_g,    Op::hlg_b,    Op::hlg_rgb},
+            {Op::hlginv_r, Op::hlginv_g, Op::hlginv_b, Op::hlginv_rgb},
+        };
 
-                    ops[0].op = fusableOp.rgb;
-                    position = 1;
-                    break;
-                }
+        int posR = cursor - 1;
+        int posG = cursor - 2;
+        int posB = cursor - 3;
+        for (const FusableOps& fusableOp : kFusableOps) {
+            if (ops[posR].op == fusableOp.r &&
+                ops[posG].op == fusableOp.g &&
+                ops[posB].op == fusableOp.b &&
+                (0 == memcmp(ops[posR].arg, ops[posG].arg, sizeof(skcms_TransferFunction))) &&
+                (0 == memcmp(ops[posR].arg, ops[posB].arg, sizeof(skcms_TransferFunction)))) {
+                // Fuse the three matching ops into one.
+                ops[posB].op = fusableOp.rgb;
+                cursor -= 2;
+                break;
             }
         }
     }
 
-    return position;
+    return cursor;
 }
 
 static size_t bytes_per_pixel(skcms_PixelFormat fmt) {