1fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot/* 2fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot * Copyright 2011 Google Inc. 3fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot * 4fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot * Use of this source code is governed by a BSD-style license that can be 5fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot * found in the LICENSE file. 6fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot */ 7fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot#include "SkColorMatrix.h" 8fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot 9fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot// To detect if we need to apply clamping after applying a matrix, we check if 10fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot// any output component might go outside of [0, 255] for any combination of 11fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot// input components in [0..255]. 12fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot// Each output component is an affine transformation of the input component, so 13fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot// the minimum and maximum values are for any combination of minimum or maximum 14fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot// values of input components (i.e. 0 or 255). 15fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot// E.g. if R' = x*R + y*G + z*B + w*A + t 16fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot// Then the maximum value will be for R=255 if x>0 or R=0 if x<0, and the 17fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot// minimum value will be for R=0 if x>0 or R=255 if x<0. 18fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot// Same goes for all components. 19fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotstatic bool component_needs_clamping(const SkScalar row[5]) { 20fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot SkScalar maxValue = row[4] / 255; 21fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot SkScalar minValue = row[4] / 255; 22fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot for (int i = 0; i < 4; ++i) { 23fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot if (row[i] > 0) 24fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot maxValue += row[i]; 25fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot else 26fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot minValue += row[i]; 27fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot } 28fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot return (maxValue > 1) || (minValue < 0); 29fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot} 30fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot 31fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotbool SkColorMatrix::NeedsClamping(const SkScalar matrix[20]) { 32fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot return component_needs_clamping(matrix) 33fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot || component_needs_clamping(matrix+5) 34fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot || component_needs_clamping(matrix+10) 35fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot || component_needs_clamping(matrix+15); 36fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot} 37fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot 38fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotvoid SkColorMatrix::SetConcat(SkScalar result[20], 39fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot const SkScalar outer[20], const SkScalar inner[20]) { 40fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot SkScalar tmp[20]; 41fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot SkScalar* target; 42fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot 43fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot if (outer == result || inner == result) { 44fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot target = tmp; // will memcpy answer when we're done into result 45fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot } else { 46fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot target = result; 47fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot } 48fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot 49fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot int index = 0; 50fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot for (int j = 0; j < 20; j += 5) { 51fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot for (int i = 0; i < 4; i++) { 52fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot target[index++] = outer[j + 0] * inner[i + 0] + 53fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot outer[j + 1] * inner[i + 5] + 54fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot outer[j + 2] * inner[i + 10] + 55fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot outer[j + 3] * inner[i + 15]; 56fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot } 57fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot target[index++] = outer[j + 0] * inner[4] + 58fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot outer[j + 1] * inner[9] + 59fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot outer[j + 2] * inner[14] + 60fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot outer[j + 3] * inner[19] + 61fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot outer[j + 4]; 62fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot } 63fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot 64fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot if (target != result) { 65fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot memcpy(result, target, 20 * sizeof(SkScalar)); 66fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot } 67fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot} 68fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot 69fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot/////////////////////////////////////////////////////////////////////////////// 70fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot 71fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotvoid SkColorMatrix::setIdentity() { 72fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot memset(fMat, 0, sizeof(fMat)); 73fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot fMat[kR_Scale] = fMat[kG_Scale] = fMat[kB_Scale] = fMat[kA_Scale] = 1; 74fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot} 75fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot 76fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotvoid SkColorMatrix::setScale(SkScalar rScale, SkScalar gScale, SkScalar bScale, 77fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot SkScalar aScale) { 78fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot memset(fMat, 0, sizeof(fMat)); 79fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot fMat[kR_Scale] = rScale; 80fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot fMat[kG_Scale] = gScale; 81fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot fMat[kB_Scale] = bScale; 82fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot fMat[kA_Scale] = aScale; 83fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot} 84fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot 85fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotvoid SkColorMatrix::postTranslate(SkScalar dr, SkScalar dg, SkScalar db, 86fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot SkScalar da) { 87fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot fMat[kR_Trans] += dr; 88fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot fMat[kG_Trans] += dg; 89fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot fMat[kB_Trans] += db; 90fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot fMat[kA_Trans] += da; 91fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot} 92fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot 93fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot/////////////////////////////////////////////////////////////////////////////// 94fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot 95fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotvoid SkColorMatrix::setRotate(Axis axis, SkScalar degrees) { 96fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot SkScalar S, C; 97fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot 98fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot S = SkScalarSinCos(SkDegreesToRadians(degrees), &C); 99fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot 100fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot this->setSinCos(axis, S, C); 101fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot} 102fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot 103fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotvoid SkColorMatrix::setSinCos(Axis axis, SkScalar sine, SkScalar cosine) { 104fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot SkASSERT((unsigned)axis < 3); 105fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot 106fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot static const uint8_t gRotateIndex[] = { 107fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot 6, 7, 11, 12, 108fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot 0, 10, 2, 12, 109fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot 0, 1, 5, 6, 110fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot }; 111fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot const uint8_t* index = gRotateIndex + axis * 4; 112fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot 113fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot this->setIdentity(); 114fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot fMat[index[0]] = cosine; 115fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot fMat[index[1]] = sine; 116fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot fMat[index[2]] = -sine; 117fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot fMat[index[3]] = cosine; 118fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot} 119fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot 120fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotvoid SkColorMatrix::preRotate(Axis axis, SkScalar degrees) { 121fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot SkColorMatrix tmp; 122fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot tmp.setRotate(axis, degrees); 123fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot this->preConcat(tmp); 124fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot} 125fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot 126fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotvoid SkColorMatrix::postRotate(Axis axis, SkScalar degrees) { 127fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot SkColorMatrix tmp; 128fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot tmp.setRotate(axis, degrees); 129fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot this->postConcat(tmp); 130fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot} 131fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot 132fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotvoid SkColorMatrix::setConcat(const SkColorMatrix& matA, const SkColorMatrix& matB) { 133fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot SetConcat(fMat, matA.fMat, matB.fMat); 134fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot} 135fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot 136fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot/////////////////////////////////////////////////////////////////////////////// 137fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot 138fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotstatic void setrow(SkScalar row[], SkScalar r, SkScalar g, SkScalar b) { 139fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot row[0] = r; 140fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot row[1] = g; 141fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot row[2] = b; 142fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot} 143fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot 144fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotstatic const SkScalar kHueR = 0.213f; 145fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotstatic const SkScalar kHueG = 0.715f; 146fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotstatic const SkScalar kHueB = 0.072f; 147fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot 148fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotvoid SkColorMatrix::setSaturation(SkScalar sat) { 149fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot memset(fMat, 0, sizeof(fMat)); 150fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot 151fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot const SkScalar R = kHueR * (1 - sat); 152fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot const SkScalar G = kHueG * (1 - sat); 153fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot const SkScalar B = kHueB * (1 - sat); 154fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot 155fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot setrow(fMat + 0, R + sat, G, B); 156fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot setrow(fMat + 5, R, G + sat, B); 157fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot setrow(fMat + 10, R, G, B + sat); 158fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot fMat[kA_Scale] = 1; 159fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot} 160fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot 161fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotstatic const SkScalar kR2Y = 0.299f; 162fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotstatic const SkScalar kG2Y = 0.587f; 163fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotstatic const SkScalar kB2Y = 0.114f; 164fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot 165fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotstatic const SkScalar kR2U = -0.16874f; 166fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotstatic const SkScalar kG2U = -0.33126f; 167fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotstatic const SkScalar kB2U = 0.5f; 168fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot 169fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotstatic const SkScalar kR2V = 0.5f; 170fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotstatic const SkScalar kG2V = -0.41869f; 171fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotstatic const SkScalar kB2V = -0.08131f; 172fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot 173fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotvoid SkColorMatrix::setRGB2YUV() { 174fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot memset(fMat, 0, sizeof(fMat)); 175fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot 176fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot setrow(fMat + 0, kR2Y, kG2Y, kB2Y); 177fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot setrow(fMat + 5, kR2U, kG2U, kB2U); 178fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot setrow(fMat + 10, kR2V, kG2V, kB2V); 179fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot fMat[kA_Scale] = 1; 180fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot} 181fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot 182fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotstatic const SkScalar kV2R = 1.402f; 183fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotstatic const SkScalar kU2G = -0.34414f; 184fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotstatic const SkScalar kV2G = -0.71414f; 185fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotstatic const SkScalar kU2B = 1.772f; 186fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot 187fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotvoid SkColorMatrix::setYUV2RGB() { 188fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot memset(fMat, 0, sizeof(fMat)); 189fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot 190fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot setrow(fMat + 0, 1, 0, kV2R); 191fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot setrow(fMat + 5, 1, kU2G, kV2G); 192fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot setrow(fMat + 10, 1, kU2B, 0); 193fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot fMat[kA_Scale] = 1; 194fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot} 195