extend modecolorfilter to 4f

BUG=skia:
GOLD_TRYBOT_URL= https://gold.skia.org/search2?unt=true&query=source_type%3Dgm&master=false&issue=1670063002

Review URL: https://codereview.chromium.org/1670063002
diff --git a/gm/color4f.cpp b/gm/color4f.cpp
index e912654..9e6fcf6 100644
--- a/gm/color4f.cpp
+++ b/gm/color4f.cpp
@@ -45,10 +45,14 @@
     return SkColorFilter::CreateComposeFilter(a, b);
 }
 
+static SkColorFilter* make_cf2() {
+    return SkColorFilter::CreateModeFilter(0x8044CC88, SkXfermode::kSrcATop_Mode);
+}
+
 static void draw_into_canvas(SkCanvas* canvas) {
-    const SkRect r = SkRect::MakeWH(100, 100);
+    const SkRect r = SkRect::MakeWH(50, 100);
     SkShader* (*shaders[])() { make_opaque_color, make_alpha_color };
-    SkColorFilter* (*filters[])() { make_cf_null, make_cf0, make_cf1 };
+    SkColorFilter* (*filters[])() { make_cf_null, make_cf0, make_cf1, make_cf2 };
     
     SkPaint paint;
     for (auto shProc : shaders) {
@@ -56,13 +60,13 @@
         for (auto cfProc : filters) {
             SkSafeUnref(paint.setColorFilter(cfProc()));
             canvas->drawRect(r, paint);
-            canvas->translate(120, 0);
+            canvas->translate(60, 0);
         }
     }
 }
 
