Reland "add getAlphaf() to pixmap/bitmap"

This is a reland of f3ebd312f21325f9afc4479fd7a4e90a32ffb920

PS 2: use faster half->float routine
PS 3: relax tolerance to 2^-10 for half, keeping others 2^-12

Original change's description:
> add getAlphaf() to pixmap/bitmap
>
> Convenient for just extracting alpha (and more efficient than getColor()) and
> works for super-normal formats w/o loss of precision.
>
> Somewhat inspired by examining multiple chrome call-sites for getColor(), where
> chrome only really cared about the alpha. This new method runs about twice
> as fast as getColor() for the simple cases (i.e. no colorspace xforms), and
> even faster in the complex cases (since retrieving alpha doesn't care about
> colorspaces).
>
> Bug: skia:
> Change-Id: I7cd5a2c7f78de781aaa69dd1aa0dba3c532fcb73
> Reviewed-on: https://skia-review.googlesource.com/155606
> Commit-Queue: Mike Reed <reed@google.com>
> Reviewed-by: Mike Klein <mtklein@google.com>

Cq-Include-Trybots: skia.primary:Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Debug-All-Android,Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-SK_CPU_LIMIT_SSE2,Test-Debian9-GCC-GCE-CPU-AVX2-x86_64-Release-All,Test-Win2016-MSVC-GCE-CPU-AVX2-x86_64-Release-All,Test-Win8-Clang-Golo-CPU-AVX-x86_64-Debug-All
Bug: skia:
Change-Id: Ie94e5c89e185fde12cbd6c56ed4026c4dc5a1623
Reviewed-on: https://skia-review.googlesource.com/156242
Commit-Queue: Ravi Mistry <rmistry@google.com>
Commit-Queue: Mike Klein <mtklein@google.com>
Reviewed-by: Mike Klein <mtklein@google.com>
diff --git a/bench/ReadPixBench.cpp b/bench/ReadPixBench.cpp
index 55078d9..b9a2335 100644
--- a/bench/ReadPixBench.cpp
+++ b/bench/ReadPixBench.cpp
@@ -105,3 +105,47 @@
     typedef Benchmark INHERITED;
 };
 DEF_BENCH( return new PixmapOrientBench(); )
+
+
+class GetAlphafBench : public Benchmark {
+    SkString fName;
+    SkColorType fCT;
+public:
+    GetAlphafBench(SkColorType ct, const char label[]) : fCT(ct) {
+        fName.printf("getalphaf_%s", label);
+    }
+
+protected:
+    void onDelayedSetup() override {
+        fBM.allocPixels(SkImageInfo::Make(1024, 1024, fCT, kPremul_SkAlphaType));
+        fBM.eraseColor(0x88112233);
+    }
+
+    const char* onGetName() override {
+        return fName.c_str();
+    }
+
+    bool isSuitableFor(Backend backend) override {
+        return backend == kNonRendering_Backend;
+    }
+
+    void onDraw(int loops, SkCanvas*) override {
+        for (int i = 0; i < loops; ++i) {
+            for (int y = 0; y < fBM.height(); ++y) {
+                for (int x = 0; x < fBM.width(); ++x) {
+                    fBM.getAlphaf(x, y);
+                }
+            }
+        }
+    }
+
+private:
+    SkBitmap fBM;
+
+    typedef Benchmark INHERITED;
+};
+DEF_BENCH( return new GetAlphafBench(kN32_SkColorType, "rgba"); )
+DEF_BENCH( return new GetAlphafBench(kRGB_888x_SkColorType, "rgbx"); )
+DEF_BENCH( return new GetAlphafBench(kRGBA_F16_SkColorType, "f16"); )
+DEF_BENCH( return new GetAlphafBench(kRGBA_F32_SkColorType, "f32"); )
+
diff --git a/include/core/SkBitmap.h b/include/core/SkBitmap.h
index ab3c7e9..7ad6c06 100644
--- a/include/core/SkBitmap.h
+++ b/include/core/SkBitmap.h
@@ -846,6 +846,18 @@
         return this->pixmap().getColor(x, y);
     }
 
