| Index: src/effects/SkColorCubeFilter.cpp
|
| diff --git a/src/effects/SkColorCubeFilter.cpp b/src/effects/SkColorCubeFilter.cpp
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..cf3126bf39c217dd2347fa74f255c912640f0f6c
|
| --- /dev/null
|
| +++ b/src/effects/SkColorCubeFilter.cpp
|
| @@ -0,0 +1,376 @@
|
| +/*
|
| + * Copyright 2014 Google Inc.
|
| + *
|
| + * Use of this source code is governed by a BSD-style license that can be
|
| + * found in the LICENSE file.
|
| + */
|
| +
|
| +#include "SkColorCubeFilter.h"
|
| +#include "SkColorPriv.h"
|
| +#include "SkOnce.h"
|
| +#include "SkReadBuffer.h"
|
| +#include "SkUnPreMultiply.h"
|
| +#include "SkWriteBuffer.h"
|
| +#if SK_SUPPORT_GPU
|
| +#include "GrContext.h"
|
| +#include "GrCoordTransform.h"
|
| +#include "gl/GrGLProcessor.h"
|
| +#include "gl/builders/GrGLProgramBuilder.h"
|
| +#include "GrTBackendProcessorFactory.h"
|
| +#include "GrTexturePriv.h"
|
| +#include "SkGr.h"
|
| +#endif
|
| +
|
| +///////////////////////////////////////////////////////////////////////////////
|
| +namespace {
|
| +
|
| +int32_t SkNextColorProfileUniqueID() {
|
| + static int32_t gColorProfileUniqueID;
|
| + // do a loop in case our global wraps around, as we never want to return a 0
|
| + int32_t genID;
|
| + do {
|
| + genID = sk_atomic_inc(&gColorProfileUniqueID) + 1;
|
| + } while (0 == genID);
|
| + return genID;
|
| +}
|
| +
|
| +} // end namespace
|
| +
|
| +static const int MIN_CUBE_SIZE = 4;
|
| +static const int MAX_CUBE_SIZE = 64;
|
| +
|
| +static bool is_valid_3D_lut(SkData* cubeData, int cubeDimension) {
|
| + size_t minMemorySize = sizeof(uint8_t) * 4 * cubeDimension * cubeDimension * cubeDimension;
|
| + return (cubeDimension >= MIN_CUBE_SIZE) && (cubeDimension <= MAX_CUBE_SIZE) &&
|
| + (NULL != cubeData) && (cubeData->size() >= minMemorySize);
|
| +}
|
| +
|
| +SkColorFilter* SkColorCubeFilter::Create(SkData* cubeData, int cubeDimension) {
|
| + if (!is_valid_3D_lut(cubeData, cubeDimension)) {
|
| + return NULL;
|
| + }
|
| +
|
| + return SkNEW_ARGS(SkColorCubeFilter, (cubeData, cubeDimension));
|
| +}
|
| +
|
| +SkColorCubeFilter::SkColorCubeFilter(SkData* cubeData, int cubeDimension)
|
| + : fCubeData(SkRef(cubeData))
|
| + , fUniqueID(SkNextColorProfileUniqueID())
|
| + , fCache(cubeDimension) {
|
| +}
|
| +
|
| +uint32_t SkColorCubeFilter::getFlags() const {
|
| + return this->INHERITED::getFlags() | kAlphaUnchanged_Flag;
|
| +}
|
| +
|
| +SkColorCubeFilter::ColorCubeProcesingCache::ColorCubeProcesingCache(int cubeDimension)
|
| + : fCubeDimension(cubeDimension)
|
| + , fLutsInited(false) {
|
| + fColorToIndex[0] = fColorToIndex[1] = NULL;
|
| + fColorToFactors[0] = fColorToFactors[1] = NULL;
|
| + fColorToScalar = NULL;
|
| +}
|
| +
|
| +void SkColorCubeFilter::ColorCubeProcesingCache::getProcessingLuts(
|
| + const int* (*colorToIndex)[2], const SkScalar* (*colorToFactors)[2],
|
| + const SkScalar** colorToScalar) {
|
| + SkOnce(&fLutsInited, &fLutsMutex,
|
| + SkColorCubeFilter::ColorCubeProcesingCache::initProcessingLuts, this);
|
| + SkASSERT((fColorToIndex[0] != NULL) &&
|
| + (fColorToIndex[1] != NULL) &&
|
| + (fColorToFactors[0] != NULL) &&
|
| + (fColorToFactors[1] != NULL) &&
|
| + (fColorToScalar != NULL));
|
| + (*colorToIndex)[0] = fColorToIndex[0];
|
| + (*colorToIndex)[1] = fColorToIndex[1];
|
| + (*colorToFactors)[0] = fColorToFactors[0];
|
| + (*colorToFactors)[1] = fColorToFactors[1];
|
| + (*colorToScalar) = fColorToScalar;
|
| +}
|
| +
|
| +void SkColorCubeFilter::ColorCubeProcesingCache::initProcessingLuts(
|
| + SkColorCubeFilter::ColorCubeProcesingCache* cache) {
|
| + static const SkScalar inv8bit = SkScalarInvert(SkIntToScalar(255));
|
| +
|
| + // We need 256 int * 2 for fColorToIndex, so a total of 512 int.
|
| + // We need 256 SkScalar * 2 for fColorToFactors and 256 SkScalar
|
| + // for fColorToScalar, so a total of 768 SkScalar.
|
| + cache->fLutStorage.reset(512 * sizeof(int) + 768 * sizeof(SkScalar));
|
| + uint8_t* storage = (uint8_t*)cache->fLutStorage.get();
|
| + cache->fColorToIndex[0] = (int*)storage;
|
| + cache->fColorToIndex[1] = cache->fColorToIndex[0] + 256;
|
| + cache->fColorToFactors[0] = (SkScalar*)(storage + (512 * sizeof(int)));
|
| + cache->fColorToFactors[1] = cache->fColorToFactors[0] + 256;
|
| + cache->fColorToScalar = cache->fColorToFactors[1] + 256;
|
| +
|
| + SkScalar size = SkIntToScalar(cache->fCubeDimension);
|
| + SkScalar scale = (size - SK_Scalar1) * inv8bit;
|
| +
|
| + for (int i = 0; i < 256; ++i) {
|
| + SkScalar index = scale * i;
|
| + cache->fColorToIndex[0][i] = SkScalarFloorToInt(index);
|
| + cache->fColorToIndex[1][i] = cache->fColorToIndex[0][i] + 1;
|
| + cache->fColorToScalar[i] = inv8bit * i;
|
| + if (cache->fColorToIndex[1][i] < cache->fCubeDimension) {
|
| + cache->fColorToFactors[1][i] = index - SkIntToScalar(cache->fColorToIndex[0][i]);
|
| + cache->fColorToFactors[0][i] = SK_Scalar1 - cache->fColorToFactors[1][i];
|
| + } else {
|
| + cache->fColorToIndex[1][i] = cache->fColorToIndex[0][i];
|
| + cache->fColorToFactors[0][i] = SK_Scalar1;
|
| + cache->fColorToFactors[1][i] = 0;
|
| + }
|
| + }
|
| +}
|
| +
|
| +void SkColorCubeFilter::filterSpan(const SkPMColor src[], int count, SkPMColor dst[]) const {
|
| + const int* colorToIndex[2];
|
| + const SkScalar* colorToFactors[2];
|
| + const SkScalar* colorToScalar;
|
| + fCache.getProcessingLuts(&colorToIndex, &colorToFactors, &colorToScalar);
|
| +
|
| + const int dim = fCache.cubeDimension();
|
| + SkColor* colorCube = (SkColor*)fCubeData->data();
|
| + for (int i = 0; i < count; ++i) {
|
| + SkColor inputColor = SkUnPreMultiply::PMColorToColor(src[i]);
|
| + uint8_t r = SkColorGetR(inputColor);
|
| + uint8_t g = SkColorGetG(inputColor);
|
| + uint8_t b = SkColorGetB(inputColor);
|
| + uint8_t a = SkColorGetA(inputColor);
|
| + SkScalar rOut(0), gOut(0), bOut(0);
|
| + for (int x = 0; x < 2; ++x) {
|
| + for (int y = 0; y < 2; ++y) {
|
| + for (int z = 0; z < 2; ++z) {
|
| + SkColor lutColor = colorCube[colorToIndex[x][r] +
|
| + (colorToIndex[y][g] +
|
| + colorToIndex[z][b] * dim) * dim];
|
| + SkScalar factor = colorToFactors[x][r] *
|
| + colorToFactors[y][g] *
|
| + colorToFactors[z][b];
|
| + rOut += colorToScalar[SkColorGetR(lutColor)] * factor;
|
| + gOut += colorToScalar[SkColorGetG(lutColor)] * factor;
|
| + bOut += colorToScalar[SkColorGetB(lutColor)] * factor;
|
| + }
|
| + }
|
| + }
|
| + const SkScalar aOut = SkIntToScalar(a);
|
| + dst[i] = SkPackARGB32(a,
|
| + SkScalarRoundToInt(rOut * aOut),
|
| + SkScalarRoundToInt(gOut * aOut),
|
| + SkScalarRoundToInt(bOut * aOut));
|
| + }
|
| +}
|
| +
|
| +#ifdef SK_SUPPORT_LEGACY_DEEPFLATTENING
|
| +SkColorCubeFilter::SkColorCubeFilter(SkReadBuffer& buffer)
|
| + : fCache(buffer.readInt()) {
|
| + fCubeData.reset(buffer.readByteArrayAsData());
|
| + buffer.validate(is_valid_3D_lut(fCubeData, fCache.cubeDimension()));
|
| + fUniqueID = SkNextColorProfileUniqueID();
|
| +}
|
| +#endif
|
| +
|
| +SkFlattenable* SkColorCubeFilter::CreateProc(SkReadBuffer& buffer) {
|
| + int cubeDimension = buffer.readInt();
|
| + SkData* cubeData = buffer.readByteArrayAsData();
|
| + if (!buffer.validate(is_valid_3D_lut(cubeData, cubeDimension))) {
|
| + SkSafeUnref(cubeData);
|
| + return NULL;
|
| + }
|
| + return Create(cubeData, cubeDimension);
|
| +}
|
| +
|
| +void SkColorCubeFilter::flatten(SkWriteBuffer& buffer) const {
|
| + this->INHERITED::flatten(buffer);
|
| + buffer.writeInt(fCache.cubeDimension());
|
| + buffer.writeDataAsByteArray(fCubeData);
|
| +}
|
| +
|
| +#ifndef SK_IGNORE_TO_STRING
|
| +void SkColorCubeFilter::toString(SkString* str) const {
|
| + str->append("SkColorCubeFilter ");
|
| +}
|
| +#endif
|
| +
|
| +///////////////////////////////////////////////////////////////////////////////
|
| +#if SK_SUPPORT_GPU
|
| +class GrColorProfileEffect : public GrFragmentProcessor {
|
| +public:
|
| + static GrFragmentProcessor* Create(GrTexture* colorCube) {
|
| + return (NULL != colorCube) ? SkNEW_ARGS(GrColorProfileEffect, (colorCube)) : NULL;
|
| + }
|
| +
|
| + virtual ~GrColorProfileEffect();
|
| +
|
| + virtual const GrBackendFragmentProcessorFactory& getFactory() const SK_OVERRIDE;
|
| + int colorCubeSize() const { return fColorCubeAccess.getTexture()->width(); }
|
| +
|
| + static const char* Name() { return "ColorProfile"; }
|
| +
|
| + virtual void onComputeInvariantOutput(GrProcessor::InvariantOutput*) const SK_OVERRIDE;
|
| +
|
| + class GLProcessor : public GrGLFragmentProcessor {
|
| + public:
|
| + GLProcessor(const GrBackendProcessorFactory& factory, const GrProcessor&);
|
| + virtual ~GLProcessor();
|
| +
|
| + virtual void emitCode(GrGLProgramBuilder*,
|
| + const GrFragmentProcessor&,
|
| + const GrProcessorKey&,
|
| + const char* outputColor,
|
| + const char* inputColor,
|
| + const TransformedCoordsArray&,
|
| + const TextureSamplerArray&) SK_OVERRIDE;
|
| +
|
| + static inline void GenKey(const GrProcessor&, const GrGLCaps&, GrProcessorKeyBuilder*);
|
| +
|
| + virtual void setData(const GrGLProgramDataManager&, const GrProcessor&) SK_OVERRIDE;
|
| +
|
| + private:
|
| + GrGLProgramDataManager::UniformHandle fColorCubeSizeUni;
|
| + GrGLProgramDataManager::UniformHandle fColorCubeInvSizeUni;
|
| +
|
| + typedef GrGLFragmentProcessor INHERITED;
|
| + };
|
| +
|
| +private:
|
| + virtual bool onIsEqual(const GrProcessor&) const SK_OVERRIDE;
|
| +
|
| + GrColorProfileEffect(GrTexture* colorCube);
|
| +
|
| + GrCoordTransform fColorCubeTransform;
|
| + GrTextureAccess fColorCubeAccess;
|
| +
|
| + typedef GrFragmentProcessor INHERITED;
|
| +};
|
| +
|
| +///////////////////////////////////////////////////////////////////////////////
|
| +
|
| +GrColorProfileEffect::GrColorProfileEffect(GrTexture* colorCube)
|
| + : fColorCubeTransform(kLocal_GrCoordSet, colorCube)
|
| + , fColorCubeAccess(colorCube, "bgra", GrTextureParams::kBilerp_FilterMode) {
|
| + this->addCoordTransform(&fColorCubeTransform);
|
| + this->addTextureAccess(&fColorCubeAccess);
|
| +}
|
| +
|
| +GrColorProfileEffect::~GrColorProfileEffect() {
|
| +}
|
| +
|
| +bool GrColorProfileEffect::onIsEqual(const GrProcessor& sBase) const {
|
| + const GrColorProfileEffect& s = sBase.cast<GrColorProfileEffect>();
|
| + return fColorCubeAccess.getTexture() == s.fColorCubeAccess.getTexture();
|
| +}
|
| +
|
| +const GrBackendFragmentProcessorFactory& GrColorProfileEffect::getFactory() const {
|
| + return GrTBackendFragmentProcessorFactory<GrColorProfileEffect>::getInstance();
|
| +}
|
| +
|
| +void GrColorProfileEffect::onComputeInvariantOutput(InvariantOutput* inout) const {
|
| + inout->fValidFlags = 0;
|
| + inout->fIsSingleComponent = false;
|
| +}
|
| +
|
| +///////////////////////////////////////////////////////////////////////////////
|
| +
|
| +GrColorProfileEffect::GLProcessor::GLProcessor(const GrBackendProcessorFactory& factory,
|
| + const GrProcessor&)
|
| + : INHERITED(factory) {
|
| +}
|
| +
|
| +GrColorProfileEffect::GLProcessor::~GLProcessor() {
|
| +}
|
| +
|
| +void GrColorProfileEffect::GLProcessor::emitCode(GrGLProgramBuilder* builder,
|
| + const GrFragmentProcessor&,
|
| + const GrProcessorKey&,
|
| + const char* outputColor,
|
| + const char* inputColor,
|
| + const TransformedCoordsArray& coords,
|
| + const TextureSamplerArray& samplers) {
|
| + if (NULL == inputColor) {
|
| + inputColor = "vec4(1)";
|
| + }
|
| +
|
| + fColorCubeSizeUni = builder->addUniform(GrGLProgramBuilder::kFragment_Visibility,
|
| + kFloat_GrSLType, "Size");
|
| + const char* colorCubeSizeUni = builder->getUniformCStr(fColorCubeSizeUni);
|
| + fColorCubeInvSizeUni = builder->addUniform(GrGLProgramBuilder::kFragment_Visibility,
|
| + kFloat_GrSLType, "InvSize");
|
| + const char* colorCubeInvSizeUni = builder->getUniformCStr(fColorCubeInvSizeUni);
|
| +
|
| + const char* nonZeroAlpha = "nonZeroAlpha";
|
| + const char* unPMColor = "unPMColor";
|
| + const char* cubeIdx = "cubeIdx";
|
| + const char* cCoords1 = "cCoords1";
|
| + const char* cCoords2 = "cCoords2";
|
| +
|
| + // Note: if implemented using texture3D in OpenGL ES older than OpenGL ES 3.0,
|
| + // the shader might need "#extension GL_OES_texture_3D : enable".
|
| +
|
| + GrGLFragmentShaderBuilder* fsBuilder = builder->getFragmentShaderBuilder();
|
| +
|
| + // Unpremultiply color
|
| + fsBuilder->codeAppendf("\tfloat %s = max(%s.a, 0.00001);\n", nonZeroAlpha, inputColor);
|
| + fsBuilder->codeAppendf("\tvec4 %s = vec4(%s.rgb / %s, %s);\n",
|
| + unPMColor, inputColor, nonZeroAlpha, nonZeroAlpha);
|
| +
|
| + // Fit input color into the cube.
|
| + fsBuilder->codeAppendf(
|
| + "vec3 %s = vec3(%s.rg * vec2((%s - 1.0) * %s) + vec2(0.5 * %s), %s.b * (%s - 1.0));\n",
|
| + cubeIdx, unPMColor, colorCubeSizeUni, colorCubeInvSizeUni, colorCubeInvSizeUni,
|
| + unPMColor, colorCubeSizeUni);
|
| +
|
| + // Compute y coord for for texture fetches.
|
| + fsBuilder->codeAppendf("vec2 %s = vec2(%s.r, (floor(%s.b) + %s.g) * %s);\n",
|
| + cCoords1, cubeIdx, cubeIdx, cubeIdx, colorCubeInvSizeUni);
|
| + fsBuilder->codeAppendf("vec2 %s = vec2(%s.r, (ceil(%s.b) + %s.g) * %s);\n",
|
| + cCoords2, cubeIdx, cubeIdx, cubeIdx, colorCubeInvSizeUni);
|
| +
|
| + // Apply the cube.
|
| + fsBuilder->codeAppendf("%s = vec4(mix(", outputColor);
|
| + fsBuilder->appendTextureLookup(samplers[0], cCoords1, coords[0].getType());
|
| + fsBuilder->codeAppend(".rgb, ");
|
| + fsBuilder->appendTextureLookup(samplers[0], cCoords2, coords[0].getType());
|
| +
|
| + // Premultiply color by alpha. Note that the input alpha is not modified by this shader.
|
| + fsBuilder->codeAppendf(".rgb, fract(%s.b)) * vec3(%s), %s.a);\n",
|
| + cubeIdx, nonZeroAlpha, inputColor);
|
| +}
|
| +
|
| +void GrColorProfileEffect::GLProcessor::setData(const GrGLProgramDataManager& pdman,
|
| + const GrProcessor& proc) {
|
| + const GrColorProfileEffect& colorProfile = proc.cast<GrColorProfileEffect>();
|
| + SkScalar size = SkIntToScalar(colorProfile.colorCubeSize());
|
| + pdman.set1f(fColorCubeSizeUni, SkScalarToFloat(size));
|
| + pdman.set1f(fColorCubeInvSizeUni, SkScalarToFloat(SkScalarInvert(size)));
|
| +}
|
| +
|
| +void GrColorProfileEffect::GLProcessor::GenKey(const GrProcessor& proc,
|
| + const GrGLCaps&, GrProcessorKeyBuilder* b) {
|
| + b->add32(1); // Always same shader for now
|
| +}
|
| +
|
| +GrFragmentProcessor* SkColorCubeFilter::asFragmentProcessor(GrContext* context) const {
|
| + static const GrCacheID::Domain gCubeDomain = GrCacheID::GenerateDomain();
|
| +
|
| + GrCacheID::Key key;
|
| + key.fData32[0] = fUniqueID;
|
| + key.fData32[1] = fCache.cubeDimension();
|
| + key.fData64[1] = 0;
|
| + GrCacheID cacheID(gCubeDomain, key);
|
| +
|
| + GrTextureDesc desc;
|
| + desc.fWidth = fCache.cubeDimension();
|
| + desc.fHeight = fCache.cubeDimension() * fCache.cubeDimension();
|
| + desc.fConfig = kRGBA_8888_GrPixelConfig;
|
| +
|
| + SkAutoTUnref<GrTexture> textureCube(
|
| + static_cast<GrTexture*>(context->findAndRefCachedResource(
|
| + GrTexturePriv::ComputeKey(context->getGpu(), NULL, desc, cacheID))));
|
| +
|
| + if (!textureCube) {
|
| + textureCube.reset(context->createTexture(NULL, desc, cacheID, fCubeData->data(), 0));
|
| + }
|
| +
|
| + return textureCube ? GrColorProfileEffect::Create(textureCube) : NULL;
|
| +}
|
| +#endif
|
|
|