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 |