make SkColorSpace_New real

Some interesting things are starting to fall out already,
like the fact that I needed to add a gamma_dst stage to
be able to draw into gamma-transfer-fn destinations.

I've also had to pass an SkAlphaType through to the linearize
functions so that they can maintain premul invariants.  I'm not
sure this is actually a good idea... if you can, please double-
check my logic at SkRasterPipeline.cpp:128?

If it's correct logic, I'm going to need to do it all over the place.
But I imagine you don't do this and somehow get away with it.

Change-Id: I42cd9b161b54287d674225103ad9e19f8b388959
Reviewed-on: https://skia-review.googlesource.com/84680
Commit-Queue: Mike Klein <mtklein@chromium.org>
Reviewed-by: Brian Osman <brianosman@google.com>
This commit is contained in:
Mike Klein 2017-12-13 11:01:31 -05:00 committed by Skia Commit-Bot
parent 474d687919
commit c38cce63b3
11 changed files with 10826 additions and 9318 deletions

View File

@ -75,6 +75,8 @@ skia_core_sources = [
"$_src/core/SkColorSpace.cpp",
"$_src/core/SkColorSpace_A2B.cpp",
"$_src/core/SkColorSpace_A2B.h",
"$_src/core/SkColorSpace_New.cpp",
"$_src/core/SkColorSpace_New.h",
"$_src/core/SkColorSpace_XYZ.cpp",
"$_src/core/SkColorSpace_XYZ.h",
"$_src/core/SkColorSpace_ICC.cpp",

View File

@ -213,6 +213,7 @@ tests_sources = [
"$_tests/skbug6389.cpp",
"$_tests/skbug6653.cpp",
"$_tests/SkColor4fTest.cpp",
"$_tests/SkColorSpace_NewTest.cpp",
"$_tests/SkDOMTest.cpp",
"$_tests/SkFixed15Test.cpp",
"$_tests/SkGaussFilterTest.cpp",

View File

@ -79,7 +79,6 @@ struct SK_API SkColorSpaceTransferFn {
class SK_API SkColorSpace : public SkRefCnt {
public:
/**
* Create the sRGB color space.
*/
@ -241,9 +240,6 @@ public:
static bool Equals(const SkColorSpace* src, const SkColorSpace* dst);
private:
SkColorSpace() = default;
friend class SkColorSpace_Base;
virtual const SkMatrix44* onToXYZD50() const = 0;
virtual uint32_t onToXYZD50Hash() const = 0;
virtual const SkMatrix44* onFromXYZD50() const = 0;

View File

@ -0,0 +1,131 @@
/*
* Copyright 2017 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "SkColorSpace_New.h"
#include "SkOpts.h"
#include "SkRasterPipeline.h"
// ~~~~~~~~~~~~~~~~~~~~~~~ SkColorSpace_New::TransferFn ~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
namespace {
struct LinearTransferFn : public SkColorSpace_New::TransferFn {
SkColorSpaceTransferFn parameterize() const override {
return { 1,1, 0,0,0,0,0 };
}
void linearizeDst(SkRasterPipeline*, SkAlphaType) const override {}
void linearizeSrc(SkRasterPipeline*, SkAlphaType) const override {}
void encodeSrc(SkRasterPipeline* ) const override {}
};
struct SRGBTransferFn : public SkColorSpace_New::TransferFn {
SkColorSpaceTransferFn parameterize() const override {
return { 2.4f, 1/1.055f, 0.055f/1.055f, 1/12.92f, 0.04045f, 0, 0 };
}
void linearizeDst(SkRasterPipeline* p, SkAlphaType at) const override {
p->append_from_srgb_dst(at);
}
void linearizeSrc(SkRasterPipeline* p, SkAlphaType at) const override {
p->append_from_srgb(at);
}
void encodeSrc(SkRasterPipeline* p) const override {
p->append(SkRasterPipeline::to_srgb);
}
};
struct GammaTransferFn : public SkColorSpace_New::TransferFn {
float fGamma;
float fInv;
explicit GammaTransferFn(float gamma) : fGamma(gamma), fInv(1.0f/gamma) {}
SkColorSpaceTransferFn parameterize() const override {
return { fGamma, 1, 0,0,0,0,0 };
}
void linearizeDst(SkRasterPipeline* p, SkAlphaType) const override {
p->append(SkRasterPipeline::gamma_dst, &fGamma);
// TODO: use SkAlphaType to clamp like SRGBTransferFn does?
}
void linearizeSrc(SkRasterPipeline* p, SkAlphaType) const override {
p->append(SkRasterPipeline::gamma, &fGamma);
// TODO: use SkAlphaType to clamp like SRGBTransferFn does?
}
void encodeSrc(SkRasterPipeline* p) const override {
p->append(SkRasterPipeline::gamma, &fInv);
}
};
}
sk_sp<SkColorSpace_New::TransferFn> SkColorSpace_New::TransferFn::MakeLinear() {
return sk_make_sp<LinearTransferFn>();
}
sk_sp<SkColorSpace_New::TransferFn> SkColorSpace_New::TransferFn::MakeSRGB() {
return sk_make_sp<SRGBTransferFn>();
}
sk_sp<SkColorSpace_New::TransferFn> SkColorSpace_New::TransferFn::MakeGamma(float gamma) {
if (gamma == 1) {
return MakeLinear();
}
return sk_make_sp<GammaTransferFn>(gamma);
}
bool SkColorSpace_New::TransferFn::equals(const SkColorSpace_New::TransferFn& other) const {
SkColorSpaceTransferFn a = this->parameterize(),
b = other.parameterize();
return 0 == memcmp(&a,&b, sizeof(SkColorSpaceTransferFn));
}
void SkColorSpace_New::TransferFn::updateICCProfile(ICCProfile*) const {
// TODO
}
// ~~~~~~~~~~~~~~~~~~~~~~~ SkColorSpace_New ~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
SkColorSpace_New::SkColorSpace_New(sk_sp<TransferFn> transferFn,
SkMatrix44 toXYZD50,
Blending blending)
: fTransferFn(std::move(transferFn))
, fToXYZD50(toXYZD50)
, fToXYZD50Hash(SkOpts::hash_fn(&toXYZD50, 16*sizeof(SkMScalar), 0))
, fBlending(blending)
{
// It's pretty subtle what do to if the to-XYZ matrix is not invertible.
// That means the same point in XYZ is mapped to from more than one point in RGB,
// or put another way, we threw information away when mapping RGB -> XYZ.
//
// We'd probably like to set fToXYZD50 as one of the family of matrices that
// will correctly roundtrip XYZ -> RGB -> XYZ. Choosing which is an open problem.
SkAssertResult(fToXYZD50.invert(&fFromXYZD50));
}
sk_sp<SkColorSpace> SkColorSpace_New::makeLinearGamma() const {
return sk_make_sp<SkColorSpace_New>(TransferFn::MakeLinear(), fToXYZD50, fBlending);
}
sk_sp<SkColorSpace> SkColorSpace_New::makeSRGBGamma() const {
return sk_make_sp<SkColorSpace_New>(TransferFn::MakeSRGB(), fToXYZD50, fBlending);
}
SkGammaNamed SkColorSpace_New::onGammaNamed() const {
return kNonStandard_SkGammaNamed; // TODO
}
bool SkColorSpace_New::onGammaCloseToSRGB() const {
return fTransferFn->equals(*TransferFn::MakeSRGB()); // TODO: more efficient?
}
bool SkColorSpace_New::onGammaIsLinear() const {
return fTransferFn->equals(*TransferFn::MakeLinear()); // TODO: more efficient?
}
bool SkColorSpace_New::onIsNumericalTransferFn(SkColorSpaceTransferFn* fn) const {
*fn = fTransferFn->parameterize();
return true;
}

View File

@ -0,0 +1,78 @@
/*
* Copyright 2017 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef SkColorSpace_New_DEFINED
#define SkColorSpace_New_DEFINED
#include "SkColorSpace.h"
#include "SkImageInfo.h"
#include "SkRefCnt.h"
class SkRasterPipeline;
class SkColorSpace_New final : public SkColorSpace {
public:
class ICCProfile; // TODO: == SkICC?
struct TransferFn : public SkRefCnt {
virtual ~TransferFn() = default;
// TODO: one day maybe we'd like to not need this call,
// instead using the more active methods below instead.
virtual SkColorSpaceTransferFn parameterize() const = 0;
// Append stages to use this transfer function with SkRasterPipeline-based rendering.
virtual void linearizeDst(SkRasterPipeline*, SkAlphaType) const = 0;
virtual void linearizeSrc(SkRasterPipeline*, SkAlphaType) const = 0;
virtual void encodeSrc(SkRasterPipeline* ) const = 0;
// TODO: Ganesh hooks.
// May return false even when this is equivalent to TransferFn,
// but must always be equivalent when this returns true. (No false positives.)
// Implemented by default with parameterize().
virtual bool equals(const TransferFn&) const;
// TODO: ???
// Implemented by default with parameterize().
virtual void updateICCProfile(ICCProfile*) const;
static sk_sp<TransferFn> MakeLinear();
static sk_sp<TransferFn> MakeSRGB();
static sk_sp<TransferFn> MakeGamma(float);
};
enum class Blending { Linear, AsEncoded };
SkColorSpace_New(sk_sp<TransferFn>, SkMatrix44 toXYZD50, Blending);
const SkMatrix44& toXYZD50() const { return fToXYZD50; }
const SkMatrix44& fromXYZD50() const { return fFromXYZD50; }
const TransferFn& transferFn() const { return *fTransferFn; }
Blending blending() const { return fBlending; }
// Transfer-function-related overrides.
sk_sp<SkColorSpace> makeLinearGamma() const override;
sk_sp<SkColorSpace> makeSRGBGamma() const override;
SkGammaNamed onGammaNamed() const override;
bool onGammaCloseToSRGB() const override;
bool onGammaIsLinear() const override;
bool onIsNumericalTransferFn(SkColorSpaceTransferFn*) const override;
// Gamut-related overrides.
const SkMatrix44* onFromXYZD50() const override { return &fFromXYZD50; }
const SkMatrix44* onToXYZD50() const override { return &fToXYZD50; }
uint32_t onToXYZD50Hash() const override { return fToXYZD50Hash; }
private:
sk_sp<TransferFn> fTransferFn;
SkMatrix44 fFromXYZD50;
SkMatrix44 fToXYZD50;
uint32_t fToXYZD50Hash;
Blending fBlending;
};
#endif//SkColorSpace_New_DEFINED

View File

@ -70,7 +70,7 @@ struct SkJumper_Engine;
M(matrix_2x3) M(matrix_3x4) M(matrix_4x5) M(matrix_4x3) \
M(matrix_perspective) \
M(parametric_r) M(parametric_g) M(parametric_b) \
M(parametric_a) M(gamma) \
M(parametric_a) M(gamma) M(gamma_dst) \
M(table_r) M(table_g) M(table_b) M(table_a) \
M(lab_to_xyz) \
M(mirror_x) M(repeat_x) \

View File

@ -236,7 +236,7 @@ extern "C" {
LOWP(matrix_2x3) NOPE(matrix_3x4) TODO(matrix_4x5) TODO(matrix_4x3)
LOWP(matrix_perspective)
NOPE(parametric_r) NOPE(parametric_g) NOPE(parametric_b)
NOPE(parametric_a) NOPE(gamma)
NOPE(parametric_a) NOPE(gamma) NOPE(gamma_dst)
NOPE(table_r) NOPE(table_g) NOPE(table_b) NOPE(table_a)
NOPE(lab_to_xyz)
TODO(mirror_x) TODO(repeat_x)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -835,6 +835,11 @@ STAGE(gamma, const float* G) {
g = approx_powf(g, *G);
b = approx_powf(b, *G);
}
STAGE(gamma_dst, const float* G) {
dr = approx_powf(dr, *G);
dg = approx_powf(dg, *G);
db = approx_powf(db, *G);
}
STAGE(lab_to_xyz, Ctx::None) {
F L = r * 100.0f,

View File

@ -0,0 +1,93 @@
/*
* Copyright 2017 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "../src/jumper/SkJumper.h"
#include "SkColorSpace_New.h"
#include "SkRasterPipeline.h"
#include "Test.h"
#include <initializer_list>
DEF_TEST(SkColorSpace_New_TransferFnBasics, r) {
auto gamut = SkMatrix44::I();
auto blending = SkColorSpace_New::Blending::AsEncoded;
SkColorSpace_New linearA{SkColorSpace_New::TransferFn::MakeLinear(), gamut, blending},
linearB{SkColorSpace_New::TransferFn::MakeGamma(1), gamut, blending},
srgb{SkColorSpace_New::TransferFn::MakeSRGB(), gamut, blending},
gamma{SkColorSpace_New::TransferFn::MakeGamma(2.2f), gamut, blending};
REPORTER_ASSERT(r, linearA.gammaIsLinear());
REPORTER_ASSERT(r, linearB.gammaIsLinear());
REPORTER_ASSERT(r, ! srgb.gammaIsLinear());
REPORTER_ASSERT(r, ! gamma.gammaIsLinear());
REPORTER_ASSERT(r, !linearA.gammaCloseToSRGB());
REPORTER_ASSERT(r, !linearB.gammaCloseToSRGB());
REPORTER_ASSERT(r, srgb.gammaCloseToSRGB());
REPORTER_ASSERT(r, ! gamma.gammaCloseToSRGB());
REPORTER_ASSERT(r, linearA.transferFn().equals(linearB.transferFn()));
REPORTER_ASSERT(r, !linearA.transferFn().equals( srgb.transferFn()));
REPORTER_ASSERT(r, !linearA.transferFn().equals( gamma.transferFn()));
REPORTER_ASSERT(r, !linearB.transferFn().equals( srgb.transferFn()));
REPORTER_ASSERT(r, !linearB.transferFn().equals( gamma.transferFn()));
REPORTER_ASSERT(r, ! srgb.transferFn().equals( gamma.transferFn()));
}
DEF_TEST(SkColorSpace_New_TransferFnStages, r) {
// We'll create a little SkRasterPipelineBlitter-like scenario,
// blending the same src color over the same dst color, but with
// three different transfer functions, for simplicity the same for src and dst.
SkColor src = 0x7f7f0000;
SkColor dsts[3];
for (SkColor& dst : dsts) {
dst = 0xff007f00;
}
auto gamut = SkMatrix44::I();
auto blending = SkColorSpace_New::Blending::Linear;
SkColorSpace_New linear{SkColorSpace_New::TransferFn::MakeLinear(), gamut, blending},
srgb{SkColorSpace_New::TransferFn::MakeSRGB(), gamut, blending},
gamma{SkColorSpace_New::TransferFn::MakeGamma(3), gamut, blending};
SkColor* dst = dsts;
for (const SkColorSpace_New* cs : {&linear, &srgb, &gamma}) {
SkJumper_MemoryCtx src_ctx = { &src, 0 },
dst_ctx = { dst++, 0 };
SkRasterPipeline_<256> p;
p.append(SkRasterPipeline::load_8888, &src_ctx);
cs->transferFn().linearizeSrc(&p, kUnpremul_SkAlphaType);
p.append(SkRasterPipeline::premul);
p.append(SkRasterPipeline::load_8888_dst, &dst_ctx);
cs->transferFn().linearizeDst(&p, kUnpremul_SkAlphaType);
p.append(SkRasterPipeline::premul_dst);
p.append(SkRasterPipeline::srcover);
p.append(SkRasterPipeline::unpremul);
cs->transferFn().encodeSrc(&p);
p.append(SkRasterPipeline::store_8888, &dst_ctx);
p.run(0,0,1,1);
}
// Double check the uninteresting channels: alpha's opaque, no blue.
REPORTER_ASSERT(r, SkColorGetA(dsts[0]) == 0xff && SkColorGetB(dsts[0]) == 0x00);
REPORTER_ASSERT(r, SkColorGetA(dsts[1]) == 0xff && SkColorGetB(dsts[1]) == 0x00);
REPORTER_ASSERT(r, SkColorGetA(dsts[2]) == 0xff && SkColorGetB(dsts[2]) == 0x00);
// Because we're doing linear blending, a more-exponential transfer function will
// brighten the encoded values more when linearizing. So we expect to see that
// linear is darker than sRGB, and sRGB in turn is darker than gamma 3.
REPORTER_ASSERT(r, SkColorGetR(dsts[0]) < SkColorGetR(dsts[1]));
REPORTER_ASSERT(r, SkColorGetR(dsts[1]) < SkColorGetR(dsts[2]));
REPORTER_ASSERT(r, SkColorGetG(dsts[0]) < SkColorGetG(dsts[1]));
REPORTER_ASSERT(r, SkColorGetG(dsts[1]) < SkColorGetG(dsts[2]));
}