1 /*
2  * Copyright (C) 2007 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.graphics;
18 
19 import java.util.Arrays;
20 
21 /**
22  * 4x5 matrix for transforming the color and alpha components of a Bitmap.
23  * The matrix can be passed as single array, and is treated as follows:
24  *
25  * <pre>
26  *  [ a, b, c, d, e,
27  *    f, g, h, i, j,
28  *    k, l, m, n, o,
29  *    p, q, r, s, t ]</pre>
30  *
31  * <p>
32  * When applied to a color <code>[R, G, B, A]</code>, the resulting color
33  * is computed as:
34  * </p>
35  *
36  * <pre>
37  *   R&rsquo; = a*R + b*G + c*B + d*A + e;
38  *   G&rsquo; = f*R + g*G + h*B + i*A + j;
39  *   B&rsquo; = k*R + l*G + m*B + n*A + o;
40  *   A&rsquo; = p*R + q*G + r*B + s*A + t;</pre>
41  *
42  * <p>
43  * That resulting color <code>[R&rsquo;, G&rsquo;, B&rsquo;, A&rsquo;]</code>
44  * then has each channel clamped to the <code>0</code> to <code>255</code>
45  * range.
46  * </p>
47  *
48  * <p>
49  * The sample ColorMatrix below inverts incoming colors by scaling each
50  * channel by <code>-1</code>, and then shifting the result up by
51  * <code>255</code> to remain in the standard color space.
52  * </p>
53  *
54  * <pre>
55  *   [ -1, 0, 0, 0, 255,
56  *     0, -1, 0, 0, 255,
57  *     0, 0, -1, 0, 255,
58  *     0, 0, 0, 1, 0 ]</pre>
59  */
60 @SuppressWarnings({ "MismatchedReadAndWriteOfArray", "PointlessArithmeticExpression" })
61 public class ColorMatrix {
62     private final float[] mArray = new float[20];
63 
64     /**
65      * Create a new colormatrix initialized to identity (as if reset() had
66      * been called).
67      */
ColorMatrix()68     public ColorMatrix() {
69         reset();
70     }
71 
72     /**
73      * Create a new colormatrix initialized with the specified array of values.
74      */
ColorMatrix(float[] src)75     public ColorMatrix(float[] src) {
76         System.arraycopy(src, 0, mArray, 0, 20);
77     }
78 
79     /**
80      * Create a new colormatrix initialized with the specified colormatrix.
81      */
ColorMatrix(ColorMatrix src)82     public ColorMatrix(ColorMatrix src) {
83         System.arraycopy(src.mArray, 0, mArray, 0, 20);
84     }
85 
86     /**
87      * Return the array of floats representing this colormatrix.
88      */
getArray()89     public final float[] getArray() { return mArray; }
90 
91     /**
92      * Set this colormatrix to identity:
93      * <pre>
94      * [ 1 0 0 0 0   - red vector
95      *   0 1 0 0 0   - green vector
96      *   0 0 1 0 0   - blue vector
97      *   0 0 0 1 0 ] - alpha vector
98      * </pre>
99      */
reset()100     public void reset() {
101         final float[] a = mArray;
102         Arrays.fill(a, 0);
103         a[0] = a[6] = a[12] = a[18] = 1;
104     }
105 
106     /**
107      * Assign the src colormatrix into this matrix, copying all of its values.
108      */
set(ColorMatrix src)109     public void set(ColorMatrix src) {
110         System.arraycopy(src.mArray, 0, mArray, 0, 20);
111     }
112 
113     /**
114      * Assign the array of floats into this matrix, copying all of its values.
115      */
set(float[] src)116     public void set(float[] src) {
117         System.arraycopy(src, 0, mArray, 0, 20);
118     }
119 
120     /**
121      * Set this colormatrix to scale by the specified values.
122      */
setScale(float rScale, float gScale, float bScale, float aScale)123     public void setScale(float rScale, float gScale, float bScale,
124                          float aScale) {
125         final float[] a = mArray;
126 
127         for (int i = 19; i > 0; --i) {
128             a[i] = 0;
129         }
130         a[0] = rScale;
131         a[6] = gScale;
132         a[12] = bScale;
133         a[18] = aScale;
134     }
135 
136     /**
137      * Set the rotation on a color axis by the specified values.
138      * <p>
139      * <code>axis=0</code> correspond to a rotation around the RED color
140      * <code>axis=1</code> correspond to a rotation around the GREEN color
141      * <code>axis=2</code> correspond to a rotation around the BLUE color
142      * </p>
143      */
setRotate(int axis, float degrees)144     public void setRotate(int axis, float degrees) {
145         reset();
146         double radians = degrees * Math.PI / 180d;
147         float cosine = (float) Math.cos(radians);
148         float sine = (float) Math.sin(radians);
149         switch (axis) {
150         // Rotation around the red color
151         case 0:
152             mArray[6] = mArray[12] = cosine;
153             mArray[7] = sine;
154             mArray[11] = -sine;
155             break;
156         // Rotation around the green color
157         case 1:
158             mArray[0] = mArray[12] = cosine;
159             mArray[2] = -sine;
160             mArray[10] = sine;
161             break;
162         // Rotation around the blue color
163         case 2:
164             mArray[0] = mArray[6] = cosine;
165             mArray[1] = sine;
166             mArray[5] = -sine;
167             break;
168         default:
169             throw new RuntimeException();
170         }
171     }
172 
173     /**
174      * Set this colormatrix to the concatenation of the two specified
175      * colormatrices, such that the resulting colormatrix has the same effect
176      * as applying matB and then applying matA.
177      * <p>
178      * It is legal for either matA or matB to be the same colormatrix as this.
179      * </p>
180      */
setConcat(ColorMatrix matA, ColorMatrix matB)181     public void setConcat(ColorMatrix matA, ColorMatrix matB) {
182         float[] tmp;
183         if (matA == this || matB == this) {
184             tmp = new float[20];
185         } else {
186             tmp = mArray;
187         }
188 
189         final float[] a = matA.mArray;
190         final float[] b = matB.mArray;
191         int index = 0;
192         for (int j = 0; j < 20; j += 5) {
193             for (int i = 0; i < 4; i++) {
194                 tmp[index++] = a[j + 0] * b[i + 0] +  a[j + 1] * b[i + 5] +
195                                a[j + 2] * b[i + 10] + a[j + 3] * b[i + 15];
196             }
197             tmp[index++] = a[j + 0] * b[4] +  a[j + 1] * b[9] +
198                            a[j + 2] * b[14] + a[j + 3] * b[19] +
199                            a[j + 4];
200         }
201 
202         if (tmp != mArray) {
203             System.arraycopy(tmp, 0, mArray, 0, 20);
204         }
205     }
206 
207     /**
208      * Concat this colormatrix with the specified prematrix.
209      * <p>
210      * This is logically the same as calling setConcat(this, prematrix);
211      * </p>
212      */
preConcat(ColorMatrix prematrix)213     public void preConcat(ColorMatrix prematrix) {
214         setConcat(this, prematrix);
215     }
216 
217     /**
218      * Concat this colormatrix with the specified postmatrix.
219      * <p>
220      * This is logically the same as calling setConcat(postmatrix, this);
221      * </p>
222      */
postConcat(ColorMatrix postmatrix)223     public void postConcat(ColorMatrix postmatrix) {
224         setConcat(postmatrix, this);
225     }
226 
227     ///////////////////////////////////////////////////////////////////////////
228 
229     /**
230      * Set the matrix to affect the saturation of colors.
231      *
232      * @param sat A value of 0 maps the color to gray-scale. 1 is identity.
233      */
setSaturation(float sat)234     public void setSaturation(float sat) {
235         reset();
236         float[] m = mArray;
237 
238         final float invSat = 1 - sat;
239         final float R = 0.213f * invSat;
240         final float G = 0.715f * invSat;
241         final float B = 0.072f * invSat;
242 
243         m[0] = R + sat; m[1] = G;       m[2] = B;
244         m[5] = R;       m[6] = G + sat; m[7] = B;
245         m[10] = R;      m[11] = G;      m[12] = B + sat;
246     }
247 
248     /**
249      * Set the matrix to convert RGB to YUV
250      */
setRGB2YUV()251     public void setRGB2YUV() {
252         reset();
253         float[] m = mArray;
254         // these coefficients match those in libjpeg
255         m[0]  = 0.299f;    m[1]  = 0.587f;    m[2]  = 0.114f;
256         m[5]  = -0.16874f; m[6]  = -0.33126f; m[7]  = 0.5f;
257         m[10] = 0.5f;      m[11] = -0.41869f; m[12] = -0.08131f;
258     }
259 
260     /**
261      * Set the matrix to convert from YUV to RGB
262      */
setYUV2RGB()263     public void setYUV2RGB() {
264         reset();
265         float[] m = mArray;
266         // these coefficients match those in libjpeg
267                                         m[2] = 1.402f;
268         m[5] = 1;   m[6] = -0.34414f;   m[7] = -0.71414f;
269         m[10] = 1;  m[11] = 1.772f;     m[12] = 0;
270     }
271 
272     @Override
equals(Object obj)273     public boolean equals(Object obj) {
274         // if (obj == this) return true; -- NaN value would mean matrix != itself
275         if (!(obj instanceof ColorMatrix)) {
276             return false;
277         }
278 
279         // we don't use Arrays.equals(), since that considers NaN == NaN
280         final float[] other = ((ColorMatrix) obj).mArray;
281         for (int i = 0; i < 20; i++) {
282             if (other[i] != mArray[i]) {
283                 return false;
284             }
285         }
286         return true;
287     }
288 }
289