experiment: float color components

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

Review URL: https://codereview.chromium.org/1617813002
This commit is contained in:
reed 2016-01-21 15:29:10 -08:00 committed by Commit bot
parent 07caf56e72
commit 353c148d8e
12 changed files with 217 additions and 19 deletions

View File

@ -160,4 +160,44 @@ SK_API SkPMColor SkPreMultiplyColor(SkColor c);
*/ */
typedef SkPMColor (*SkXfermodeProc)(SkPMColor src, SkPMColor dst); typedef SkPMColor (*SkXfermodeProc)(SkPMColor src, SkPMColor dst);
///////////////////////////////////////////////////////////////////////////////////////////////////
/*
* The float values are 0...1 premultiplied
*/
struct SK_ATTRIBUTE(aligned(16)) SkPM4f {
float fVec[4];
float a() const { return fVec[SK_A32_SHIFT/8]; }
};
/*
* The float values are 0...1 unpremultiplied
*/
struct SkColor4f {
float fA;
float fR;
float fG;
float fB;
bool operator==(const SkColor4f& other) const {
return fA == other.fA && fR == other.fR && fG == other.fG && fB == other.fB;
}
bool operator!=(const SkColor4f& other) const {
return !(*this == other);
}
const float* vec() const { return &fA; }
float* vec() { return &fA; }
static SkColor4f Pin(float a, float r, float g, float b);
static SkColor4f FromColor(SkColor);
SkColor4f pin() const {
return Pin(fA, fR, fG, fB);
}
SkPM4f premul() const;
};
#endif #endif

View File

