/* * 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(); SkAutoDataUnref cubeData(buffer.readByteArrayAsData()); if (!buffer.validate(is_valid_3D_lut(cubeData, cubeDimension))) { 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(GrGLFPBuilder*, 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(); return fColorCubeAccess.getTexture() == s.fColorCubeAccess.getTexture(); } const GrBackendFragmentProcessorFactory& GrColorProfileEffect::getFactory() const { return GrTBackendFragmentProcessorFactory::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(GrGLFPBuilder* 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". GrGLFPFragmentBuilder* 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(); 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 textureCube( static_cast(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