-DEF_SIMPLE_GM(color4f, canvas, 620, 260) {
-    canvas->translate(20, 20);
+DEF_SIMPLE_GM(color4f, canvas, 1024, 260) {
+    canvas->translate(10, 10);
 
     SkPaint bg;
     // need the target to be opaque, so we can draw it to the screen
@@ -71,7 +75,7 @@
 
     SkColorProfileType const profiles[] { kLinear_SkColorProfileType, kSRGB_SkColorProfileType };
     for (auto profile : profiles) {
-        const SkImageInfo info = SkImageInfo::Make(600, 100, kN32_SkColorType, kPremul_SkAlphaType,
+        const SkImageInfo info = SkImageInfo::Make(1024, 100, kN32_SkColorType, kPremul_SkAlphaType,
                                                    profile);
         SkAutoTUnref<SkSurface> surface(SkSurface::NewRaster(info));
         surface->getCanvas()->drawPaint(bg);
diff --git a/include/core/SkColor.h b/include/core/SkColor.h
index b1571c7..101a9bd 100644
--- a/include/core/SkColor.h
+++ b/include/core/SkColor.h
@@ -192,6 +192,8 @@
 #endif
 };
 
+typedef SkPM4f (*SkXfermodeProc4f)(const SkPM4f& src, const SkPM4f& dst);
+
 /*
  *  The float values are 0...1 unpremultiplied
  */
diff --git a/include/core/SkXfermode.h b/include/core/SkXfermode.h
index f2d8d57..9549452 100644
--- a/include/core/SkXfermode.h
+++ b/include/core/SkXfermode.h
@@ -144,6 +144,7 @@
         porter-duff transfer mode.
      */
     static SkXfermodeProc GetProc(Mode mode);
+    static SkXfermodeProc4f GetProc4f(Mode);
 
     /**
      *  If the specified mode can be represented by a pair of Coeff, then return
@@ -241,6 +242,7 @@
                               int count, const SkAlpha coverage[]);
     typedef void (*PM4fProcN)(const PM4fState&, uint32_t dst[], const SkPM4f src[],
                               int count, const SkAlpha coverage[]);
+
     static PM4fProc1 GetPM4fProc1(Mode, uint32_t flags);
     static PM4fProcN GetPM4fProcN(Mode, uint32_t flags);
     virtual PM4fProc1 getPM4fProc1(uint32_t flags) const;
diff --git a/src/core/SkModeColorFilter.cpp b/src/core/SkModeColorFilter.cpp
index df1051a..14e3fb7 100644
--- a/src/core/SkModeColorFilter.cpp
+++ b/src/core/SkModeColorFilter.cpp
@@ -37,20 +37,30 @@
 }
 
 uint32_t SkModeColorFilter::getFlags() const {
+    uint32_t flags = kSupports4f_Flag;
     switch (fMode) {
         case SkXfermode::kDst_Mode:      //!< [Da, Dc]
         case SkXfermode::kSrcATop_Mode:  //!< [Da, Sc * Da + (1 - Sa) * Dc]
-            return kAlphaUnchanged_Flag;
+            flags |= kAlphaUnchanged_Flag;
         default:
             break;
     }
-    return 0;
+    return flags;
 }
 
 void SkModeColorFilter::filterSpan(const SkPMColor shader[], int count, SkPMColor result[]) const {
     SkPMColor       color = fPMColor;
     SkXfermodeProc  proc = fProc;
+    
+    for (int i = 0; i < count; i++) {
+        result[i] = proc(color, shader[i]);
+    }
+}
 
+void SkModeColorFilter::filterSpan4f(const SkPM4f shader[], int count, SkPM4f result[]) const {
+    SkPM4f            color = SkPM4f::FromPMColor(fPMColor);
+    SkXfermodeProc4f  proc = SkXfermode::GetProc4f(fMode);
+    
     for (int i = 0; i < count; i++) {
         result[i] = proc(color, shader[i]);
     }
diff --git a/src/core/SkModeColorFilter.h b/src/core/SkModeColorFilter.h
index 9fa7717..44e618a 100644
--- a/src/core/SkModeColorFilter.h
+++ b/src/core/SkModeColorFilter.h
@@ -24,6 +24,7 @@
     bool asColorMode(SkColor*, SkXfermode::Mode*) const override;
     uint32_t getFlags() const override;
     void filterSpan(const SkPMColor shader[], int count, SkPMColor result[]) const override;
+    void filterSpan4f(const SkPM4f shader[], int count, SkPM4f result[]) const override;
 
 #ifndef SK_IGNORE_TO_STRING
     void toString(SkString* str) const override;
diff --git a/src/core/SkXfermode.cpp b/src/core/SkXfermode.cpp
index 86e3ed5..8108319 100644
--- a/src/core/SkXfermode.cpp
+++ b/src/core/SkXfermode.cpp
@@ -47,6 +47,54 @@
 }
 
 ///////////////////////////////////////////////////////////////////////////////
+#include "SkNx.h"
+
+static Sk4f     alpha(const Sk4f& color) { return Sk4f(color.kth<3>()); }
+static Sk4f inv_alpha(const Sk4f& color) { return Sk4f(1 - color.kth<3>()); }
+static Sk4f     pin_1(const Sk4f& value) { return Sk4f::Min(value, Sk4f(1)); }
+
+static Sk4f    clear_4f(const Sk4f& s, const Sk4f& d) { return Sk4f(0); }
+static Sk4f      src_4f(const Sk4f& s, const Sk4f& d) { return s; }
+static Sk4f      dst_4f(const Sk4f& s, const Sk4f& d) { return d; }
+static Sk4f  srcover_4f(const Sk4f& s, const Sk4f& d) { return s + inv_alpha(s) * d; }
+static Sk4f  dstover_4f(const Sk4f& s, const Sk4f& d) { return d + inv_alpha(d) * s; }
+static Sk4f    srcin_4f(const Sk4f& s, const Sk4f& d) { return s * alpha(d); }
+static Sk4f    dstin_4f(const Sk4f& s, const Sk4f& d) { return d * alpha(s); }
+static Sk4f   srcout_4f(const Sk4f& s, const Sk4f& d) { return s * inv_alpha(d); }
+static Sk4f   dstout_4f(const Sk4f& s, const Sk4f& d) { return d * inv_alpha(s); }
+static Sk4f  srcatop_4f(const Sk4f& s, const Sk4f& d) { return s * alpha(d) + d * inv_alpha(s); }
+static Sk4f  dstatop_4f(const Sk4f& s, const Sk4f& d) { return d * alpha(s) + s * inv_alpha(d); }
+static Sk4f      xor_4f(const Sk4f& s, const Sk4f& d) { return s * inv_alpha(d) + d * inv_alpha(s);}
+static Sk4f     plus_4f(const Sk4f& s, const Sk4f& d) { return pin_1(s + d); }
+static Sk4f modulate_4f(const Sk4f& s, const Sk4f& d) { return s * d; }
+static Sk4f   screen_4f(const Sk4f& s, const Sk4f& d) { return s + d - s * d; }
+
+static Sk4f multiply_4f(const Sk4f& s, const Sk4f& d) {
+    return s * inv_alpha(d) + d * inv_alpha(s) + s * d;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static SkPM4f as_pm4f(const Sk4f& x) {
+    SkPM4f pm4;
+    x.store(pm4.fVec);
+    return pm4;
+}
+
+static Sk4f as_4f(const SkPM4f& pm4) {
+    return Sk4f::Load(pm4.fVec);
+}
+
+template <Sk4f (blend)(const Sk4f&, const Sk4f&)>
+SkPM4f proc_4f(const SkPM4f& src, const SkPM4f& dst) {
+    return as_pm4f(blend(as_4f(src), as_4f(dst)));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static SkPM4f not_implemented_yet_proc4f(const SkPM4f& src, const SkPM4f& dst) {
+    return {{ 0.5f, 1.0f, 0.25f, 0.5f }};
+}
 
 //  kClear_Mode,    //!< [0, 0]
 static SkPMColor clear_modeproc(SkPMColor src, SkPMColor dst) {
@@ -219,6 +267,9 @@
     int b = overlay_byte(SkGetPackedB32(src), SkGetPackedB32(dst), sa, da);
     return SkPackARGB32(a, r, g, b);
 }
+static SkPM4f overlay_proc4f(const SkPM4f& src, const SkPM4f& dst) {
+    return not_implemented_yet_proc4f(src, dst);
+}
 
 // kDarken_Mode
 static inline int darken_byte(int sc, int dc, int sa, int da) {
@@ -241,6 +292,9 @@
     int b = darken_byte(SkGetPackedB32(src), SkGetPackedB32(dst), sa, da);
     return SkPackARGB32(a, r, g, b);
 }
+static SkPM4f darken_proc4f(const SkPM4f& src, const SkPM4f& dst) {
+    return not_implemented_yet_proc4f(src, dst);
+}
 
 // kLighten_Mode
 static inline int lighten_byte(int sc, int dc, int sa, int da) {
@@ -263,6 +317,9 @@
     int b = lighten_byte(SkGetPackedB32(src), SkGetPackedB32(dst), sa, da);
     return SkPackARGB32(a, r, g, b);
 }
+static SkPM4f lighten_proc4f(const SkPM4f& src, const SkPM4f& dst) {
+    return not_implemented_yet_proc4f(src, dst);
+}
 
 // kColorDodge_Mode
 static inline int colordodge_byte(int sc, int dc, int sa, int da) {
@@ -287,6 +344,9 @@
     int b = colordodge_byte(SkGetPackedB32(src), SkGetPackedB32(dst), sa, da);
     return SkPackARGB32(a, r, g, b);
 }
+static SkPM4f colordodge_proc4f(const SkPM4f& src, const SkPM4f& dst) {
+    return not_implemented_yet_proc4f(src, dst);
+}
 
 // kColorBurn_Mode
 static inline int colorburn_byte(int sc, int dc, int sa, int da) {
@@ -311,6 +371,9 @@
     int b = colorburn_byte(SkGetPackedB32(src), SkGetPackedB32(dst), sa, da);
     return SkPackARGB32(a, r, g, b);
 }
+static SkPM4f colorburn_proc4f(const SkPM4f& src, const SkPM4f& dst) {
+    return not_implemented_yet_proc4f(src, dst);
+}
 
 // kHardLight_Mode
 static inline int hardlight_byte(int sc, int dc, int sa, int da) {
@@ -331,6 +394,9 @@
     int b = hardlight_byte(SkGetPackedB32(src), SkGetPackedB32(dst), sa, da);
     return SkPackARGB32(a, r, g, b);
 }
+static SkPM4f hardlight_proc4f(const SkPM4f& src, const SkPM4f& dst) {
+    return not_implemented_yet_proc4f(src, dst);
+}
 
 // returns 255 * sqrt(n/255)
 static U8CPU sqrt_unit_byte(U8CPU n) {
@@ -361,6 +427,9 @@
     int b = softlight_byte(SkGetPackedB32(src), SkGetPackedB32(dst), sa, da);
     return SkPackARGB32(a, r, g, b);
 }
+static SkPM4f softlight_proc4f(const SkPM4f& src, const SkPM4f& dst) {
+    return not_implemented_yet_proc4f(src, dst);
+}
 
 // kDifference_Mode
 static inline int difference_byte(int sc, int dc, int sa, int da) {
@@ -376,6 +445,9 @@
     int b = difference_byte(SkGetPackedB32(src), SkGetPackedB32(dst), sa, da);
     return SkPackARGB32(a, r, g, b);
 }
+static SkPM4f difference_proc4f(const SkPM4f& src, const SkPM4f& dst) {
+    return not_implemented_yet_proc4f(src, dst);
+}
 
 // kExclusion_Mode
 static inline int exclusion_byte(int sc, int dc, int, int) {
@@ -395,6 +467,9 @@
     int b = exclusion_byte(SkGetPackedB32(src), SkGetPackedB32(dst), sa, da);
     return SkPackARGB32(a, r, g, b);
 }
+static SkPM4f exclusion_proc4f(const SkPM4f& src, const SkPM4f& dst) {
+    return not_implemented_yet_proc4f(src, dst);
+}
 
 // The CSS compositing spec introduces the following formulas:
 // (See https://dvcs.w3.org/hg/FXTF/rawfile/tip/compositing/index.html#blendingnonseparable)
@@ -510,6 +585,9 @@
     int b = blendfunc_nonsep_byte(sb, db, sa, da, Sb);
     return SkPackARGB32(a, r, g, b);
 }
+static SkPM4f hue_proc4f(const SkPM4f& src, const SkPM4f& dst) {
+    return not_implemented_yet_proc4f(src, dst);
+}
 
 // kSaturation_Mode
 // B(Cb, Cs) = SetLum(SetSat(Cb, Sat(Cs)), Lum(Cb))
@@ -544,6 +622,9 @@
     int b = blendfunc_nonsep_byte(sb, db, sa, da, Db);
     return SkPackARGB32(a, r, g, b);
 }
+static SkPM4f saturation_proc4f(const SkPM4f& src, const SkPM4f& dst) {
+    return not_implemented_yet_proc4f(src, dst);
+}
 
 // kColor_Mode
 // B(Cb, Cs) = SetLum(Cs, Lum(Cb))
@@ -577,6 +658,9 @@
     int b = blendfunc_nonsep_byte(sb, db, sa, da, Sb);
     return SkPackARGB32(a, r, g, b);
 }
+static SkPM4f color_proc4f(const SkPM4f& src, const SkPM4f& dst) {
+    return not_implemented_yet_proc4f(src, dst);
+}
 
 // kLuminosity_Mode
 // B(Cb, Cs) = SetLum(Cb, Lum(Cs))
@@ -610,38 +694,41 @@
     int b = blendfunc_nonsep_byte(sb, db, sa, da, Db);
     return SkPackARGB32(a, r, g, b);
 }
+static SkPM4f luminosity_proc4f(const SkPM4f& src, const SkPM4f& dst) {
+    return not_implemented_yet_proc4f(src, dst);
+}
 
 const ProcCoeff gProcCoeffs[] = {
-    { clear_modeproc,   SkXfermode::kZero_Coeff,    SkXfermode::kZero_Coeff },
-    { src_modeproc,     SkXfermode::kOne_Coeff,     SkXfermode::kZero_Coeff },
-    { dst_modeproc,     SkXfermode::kZero_Coeff,    SkXfermode::kOne_Coeff },
-    { srcover_modeproc, SkXfermode::kOne_Coeff,     SkXfermode::kISA_Coeff },
-    { dstover_modeproc, SkXfermode::kIDA_Coeff,     SkXfermode::kOne_Coeff },
-    { srcin_modeproc,   SkXfermode::kDA_Coeff,      SkXfermode::kZero_Coeff },
-    { dstin_modeproc,   SkXfermode::kZero_Coeff,    SkXfermode::kSA_Coeff },
-    { srcout_modeproc,  SkXfermode::kIDA_Coeff,     SkXfermode::kZero_Coeff },
-    { dstout_modeproc,  SkXfermode::kZero_Coeff,    SkXfermode::kISA_Coeff },
-    { srcatop_modeproc, SkXfermode::kDA_Coeff,      SkXfermode::kISA_Coeff },
-    { dstatop_modeproc, SkXfermode::kIDA_Coeff,     SkXfermode::kSA_Coeff },
-    { xor_modeproc,     SkXfermode::kIDA_Coeff,     SkXfermode::kISA_Coeff },
+    { clear_modeproc,       proc_4f<clear_4f>,      SkXfermode::kZero_Coeff,    SkXfermode::kZero_Coeff },
+    { src_modeproc,         proc_4f<src_4f>,        SkXfermode::kOne_Coeff,     SkXfermode::kZero_Coeff },
+    { dst_modeproc,         proc_4f<dst_4f>,        SkXfermode::kZero_Coeff,    SkXfermode::kOne_Coeff  },
+    { srcover_modeproc,     proc_4f<srcover_4f>,    SkXfermode::kOne_Coeff,     SkXfermode::kISA_Coeff  },
+    { dstover_modeproc,     proc_4f<dstover_4f>,    SkXfermode::kIDA_Coeff,     SkXfermode::kOne_Coeff  },
+    { srcin_modeproc,       proc_4f<srcin_4f>,      SkXfermode::kDA_Coeff,      SkXfermode::kZero_Coeff },
+    { dstin_modeproc,       proc_4f<dstin_4f>,      SkXfermode::kZero_Coeff,    SkXfermode::kSA_Coeff   },
+    { srcout_modeproc,      proc_4f<srcout_4f>,     SkXfermode::kIDA_Coeff,     SkXfermode::kZero_Coeff },
+    { dstout_modeproc,      proc_4f<dstout_4f>,     SkXfermode::kZero_Coeff,    SkXfermode::kISA_Coeff  },
+    { srcatop_modeproc,     proc_4f<srcatop_4f>,    SkXfermode::kDA_Coeff,      SkXfermode::kISA_Coeff  },
+    { dstatop_modeproc,     proc_4f<dstatop_4f>,    SkXfermode::kIDA_Coeff,     SkXfermode::kSA_Coeff   },
+    { xor_modeproc,         proc_4f<xor_4f>,        SkXfermode::kIDA_Coeff,     SkXfermode::kISA_Coeff  },
 
-    { plus_modeproc,    SkXfermode::kOne_Coeff,     SkXfermode::kOne_Coeff },
-    { modulate_modeproc,SkXfermode::kZero_Coeff,    SkXfermode::kSC_Coeff },
-    { screen_modeproc,  SkXfermode::kOne_Coeff,     SkXfermode::kISC_Coeff },
-    { overlay_modeproc,     CANNOT_USE_COEFF,       CANNOT_USE_COEFF },
-    { darken_modeproc,      CANNOT_USE_COEFF,       CANNOT_USE_COEFF },
-    { lighten_modeproc,     CANNOT_USE_COEFF,       CANNOT_USE_COEFF },
-    { colordodge_modeproc,  CANNOT_USE_COEFF,       CANNOT_USE_COEFF },
-    { colorburn_modeproc,   CANNOT_USE_COEFF,       CANNOT_USE_COEFF },
-    { hardlight_modeproc,   CANNOT_USE_COEFF,       CANNOT_USE_COEFF },
-    { softlight_modeproc,   CANNOT_USE_COEFF,       CANNOT_USE_COEFF },
-    { difference_modeproc,  CANNOT_USE_COEFF,       CANNOT_USE_COEFF },
-    { exclusion_modeproc,   CANNOT_USE_COEFF,       CANNOT_USE_COEFF },
-    { multiply_modeproc,    CANNOT_USE_COEFF,       CANNOT_USE_COEFF },
-    { hue_modeproc,         CANNOT_USE_COEFF,       CANNOT_USE_COEFF },
-    { saturation_modeproc,  CANNOT_USE_COEFF,       CANNOT_USE_COEFF },
-    { color_modeproc,       CANNOT_USE_COEFF,       CANNOT_USE_COEFF },
-    { luminosity_modeproc,  CANNOT_USE_COEFF,       CANNOT_USE_COEFF },
+    { plus_modeproc,        proc_4f<plus_4f>,       SkXfermode::kOne_Coeff,     SkXfermode::kOne_Coeff  },
+    { modulate_modeproc,    proc_4f<modulate_4f>,   SkXfermode::kZero_Coeff,    SkXfermode::kSC_Coeff   },
+    { screen_modeproc,      proc_4f<screen_4f>,     SkXfermode::kOne_Coeff,     SkXfermode::kISC_Coeff  },
+    { overlay_modeproc,     overlay_proc4f,   CANNOT_USE_COEFF,       CANNOT_USE_COEFF },
+    { darken_modeproc,      darken_proc4f,   CANNOT_USE_COEFF,       CANNOT_USE_COEFF },
+    { lighten_modeproc,     lighten_proc4f,   CANNOT_USE_COEFF,       CANNOT_USE_COEFF },
+    { colordodge_modeproc,  colordodge_proc4f,   CANNOT_USE_COEFF,       CANNOT_USE_COEFF },
+    { colorburn_modeproc,   colorburn_proc4f,   CANNOT_USE_COEFF,       CANNOT_USE_COEFF },
+    { hardlight_modeproc,   hardlight_proc4f,   CANNOT_USE_COEFF,       CANNOT_USE_COEFF },
+    { softlight_modeproc,   softlight_proc4f,   CANNOT_USE_COEFF,       CANNOT_USE_COEFF },
+    { difference_modeproc,  difference_proc4f,   CANNOT_USE_COEFF,       CANNOT_USE_COEFF },
+    { exclusion_modeproc,   exclusion_proc4f,   CANNOT_USE_COEFF,       CANNOT_USE_COEFF },
+    { multiply_modeproc,    proc_4f<multiply_4f>,   CANNOT_USE_COEFF,       CANNOT_USE_COEFF },
+    { hue_modeproc,         hue_proc4f,   CANNOT_USE_COEFF,       CANNOT_USE_COEFF },
+    { saturation_modeproc,  saturation_proc4f,   CANNOT_USE_COEFF,       CANNOT_USE_COEFF },
+    { color_modeproc,       color_proc4f,   CANNOT_USE_COEFF,       CANNOT_USE_COEFF },
+    { luminosity_modeproc,  luminosity_proc4f,   CANNOT_USE_COEFF,       CANNOT_USE_COEFF },
 };
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -1006,6 +1093,14 @@
     return proc;
 }
 
+SkXfermodeProc4f SkXfermode::GetProc4f(Mode mode) {
+    SkXfermodeProc4f  proc = nullptr;
+    if ((unsigned)mode < kModeCount) {
+        proc = gProcCoeffs[mode].fProc4f;
+    }
+    return proc;
+}
+
 bool SkXfermode::ModeAsCoeff(Mode mode, Coeff* src, Coeff* dst) {
     SkASSERT(SK_ARRAY_COUNT(gProcCoeffs) == kModeCount);
 
diff --git a/src/core/SkXfermode_proccoeff.h b/src/core/SkXfermode_proccoeff.h
index f86af2d..264d65c 100644
--- a/src/core/SkXfermode_proccoeff.h
+++ b/src/core/SkXfermode_proccoeff.h
@@ -14,6 +14,7 @@
 
 struct ProcCoeff {
     SkXfermodeProc      fProc;
+    SkXfermodeProc4f    fProc4f;
     SkXfermode::Coeff   fSC;
     SkXfermode::Coeff   fDC;
 };
diff --git a/tests/SkColor4fTest.cpp b/tests/SkColor4fTest.cpp
index 48a0c38..b9dcd0d 100644
--- a/tests/SkColor4fTest.cpp
+++ b/tests/SkColor4fTest.cpp
@@ -168,9 +168,9 @@
         SkColorFilter* (*fFact)();
         bool           fSupports4f;
     } recs[] = {
-        { make_mode_cf,     false },
+        { make_mode_cf,     true },
         { make_mx_cf,       true },
-        { make_compose_cf,  false },
+        { make_compose_cf,  true },
     };
 
     // prepare the src
@@ -197,3 +197,45 @@
         }
     }
 }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+typedef SkPM4f (*SkXfermodeProc4f)(const SkPM4f& src, const SkPM4f& dst);
+
+static bool compare_procs(SkXfermodeProc proc32, SkXfermodeProc4f proc4f) {
+    const float kTolerance = 1.0f / 255;
+
+    const SkColor colors[] = {
+        0, 0xFF000000, 0xFFFFFFFF, 0x80FF0000
+    };
+
+    for (auto s32 : colors) {
+        SkPMColor s_pm32 = SkPreMultiplyColor(s32);
+        SkPM4f    s_pm4f = SkColor4f::FromColor(s32).premul();
+        for (auto d32 : colors) {
+            SkPMColor d_pm32 = SkPreMultiplyColor(d32);
+            SkPM4f    d_pm4f = SkColor4f::FromColor(d32).premul();
+
+            SkPMColor r32 = proc32(s_pm32, d_pm32);
+            SkPM4f    r4f = proc4f(s_pm4f, d_pm4f);
+
+            SkPM4f r32_4f = SkPM4f::FromPMColor(r32);
+            if (!nearly_equal(r4f, r32_4f, kTolerance)) {
+                return false;
+            }
+        }
+    }
+    return true;
+}
+
+// Check that our Proc and Proc4f return (nearly) the same results
+//
+DEF_TEST(Color4f_xfermode_proc4f, reporter) {
+    // TODO: extend xfermodes so that all cases can be tested.
+    //
+    for (int mode = SkXfermode::kClear_Mode; mode <= SkXfermode::kScreen_Mode; ++mode) {
+        SkXfermodeProc   proc32 = SkXfermode::GetProc((SkXfermode::Mode)mode);
+        SkXfermodeProc4f proc4f = SkXfermode::GetProc4f((SkXfermode::Mode)mode);
+        REPORTER_ASSERT(reporter, compare_procs(proc32, proc4f));
+    }
+}