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:
parent
474d687919
commit
c38cce63b3
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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;
|
||||
|
131
src/core/SkColorSpace_New.cpp
Normal file
131
src/core/SkColorSpace_New.cpp
Normal 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;
|
||||
}
|
78
src/core/SkColorSpace_New.h
Normal file
78
src/core/SkColorSpace_New.h
Normal 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
|
@ -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) \
|
||||
|
@ -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
@ -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,
|
||||
|
93
tests/SkColorSpace_NewTest.cpp
Normal file
93
tests/SkColorSpace_NewTest.cpp
Normal 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]));
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user