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");
+ }
+ }
+}
+