+    /** Look up the pixel at (x,y) and return its alpha component, normalized to [0..1].
+        This is roughly equivalent to SkGetColorA(getColor()), but can be more efficent
+        (and more precise if the pixels store more than 8 bits per component).
+
+        @param x  column index, zero or greater, and less than width()
+        @param y  row index, zero or greater, and less than height()
+        @return   alpha converted to normalized float
+     */
+    float getAlphaf(int x, int y) const {
+        return this->pixmap().getAlphaf(x, y);
+    }
+
     /** Returns pixel address at (x, y).
 
         Input is not validated: out of bounds values of x or y, or kUnknown_SkColorType,
diff --git a/include/core/SkPixmap.h b/include/core/SkPixmap.h
index c98b5cf..b5f0538 100644
--- a/include/core/SkPixmap.h
+++ b/include/core/SkPixmap.h
@@ -259,6 +259,16 @@
     */
     SkColor getColor(int x, int y) const;
 
+    /** Look up the pixel at (x,y) and return its alpha component, normalized to [0..1].
+        This is roughly equivalent to SkGetColorA(getColor()), but can be more efficent
+        (and more precise if the pixels store more than 8 bits per component).
+
+        @param x  column index, zero or greater, and less than width()
+        @param y  row index, zero or greater, and less than height()
+        @return   alpha converted to normalized float
+     */
+    float getAlphaf(int x, int y) const;
+
     /** Returns readable pixel address at (x, y). Returns nullptr if SkPixelRef is nullptr.
 
         Input is not validated: out of bounds values of x or y trigger an assert() if
diff --git a/src/core/SkPixmap.cpp b/src/core/SkPixmap.cpp
index 4a55504..44792fb 100644
--- a/src/core/SkPixmap.cpp
+++ b/src/core/SkPixmap.cpp
@@ -79,6 +79,57 @@
     return true;
 }
 
+// This is the same as SkPixmap::addr(x,y), but this version gets inlined, while the public
+// method does not. Perhaps we could bloat it so it can be inlined, but that would grow code-size
+// everywhere, instead of just here (on behalf of getAlphaf()).
+static const void* fast_getaddr(const SkPixmap& pm, int x, int y) {
+    x <<= SkColorTypeShiftPerPixel(pm.colorType());
+    return static_cast<const char*>(pm.addr()) + y * pm.rowBytes() + x;
+}
+
+float SkPixmap::getAlphaf(int x, int y) const {
+    SkASSERT(this->addr());
+    SkASSERT((unsigned)x < (unsigned)this->width());
+    SkASSERT((unsigned)y < (unsigned)this->height());
+
+    float value = 0;
+    const void* srcPtr = fast_getaddr(*this, x, y);
+
+    switch (this->colorType()) {
+        case kUnknown_SkColorType:
+            return 0;
+        case kGray_8_SkColorType:
+        case kRGB_565_SkColorType:
+        case kRGB_888x_SkColorType:
+        case kRGB_101010x_SkColorType:
+            return 1;
+        case kAlpha_8_SkColorType:
+            value = static_cast<const uint8_t*>(srcPtr)[0] * (1.0f/255);
+            break;
+        case kARGB_4444_SkColorType: {
+            uint16_t u16 = static_cast<const uint16_t*>(srcPtr)[0];
+            value = SkGetPackedA4444(u16) * (1.0f/15);
+        } break;
+        case kRGBA_8888_SkColorType:
+        case kBGRA_8888_SkColorType:
+            value = static_cast<const uint8_t*>(srcPtr)[3] * (1.0f/255);
+            break;
+        case kRGBA_1010102_SkColorType: {
+            uint32_t u32 = static_cast<const uint32_t*>(srcPtr)[0];
+            value = (u32 >> 30) * (1.0f/3);
+        } break;
+        case kRGBA_F16_SkColorType: {
+            uint64_t px;
+            memcpy(&px, srcPtr, sizeof(px));
+            value = SkHalfToFloat_finite_ftz(px)[3];
+        } break;
+        case kRGBA_F32_SkColorType:
+            value = static_cast<const float*>(srcPtr)[3];
+            break;
+    }
+    return value;
+}
+
 bool SkPixmap::readPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRB,
                           int x, int y) const {
     if (!SkImageInfoValidConversion(dstInfo, fInfo)) {
diff --git a/tests/BitmapTest.cpp b/tests/BitmapTest.cpp
index 439136d..70dde59 100644
--- a/tests/BitmapTest.cpp
+++ b/tests/BitmapTest.cpp
@@ -263,3 +263,103 @@
         REPORTER_ASSERT(r, bm.getColor(0,0) != 0x00000000);
     }
 }
+
+static void check_alphas(skiatest::Reporter* reporter, const SkBitmap& bm,
+                         bool (*pred)(float expected, float actual)) {
+    SkASSERT(bm.width() == 16);
+    SkASSERT(bm.height() == 16);
+
+    int alpha = 0;
+    for (int y = 0; y < 16; ++y) {
+        for (int x = 0; x < 16; ++x) {
+            float expected = alpha / 255.0f;
+            float actual = bm.getAlphaf(x, y);
+            if (!pred(expected, actual)) {
+                ERRORF(reporter, "got %g, want %g\n", actual, expected);
+            }
+            alpha += 1;
+        }
+    }
+}
+
+static bool unit_compare(float expected, float actual, float tol = 1.0f/(1<<12)) {
+    SkASSERT(expected >= 0 && expected <= 1);
+    SkASSERT(  actual >= 0 &&   actual <= 1);
+    if (expected == 0 || expected == 1) {
+        return actual == expected;
+    } else {
+        return SkScalarNearlyEqual(expected, actual, tol);
+    }
+}
+
+static float unit_discretize(float value, float scale) {
+    SkASSERT(value >= 0 && value <= 1);
+    if (value == 1) {
+        return 1;
+    } else {
+        return sk_float_floor(value * scale + 0.5f) / scale;
+    }
+}
+
+DEF_TEST(getalphaf, reporter) {
+    SkImageInfo info = SkImageInfo::MakeN32Premul(16, 16);
+    SkBitmap bm;
+    bm.allocPixels(info);
+
+    int alpha = 0;
+    for (int y = 0; y < 16; ++y) {
+        for (int x = 0; x < 16; ++x) {
+            *bm.getAddr32(x, y) = alpha++ << 24;
+        }
+    }
+
+    auto nearly = [](float expected, float actual) -> bool {
+        return unit_compare(expected, actual);
+    };
+    auto nearly4bit = [](float expected, float actual) -> bool {
+        expected = unit_discretize(expected, 15);
+        return unit_compare(expected, actual);
+    };
+    auto nearly2bit = [](float expected, float actual) -> bool {
+        expected = unit_discretize(expected, 3);
+        return unit_compare(expected, actual);
+    };
+    auto opaque = [](float expected, float actual) -> bool {
+        return actual == 1.0f;
+    };
+
+    auto nearly_half = [](float expected, float actual) -> bool {
+        return unit_compare(expected, actual, 1.0f/(1<<10));
+    };
+
+    const struct {
+        SkColorType fColorType;
+        bool (*fPred)(float, float);
+    } recs[] = {
+        { kRGB_565_SkColorType,     opaque },
+        { kGray_8_SkColorType,      opaque },
+        { kRGB_888x_SkColorType,    opaque },
+        { kRGB_101010x_SkColorType, opaque },
+
+        { kAlpha_8_SkColorType,     nearly },
+        { kRGBA_8888_SkColorType,   nearly },
+        { kBGRA_8888_SkColorType,   nearly },
+        { kRGBA_F16_SkColorType,    nearly_half },
+        { kRGBA_F32_SkColorType,    nearly },
+
+        { kRGBA_1010102_SkColorType, nearly2bit },
+
+        { kARGB_4444_SkColorType,   nearly4bit },
+    };
+
+    for (const auto& rec : recs) {
+        SkBitmap tmp;
+        tmp.allocPixels(bm.info().makeColorType(rec.fColorType));
+        if (bm.readPixels(tmp.pixmap())) {
+            check_alphas(reporter, tmp, rec.fPred);
+        } else {
+            SkDebugf("can't readpixels\n");
+        }
+    }
+}
+