From 8a8d841d485ff36b64d838575eb3735c1ddcf929 Mon Sep 17 00:00:00 2001 From: reed Date: Mon, 2 Mar 2015 13:46:03 -0800 Subject: [PATCH] add virtuals to optimize composing colorfilters BUG=skia: Review URL: https://codereview.chromium.org/968993004 --- include/core/SkColorFilter.h | 9 +++ include/effects/SkColorMatrix.h | 3 + include/effects/SkColorMatrixFilter.h | 2 + src/core/SkColorFilter.cpp | 10 +++- src/effects/SkColorFilterImageFilter.cpp | 86 +--------------------------- src/effects/SkColorMatrix.cpp | 97 +++++++++++++++++++++----------- src/effects/SkColorMatrixFilter.cpp | 10 ++++ src/effects/SkTableColorFilter.cpp | 57 +++++++++++++++++-- 8 files changed, 154 insertions(+), 120 deletions(-) diff --git a/include/core/SkColorFilter.h b/include/core/SkColorFilter.h index bb62805..31a4365 100644 --- a/include/core/SkColorFilter.h +++ b/include/core/SkColorFilter.h @@ -98,6 +98,15 @@ public: virtual uint32_t getFlags() const { return 0; } /** + * If this subclass can optimally createa composition with the inner filter, return it as + * a new filter (which the caller must unref() when it is done). If no such optimization + * is known, return NULL. + * + * e.g. result(color) == this_filter(inner(color)) + */ + virtual SkColorFilter* newComposed(const SkColorFilter* /*inner*/) const { return NULL; } + + /** * Apply this colorfilter to the specified SkColor. This routine handles * converting to SkPMColor, calling the filter, and then converting back * to SkColor. This method is not virtual, but will call filterSpan() diff --git a/include/effects/SkColorMatrix.h b/include/effects/SkColorMatrix.h index c598a12..7ac4579 100644 --- a/include/effects/SkColorMatrix.h +++ b/include/effects/SkColorMatrix.h @@ -59,6 +59,9 @@ public: } bool operator!=(const SkColorMatrix& other) const { return !((*this) == other); } + + static bool NeedsClamping(const SkScalar[20]); + static void SetConcat(SkScalar result[20], const SkScalar outer[20], const SkScalar inner[20]); }; #endif diff --git a/include/effects/SkColorMatrixFilter.h b/include/effects/SkColorMatrixFilter.h index b5ab689..ff80607 100644 --- a/include/effects/SkColorMatrixFilter.h +++ b/include/effects/SkColorMatrixFilter.h @@ -25,6 +25,8 @@ public: void filterSpan16(const uint16_t src[], int count, uint16_t[]) const SK_OVERRIDE; uint32_t getFlags() const SK_OVERRIDE; bool asColorMatrix(SkScalar matrix[20]) const SK_OVERRIDE; + SkColorFilter* newComposed(const SkColorFilter*) const SK_OVERRIDE; + #if SK_SUPPORT_GPU GrFragmentProcessor* asFragmentProcessor(GrContext*) const SK_OVERRIDE; #endif diff --git a/src/core/SkColorFilter.cpp b/src/core/SkColorFilter.cpp index d49fc52..2a78058 100644 --- a/src/core/SkColorFilter.cpp +++ b/src/core/SkColorFilter.cpp @@ -104,6 +104,8 @@ SkFlattenable* SkComposeColorFilter::CreateProc(SkReadBuffer& buffer) { return CreateComposeFilter(outer, inner); } +/////////////////////////////////////////////////////////////////////////////////////////////////// + SkColorFilter* SkColorFilter::CreateComposeFilter(SkColorFilter* outer, SkColorFilter* inner) { if (!outer) { return SkSafeRef(inner); @@ -111,7 +113,13 @@ SkColorFilter* SkColorFilter::CreateComposeFilter(SkColorFilter* outer, SkColorF if (!inner) { return SkSafeRef(outer); } - return SkNEW_ARGS(SkComposeColorFilter, (outer, inner)); + + // Give the subclass a shot at a more optimal composition... + SkColorFilter* composition = outer->newComposed(inner); + if (NULL == composition) { + composition = SkNEW_ARGS(SkComposeColorFilter, (outer, inner)); + } + return composition; } SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkColorFilter) diff --git a/src/effects/SkColorFilterImageFilter.cpp b/src/effects/SkColorFilterImageFilter.cpp index 0a4a693..fdf6de7 100755 --- a/src/effects/SkColorFilterImageFilter.cpp +++ b/src/effects/SkColorFilterImageFilter.cpp @@ -15,61 +15,6 @@ #include "SkTableColorFilter.h" #include "SkWriteBuffer.h" -namespace { - -void mult_color_matrix(SkScalar a[20], SkScalar b[20], SkScalar out[20]) { - for (int j = 0; j < 4; ++j) { - for (int i = 0; i < 5; ++i) { - out[i+j*5] = 4 == i ? a[4+j*5] : 0; - for (int k = 0; k < 4; ++k) - out[i+j*5] += a[k+j*5] * b[i+k*5]; - } - } -} - -// Combines the two lookup tables so that making a lookup using OUT has -// the same effect as making a lookup through B then A. -void combine_color_tables(const uint8_t a[4 * 256], - const uint8_t b[4 * 256], - uint8_t out[4 * 256]) { - for (int i = 0; i < 4; i++) { - for (int j = 0; j < 256; j++) { - out[i * 256 + j] = a[i * 256 + b[i * 256 + j]]; - } - } -} - -// To detect if we need to apply clamping after applying a matrix, we check if -// any output component might go outside of [0, 255] for any combination of -// input components in [0..255]. -// Each output component is an affine transformation of the input component, so -// the minimum and maximum values are for any combination of minimum or maximum -// values of input components (i.e. 0 or 255). -// E.g. if R' = x*R + y*G + z*B + w*A + t -// Then the maximum value will be for R=255 if x>0 or R=0 if x<0, and the -// minimum value will be for R=0 if x>0 or R=255 if x<0. -// Same goes for all components. -bool component_needs_clamping(SkScalar row[5]) { - SkScalar maxValue = row[4] / 255; - SkScalar minValue = row[4] / 255; - for (int i = 0; i < 4; ++i) { - if (row[i] > 0) - maxValue += row[i]; - else - minValue += row[i]; - } - return (maxValue > 1) || (minValue < 0); -} - -bool matrix_needs_clamping(SkScalar matrix[20]) { - return component_needs_clamping(matrix) - || component_needs_clamping(matrix+5) - || component_needs_clamping(matrix+10) - || component_needs_clamping(matrix+15); -} - -}; - SkColorFilterImageFilter* SkColorFilterImageFilter::Create(SkColorFilter* cf, SkImageFilter* input, const CropRect* cropRect, uint32_t uniqueID) { if (NULL == cf) { @@ -77,35 +22,10 @@ SkColorFilterImageFilter* SkColorFilterImageFilter::Create(SkColorFilter* cf, } SkColorFilter* inputColorFilter; - if (input && input->asColorFilter(&inputColorFilter) && inputColorFilter) { + if (input && input->asColorFilter(&inputColorFilter)) { SkAutoUnref autoUnref(inputColorFilter); - - // Try to collapse two consecutive matrix filters - SkScalar colorMatrix[20], inputMatrix[20]; - if (cf->asColorMatrix(colorMatrix) && inputColorFilter->asColorMatrix(inputMatrix) - && !matrix_needs_clamping(inputMatrix)) { - SkScalar combinedMatrix[20]; - mult_color_matrix(colorMatrix, inputMatrix, combinedMatrix); - SkAutoTUnref newCF(SkColorMatrixFilter::Create(combinedMatrix)); - return SkNEW_ARGS(SkColorFilterImageFilter, (newCF, input->getInput(0), cropRect, 0)); - } - - // Try to collapse two consecutive table filters - SkBitmap colorTable, inputTable; - if (cf->asComponentTable(&colorTable) && inputColorFilter->asComponentTable(&inputTable)) { - uint8_t combinedTable[4 * 256]; - SkAutoLockPixels colorLock(colorTable); - SkAutoLockPixels inputLock(inputTable); - - combine_color_tables(colorTable.getAddr8(0, 0), inputTable.getAddr8(0, 0), - combinedTable); - SkAutoTUnref newCF(SkTableColorFilter::CreateARGB( - &combinedTable[256 * 0], - &combinedTable[256 * 1], - &combinedTable[256 * 2], - &combinedTable[256 * 3]) - ); - + SkAutoTUnref newCF(cf->newComposed(inputColorFilter)); + if (newCF) { return SkNEW_ARGS(SkColorFilterImageFilter, (newCF, input->getInput(0), cropRect, 0)); } } diff --git a/src/effects/SkColorMatrix.cpp b/src/effects/SkColorMatrix.cpp index 3842285..d99d51e 100644 --- a/src/effects/SkColorMatrix.cpp +++ b/src/effects/SkColorMatrix.cpp @@ -6,6 +6,68 @@ */ #include "SkColorMatrix.h" +// To detect if we need to apply clamping after applying a matrix, we check if +// any output component might go outside of [0, 255] for any combination of +// input components in [0..255]. +// Each output component is an affine transformation of the input component, so +// the minimum and maximum values are for any combination of minimum or maximum +// values of input components (i.e. 0 or 255). +// E.g. if R' = x*R + y*G + z*B + w*A + t +// Then the maximum value will be for R=255 if x>0 or R=0 if x<0, and the +// minimum value will be for R=0 if x>0 or R=255 if x<0. +// Same goes for all components. +static bool component_needs_clamping(const SkScalar row[5]) { + SkScalar maxValue = row[4] / 255; + SkScalar minValue = row[4] / 255; + for (int i = 0; i < 4; ++i) { + if (row[i] > 0) + maxValue += row[i]; + else + minValue += row[i]; + } + return (maxValue > 1) || (minValue < 0); +} + +bool SkColorMatrix::NeedsClamping(const SkScalar matrix[20]) { + return component_needs_clamping(matrix) + || component_needs_clamping(matrix+5) + || component_needs_clamping(matrix+10) + || component_needs_clamping(matrix+15); +} + +void SkColorMatrix::SetConcat(SkScalar result[20], + const SkScalar outer[20], const SkScalar inner[20]) { + SkScalar tmp[20]; + SkScalar* target; + + if (outer == result || inner == result) { + target = tmp; // will memcpy answer when we're done into result + } else { + target = result; + } + + int index = 0; + for (int j = 0; j < 20; j += 5) { + for (int i = 0; i < 4; i++) { + target[index++] = outer[j + 0] * inner[i + 0] + + outer[j + 1] * inner[i + 5] + + outer[j + 2] * inner[i + 10] + + outer[j + 3] * inner[i + 15]; + } + target[index++] = outer[j + 0] * inner[4] + + outer[j + 1] * inner[9] + + outer[j + 2] * inner[14] + + outer[j + 3] * inner[19] + + outer[j + 4]; + } + + if (target != result) { + memcpy(result, target, 20 * sizeof(SkScalar)); + } +} + +/////////////////////////////////////////////////////////////////////////////// + void SkColorMatrix::setIdentity() { memset(fMat, 0, sizeof(fMat)); fMat[kR_Scale] = fMat[kG_Scale] = fMat[kB_Scale] = fMat[kA_Scale] = 1; @@ -67,38 +129,8 @@ void SkColorMatrix::postRotate(Axis axis, SkScalar degrees) { this->postConcat(tmp); } -/////////////////////////////////////////////////////////////////////////////// - -void SkColorMatrix::setConcat(const SkColorMatrix& matA, - const SkColorMatrix& matB) { - SkScalar tmp[20]; - SkScalar* result = fMat; - - if (&matA == this || &matB == this) { - result = tmp; - } - - const SkScalar* a = matA.fMat; - const SkScalar* b = matB.fMat; - - int index = 0; - for (int j = 0; j < 20; j += 5) { - for (int i = 0; i < 4; i++) { - result[index++] = SkScalarMul(a[j + 0], b[i + 0]) + - SkScalarMul(a[j + 1], b[i + 5]) + - SkScalarMul(a[j + 2], b[i + 10]) + - SkScalarMul(a[j + 3], b[i + 15]); - } - result[index++] = SkScalarMul(a[j + 0], b[4]) + - SkScalarMul(a[j + 1], b[9]) + - SkScalarMul(a[j + 2], b[14]) + - SkScalarMul(a[j + 3], b[19]) + - a[j + 4]; - } - - if (fMat != result) { - memcpy(fMat, result, sizeof(fMat)); - } +void SkColorMatrix::setConcat(const SkColorMatrix& matA, const SkColorMatrix& matB) { + SetConcat(fMat, matA.fMat, matB.fMat); } /////////////////////////////////////////////////////////////////////////////// @@ -160,3 +192,4 @@ void SkColorMatrix::setYUV2RGB() { setrow(fMat + 10, 1, kU2B, 0); fMat[kA_Scale] = 1; } + diff --git a/src/effects/SkColorMatrixFilter.cpp b/src/effects/SkColorMatrixFilter.cpp index c9cb186..6ab4a67 100644 --- a/src/effects/SkColorMatrixFilter.cpp +++ b/src/effects/SkColorMatrixFilter.cpp @@ -322,6 +322,16 @@ bool SkColorMatrixFilter::asColorMatrix(SkScalar matrix[20]) const { return true; } +SkColorFilter* SkColorMatrixFilter::newComposed(const SkColorFilter* innerFilter) const { + SkScalar innerMatrix[20]; + if (innerFilter->asColorMatrix(innerMatrix) && !SkColorMatrix::NeedsClamping(innerMatrix)) { + SkScalar concat[20]; + SkColorMatrix::SetConcat(concat, fMatrix.fMat, innerMatrix); + return SkColorMatrixFilter::Create(concat); + } + return NULL; +} + #if SK_SUPPORT_GPU #include "GrFragmentProcessor.h" #include "GrInvariantOutput.h" diff --git a/src/effects/SkTableColorFilter.cpp b/src/effects/SkTableColorFilter.cpp index 37fdd90..d6c3e5f 100644 --- a/src/effects/SkTableColorFilter.cpp +++ b/src/effects/SkTableColorFilter.cpp @@ -41,13 +41,13 @@ public: } bool asComponentTable(SkBitmap* table) const SK_OVERRIDE; + SkColorFilter* newComposed(const SkColorFilter* inner) const SK_OVERRIDE; #if SK_SUPPORT_GPU GrFragmentProcessor* asFragmentProcessor(GrContext* context) const SK_OVERRIDE; #endif - virtual void filterSpan(const SkPMColor src[], int count, - SkPMColor dst[]) const SK_OVERRIDE; + void filterSpan(const SkPMColor src[], int count, SkPMColor dst[]) const SK_OVERRIDE; SK_TO_STRING_OVERRIDE() @@ -109,8 +109,7 @@ static const uint8_t gIdentityTable[] = { 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF }; -void SkTable_ColorFilter::filterSpan(const SkPMColor src[], int count, - SkPMColor dst[]) const { +void SkTable_ColorFilter::filterSpan(const SkPMColor src[], int count, SkPMColor dst[]) const { const uint8_t* table = fStorage; const uint8_t* tableA = gIdentityTable; const uint8_t* tableR = gIdentityTable; @@ -274,6 +273,56 @@ bool SkTable_ColorFilter::asComponentTable(SkBitmap* table) const { return true; } +// Combines the two lookup tables so that making a lookup using res[] has +// the same effect as making a lookup through inner[] then outer[]. +static void combine_tables(uint8_t res[256], const uint8_t outer[256], const uint8_t inner[256]) { + for (int i = 0; i < 256; i++) { + res[i] = outer[inner[i]]; + } +} + +SkColorFilter* SkTable_ColorFilter::newComposed(const SkColorFilter* innerFilter) const { + SkBitmap innerBM; + if (!innerFilter->asComponentTable(&innerBM)) { + return NULL; + } + + innerBM.lockPixels(); + if (NULL == innerBM.getPixels()) { + return NULL; + } + + const uint8_t* table = fStorage; + const uint8_t* tableA = gIdentityTable; + const uint8_t* tableR = gIdentityTable; + const uint8_t* tableG = gIdentityTable; + const uint8_t* tableB = gIdentityTable; + if (fFlags & kA_Flag) { + tableA = table; table += 256; + } + if (fFlags & kR_Flag) { + tableR = table; table += 256; + } + if (fFlags & kG_Flag) { + tableG = table; table += 256; + } + if (fFlags & kB_Flag) { + tableB = table; + } + + uint8_t concatA[256]; + uint8_t concatR[256]; + uint8_t concatG[256]; + uint8_t concatB[256]; + + combine_tables(concatA, tableA, innerBM.getAddr8(0, 0)); + combine_tables(concatR, tableR, innerBM.getAddr8(0, 1)); + combine_tables(concatG, tableG, innerBM.getAddr8(0, 2)); + combine_tables(concatB, tableB, innerBM.getAddr8(0, 3)); + + return SkTableColorFilter::CreateARGB(concatA, concatR, concatG, concatB); +} + #if SK_SUPPORT_GPU #include "GrFragmentProcessor.h" -- 2.7.4