@ -68,10 +68,13 @@ public:
*/ */
virtual void filterSpan(const SkPMColor src[], int count, SkPMColor result[]) const = 0; virtual void filterSpan(const SkPMColor src[], int count, SkPMColor result[]) const = 0;
virtual void filterSpan4f(const SkPM4f src[], int count, SkPM4f result[]) const;
enum Flags { enum Flags {
/** If set the filter methods will not change the alpha channel of the colors. /** If set the filter methods will not change the alpha channel of the colors.
*/ */
kAlphaUnchanged_Flag = 0x01, kAlphaUnchanged_Flag = 1 << 0,
kSupports4f_Flag = 1 << 1,
}; };
/** Returns the flags for this filter. Override in subclasses to return custom flags. /** Returns the flags for this filter. Override in subclasses to return custom flags.

View File

@ -71,6 +71,11 @@
SK_B32_SHIFT == SK_BGRA_B32_SHIFT) SK_B32_SHIFT == SK_BGRA_B32_SHIFT)
#define SK_A_INDEX (SK_A32_SHIFT/8)
#define SK_R_INDEX (SK_R32_SHIFT/8)
#define SK_G_INDEX (SK_G32_SHIFT/8)
#define SK_B_INDEX (SK_B32_SHIFT/8)
#if defined(SK_PMCOLOR_IS_RGBA) && !LOCAL_PMCOLOR_SHIFTS_EQUIVALENT_TO_RGBA #if defined(SK_PMCOLOR_IS_RGBA) && !LOCAL_PMCOLOR_SHIFTS_EQUIVALENT_TO_RGBA
#error "SK_PMCOLOR_IS_RGBA does not match SK_*32_SHIFT values" #error "SK_PMCOLOR_IS_RGBA does not match SK_*32_SHIFT values"
#endif #endif

View File

@ -81,6 +81,7 @@ public:
shadeSpan(). shadeSpan().
*/ */
kConstInY32_Flag = 1 << 1, kConstInY32_Flag = 1 << 1,
kSupports4f_Flag = 1 << 2,
}; };
/** /**
@ -127,6 +128,8 @@ public:
*/ */
virtual void shadeSpan(int x, int y, SkPMColor[], int count) = 0; virtual void shadeSpan(int x, int y, SkPMColor[], int count) = 0;
virtual void shadeSpan4f(int x, int y, SkPM4f[], int count);
/** /**
* The const void* ctx is only const because all the implementations are const. * The const void* ctx is only const because all the implementations are const.
* This can be changed to non-const if a new shade proc needs to change the ctx. * This can be changed to non-const if a new shade proc needs to change the ctx.

View File

@ -29,6 +29,7 @@ public:
static SkColorFilter* CreateLightingFilter(SkColor mul, SkColor add); static SkColorFilter* CreateLightingFilter(SkColor mul, SkColor add);
void filterSpan(const SkPMColor src[], int count, SkPMColor[]) const override; void filterSpan(const SkPMColor src[], int count, SkPMColor[]) const override;
void filterSpan4f(const SkPM4f src[], int count, SkPM4f[]) const override;
uint32_t getFlags() const override; uint32_t getFlags() const override;
bool asColorMatrix(SkScalar matrix[20]) const override; bool asColorMatrix(SkScalar matrix[20]) const override;
SkColorFilter* newComposed(const SkColorFilter*) const override; SkColorFilter* newComposed(const SkColorFilter*) const override;

View File

@ -100,3 +100,37 @@ SkColor SkHSVToColor(U8CPU a, const SkScalar hsv[3]) {
} }
return SkColorSetARGB(a, r, g, b); return SkColorSetARGB(a, r, g, b);
} }
///////////////////////////////////////////////////////////////////////////////////////////////////
#include "SkNx.h"
SkColor4f SkColor4f::Pin(float a, float r, float g, float b) {
SkColor4f c4;
Sk4f::Min(Sk4f::Max(Sk4f(a, r, g, b), Sk4f(0)), Sk4f(1)).store(c4.vec());
return c4;
}
SkColor4f SkColor4f::FromColor(SkColor c) {
Sk4f value = SkNx_shuffle<3,2,1,0>(SkNx_cast<float>(Sk4b::Load((const uint8_t*)&c)));
SkColor4f c4;
(value * Sk4f(1.0f / 255)).store(c4.vec());
return c4;
}
SkPM4f SkColor4f::premul() const {
auto src = Sk4f::Load(this->pin().vec());
float srcAlpha = src.kth<0>(); // need the pinned version of our alpha
src = src * Sk4f(1, srcAlpha, srcAlpha, srcAlpha);
#ifdef SK_PMCOLOR_IS_BGRA
// ARGB -> BGRA
Sk4f dst = SkNx_shuffle<3,2,1,0>(src);
#else
// ARGB -> RGBA
Sk4f dst = SkNx_shuffle<1,2,3,0>(src);
#endif
SkPM4f pm4;
dst.store(pm4.fVec);
return pm4;
}

View File

@ -35,6 +35,10 @@ SkColor SkColorFilter::filterColor(SkColor c) const {
return SkUnPreMultiply::PMColorToColor(dst); return SkUnPreMultiply::PMColorToColor(dst);
} }
void SkColorFilter::filterSpan4f(const SkPM4f[], int count, SkPM4f[]) const {
SkASSERT(false && "filterSpan4f called but not implemented");
}
/////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////
/* /*

View File

@ -36,8 +36,10 @@ public:
uint32_t getFlags() const override; uint32_t getFlags() const override;
void shadeSpan(int x, int y, SkPMColor span[], int count) override; void shadeSpan(int x, int y, SkPMColor span[], int count) override;
void shadeSpanAlpha(int x, int y, uint8_t alpha[], int count) override; void shadeSpanAlpha(int x, int y, uint8_t alpha[], int count) override;
void shadeSpan4f(int x, int y, SkPM4f[], int count) override;
private: private:
SkPM4f fPM4f;
SkPMColor fPMColor; SkPMColor fPMColor;
uint32_t fFlags; uint32_t fFlags;

View File

@ -78,4 +78,8 @@ static inline unsigned SkDiv255Round(unsigned prod) {
return (prod + (prod >> 8)) >> 8; return (prod + (prod >> 8)) >> 8;
} }
static inline float SkPinToUnitFloat(float x) {
return SkTMin(SkTMax(x, 0.0f), 1.0f);
}
#endif #endif

View File

@ -117,6 +117,10 @@ SkShader::Context::ShadeProc SkShader::Context::asAShadeProc(void** ctx) {
return nullptr; return nullptr;
} }
void SkShader::Context::shadeSpan4f(int x, int y, SkPM4f[], int count) {
SkASSERT(false && "shadeSpan4f called but not implemented");
}
#include "SkColorPriv.h" #include "SkColorPriv.h"
#define kTempColorQuadCount 6 // balance between speed (larger) and saving stack-space #define kTempColorQuadCount 6 // balance between speed (larger) and saving stack-space
@ -279,7 +283,11 @@ SkColorShader::ColorShaderContext::ColorShaderContext(const SkColorShader& shade
} }
fPMColor = SkPackARGB32(a, r, g, b); fPMColor = SkPackARGB32(a, r, g, b);
fFlags = kConstInY32_Flag; SkColor4f c4 = SkColor4f::FromColor(shader.fColor);
c4.fA *= rec.fPaint->getAlpha() / 255.0f;
fPM4f = c4.premul();
fFlags = kConstInY32_Flag | kSupports4f_Flag;
if (255 == a) { if (255 == a) {
fFlags |= kOpaqueAlpha_Flag; fFlags |= kOpaqueAlpha_Flag;
} }
@ -293,6 +301,12 @@ void SkColorShader::ColorShaderContext::shadeSpanAlpha(int x, int y, uint8_t alp
memset(alpha, SkGetPackedA32(fPMColor), count); memset(alpha, SkGetPackedA32(fPMColor), count);
} }
void SkColorShader::ColorShaderContext::shadeSpan4f(int x, int y, SkPM4f span[], int count) {
for (int i = 0; i < count; ++i) {
span[i] = fPM4f;
}
}
SkShader::GradientType SkColorShader::asAGradient(GradientInfo* info) const { SkShader::GradientType SkColorShader::asAGradient(GradientInfo* info) const {
if (info) { if (info) {
if (info->fColors && info->fColorCount >= 1) { if (info->fColors && info->fColorCount >= 1) {

View File

@ -45,10 +45,11 @@ void SkColorMatrixFilter::initState(const SkScalar* SK_RESTRICT src) {
bool usesAlpha = (array[3] || array[8] || array[13]); bool usesAlpha = (array[3] || array[8] || array[13]);
if (changesAlpha || usesAlpha) { if (changesAlpha || usesAlpha) {
fFlags = changesAlpha ? 0 : SkColorFilter::kAlphaUnchanged_Flag; fFlags = changesAlpha ? 0 : kAlphaUnchanged_Flag;
} else { } else {
fFlags = SkColorFilter::kAlphaUnchanged_Flag; fFlags = kAlphaUnchanged_Flag;
} }
fFlags |= kSupports4f_Flag;
} }
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
@ -89,28 +90,28 @@ static SkPMColor round(const Sk4f& x) {
return c; return c;
} }
void SkColorMatrixFilter::filterSpan(const SkPMColor src[], int count, SkPMColor dst[]) const { template <typename Adaptor, typename T>
void filter_span(const float array[], const T src[], int count, T dst[]) {
// c0-c3 are already in [0,1]. // c0-c3 are already in [0,1].
const Sk4f c0 = Sk4f::Load(fTranspose + 0); const Sk4f c0 = Sk4f::Load(array + 0);
const Sk4f c1 = Sk4f::Load(fTranspose + 4); const Sk4f c1 = Sk4f::Load(array + 4);
const Sk4f c2 = Sk4f::Load(fTranspose + 8); const Sk4f c2 = Sk4f::Load(array + 8);
const Sk4f c3 = Sk4f::Load(fTranspose + 12); const Sk4f c3 = Sk4f::Load(array + 12);
// c4 (the translate vector) is in [0, 255]. Bring it back to [0,1]. // c4 (the translate vector) is in [0, 255]. Bring it back to [0,1].
const Sk4f c4 = Sk4f::Load(fTranspose + 16)*Sk4f(1.0f/255); const Sk4f c4 = Sk4f::Load(array + 16)*Sk4f(1.0f/255);
// todo: we could cache this in the constructor... // todo: we could cache this in the constructor...
SkPMColor matrix_translate_pmcolor = round(premul(clamp_0_1(c4))); T matrix_translate_pmcolor = Adaptor::From4f(premul(clamp_0_1(c4)));
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
const SkPMColor src_c = src[i]; Sk4f srcf = Adaptor::To4f(src[i]);
if (0 == src_c) { float srcA = srcf.kth<SK_A32_SHIFT/8>();
if (0 == srcA) {
dst[i] = matrix_translate_pmcolor; dst[i] = matrix_translate_pmcolor;
continue; continue;
} }
if (1 == srcA) {
Sk4f srcf = SkNx_cast<float>(Sk4b::Load((const uint8_t*)&src_c)) * Sk4f(1.0f/255);
if (0xFF != SkGetPackedA32(src_c)) {
srcf = unpremul(srcf); srcf = unpremul(srcf);
} }
@ -122,11 +123,36 @@ void SkColorMatrixFilter::filterSpan(const SkPMColor src[], int count, SkPMColor
// apply matrix // apply matrix
Sk4f dst4 = c0 * r4 + c1 * g4 + c2 * b4 + c3 * a4 + c4; Sk4f dst4 = c0 * r4 + c1 * g4 + c2 * b4 + c3 * a4 + c4;
// clamp, re-premul, and write dst[i] = Adaptor::From4f(premul(clamp_0_1(dst4)));
dst[i] = round(premul(clamp_0_1(dst4)));
} }
} }
struct SkPMColorAdaptor {
static SkPMColor From4f(const Sk4f& c4) {
return round(c4);
}
static Sk4f To4f(SkPMColor c) {
return SkNx_cast<float>(Sk4b::Load((const uint8_t*)&c)) * Sk4f(1.0f/255);
}
};
void SkColorMatrixFilter::filterSpan(const SkPMColor src[], int count, SkPMColor dst[]) const {
filter_span<SkPMColorAdaptor>(fTranspose, src, count, dst);
}
struct SkPM4fAdaptor {
static SkPM4f From4f(const Sk4f& c4) {
SkPM4f c;
c4.store(c.fVec);
return c;
}
static Sk4f To4f(const SkPM4f& c) {
return Sk4f::Load(c.fVec);
}
};
void SkColorMatrixFilter::filterSpan4f(const SkPM4f src[], int count, SkPM4f dst[]) const {
filter_span<SkPM4fAdaptor>(fTranspose, src, count, dst);
}
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
void SkColorMatrixFilter::flatten(SkWriteBuffer& buffer) const { void SkColorMatrixFilter::flatten(SkWriteBuffer& buffer) const {

62
tests/SkColor4fTest.cpp Normal file
View File

@ -0,0 +1,62 @@
/*
* Copyright 2016 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "SkColor.h"
#include "SkShader.h"
#include "SkColorMatrixFilter.h"
#include "Test.h"
#include "SkRandom.h"
DEF_TEST(SkColor4f_FromColor, reporter) {
const struct {
SkColor fC;
SkColor4f fC4;
} recs[] = {
{ SK_ColorBLACK, { 1, 0, 0, 0 } },
{ SK_ColorWHITE, { 1, 1, 1, 1 } },
{ SK_ColorRED, { 1, 1, 0, 0 } },
{ SK_ColorGREEN, { 1, 0, 1, 0 } },
{ SK_ColorBLUE, { 1, 0, 0, 1 } },
{ 0, { 0, 0, 0, 0 } },
{ 0x55AAFF00, { 1/3.0f, 2/3.0f, 1, 0 } },
};
for (const auto& r : recs) {
SkColor4f c4 = SkColor4f::FromColor(r.fC);
REPORTER_ASSERT(reporter, c4 == r.fC4);
}
}
static bool nearly_equal(float a, float b) {
const float kTolerance = 1.0f / (1 << 20);
return fabsf(a - b) < kTolerance;
}
DEF_TEST(SkColor4f_premul, reporter) {
SkRandom rand;
for (int i = 0; i < 1000000; ++i) {
// First just test opaque colors, so that the premul should be exact
SkColor4f c4 {
1, rand.nextUScalar1(), rand.nextUScalar1(), rand.nextUScalar1()
};
SkPM4f pm4 = c4.premul();
REPORTER_ASSERT(reporter, pm4.fVec[SK_A_INDEX] == c4.fA);
REPORTER_ASSERT(reporter, pm4.fVec[SK_R_INDEX] == c4.fA * c4.fR);
REPORTER_ASSERT(reporter, pm4.fVec[SK_G_INDEX] == c4.fA * c4.fG);
REPORTER_ASSERT(reporter, pm4.fVec[SK_B_INDEX] == c4.fA * c4.fB);
// We compare with a tolerance, in case our premul multiply is implemented at slightly
// different precision than the test code.
c4.fA = rand.nextUScalar1();
pm4 = c4.premul();
REPORTER_ASSERT(reporter, pm4.fVec[SK_A_INDEX] == c4.fA);
REPORTER_ASSERT(reporter, nearly_equal(pm4.fVec[SK_R_INDEX], c4.fA * c4.fR));
REPORTER_ASSERT(reporter, nearly_equal(pm4.fVec[SK_G_INDEX], c4.fA * c4.fG));
REPORTER_ASSERT(reporter, nearly_equal(pm4.fVec[SK_B_INDEX], c4.fA * c4.fB));
}
}