1 /*
2  * Copyright (C) 2014 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.hardware.camera2.params;
18 
19 import static com.android.internal.util.Preconditions.*;
20 
21 import android.hardware.camera2.CameraMetadata;
22 import android.hardware.camera2.utils.HashCodeHelpers;
23 import android.util.Rational;
24 
25 import java.util.Arrays;
26 
27 /**
28  * Immutable class for describing a 3x3 matrix of {@link Rational} values in row-major order.
29  *
30  * <p>This matrix maps a transform from one color space to another. For the particular color space
31  * source and target, see the appropriate camera metadata documentation for the key that provides
32  * this value.</p>
33  *
34  * @see CameraMetadata
35  */
36 public final class ColorSpaceTransform {
37 
38     /** The number of rows in this matrix. */
39     private static final int ROWS = 3;
40 
41     /** The number of columns in this matrix. */
42     private static final int COLUMNS = 3;
43 
44     /** The number of total Rational elements in this matrix. */
45     private static final int COUNT = ROWS * COLUMNS;
46 
47     /** Number of int elements in a rational. */
48     private static final int RATIONAL_SIZE = 2;
49 
50     /** Numerator offset inside a rational (pair). */
51     private static final int OFFSET_NUMERATOR = 0;
52 
53     /** Denominator offset inside a rational (pair). */
54     private static final int OFFSET_DENOMINATOR = 1;
55 
56     /** Number of int elements in this matrix. */
57     private static final int COUNT_INT = ROWS * COLUMNS * RATIONAL_SIZE;
58 
59     /**
60      * Create a new immutable {@link ColorSpaceTransform} instance from a {@link Rational} array.
61      *
62      * <p>The elements must be stored in a row-major order.</p>
63      *
64      * @param elements An array of {@code 9} elements
65      *
66      * @throws IllegalArgumentException
67      *            if the count of {@code elements} is not {@code 9}
68      * @throws NullPointerException
69      *            if {@code elements} or any sub-element is {@code null}
70      */
ColorSpaceTransform(Rational[] elements)71     public ColorSpaceTransform(Rational[] elements) {
72 
73         checkNotNull(elements, "elements must not be null");
74         if (elements.length != COUNT) {
75             throw new IllegalArgumentException("elements must be " + COUNT + " length");
76         }
77 
78         mElements = new int[COUNT_INT];
79 
80         for (int i = 0; i < elements.length; ++i) {
81             checkNotNull(elements, "element[" + i + "] must not be null");
82             mElements[i * RATIONAL_SIZE + OFFSET_NUMERATOR] = elements[i].getNumerator();
83             mElements[i * RATIONAL_SIZE + OFFSET_DENOMINATOR] = elements[i].getDenominator();
84         }
85     }
86 
87     /**
88      * Create a new immutable {@link ColorSpaceTransform} instance from an {@code int} array.
89      *
90      * <p>The elements must be stored in a row-major order. Each rational is stored
91      * contiguously as a {@code (numerator, denominator)} pair.</p>
92      *
93      * <p>In particular:<pre>{@code
94      * int[] elements = new int[
95      *     N11, D11, N12, D12, N13, D13,
96      *     N21, D21, N22, D22, N23, D23,
97      *     N31, D31, N32, D32, N33, D33
98      * ];
99      *
100      * new ColorSpaceTransform(elements)}</pre>
101      *
102      * where {@code Nij} and {@code Dij} is the numerator and denominator for row {@code i} and
103      * column {@code j}.</p>
104      *
105      * @param elements An array of {@code 18} elements
106      *
107      * @throws IllegalArgumentException
108      *            if the count of {@code elements} is not {@code 18}
109      * @throws NullPointerException
110      *            if {@code elements} is {@code null}
111      */
ColorSpaceTransform(int[] elements)112     public ColorSpaceTransform(int[] elements) {
113         checkNotNull(elements, "elements must not be null");
114         if (elements.length != COUNT_INT) {
115             throw new IllegalArgumentException("elements must be " + COUNT_INT + " length");
116         }
117 
118         for (int i = 0; i < elements.length; ++i) {
119             checkNotNull(elements, "element " + i + " must not be null");
120         }
121 
122         mElements = Arrays.copyOf(elements, elements.length);
123     }
124 
125     /**
126      * Get an element of this matrix by its row and column.
127      *
128      * <p>The rows must be within the range [0, 3),
129      * and the column must be within the range [0, 3).</p>
130      *
131      * @return element (non-{@code null})
132      *
133      * @throws IllegalArgumentException if column or row was out of range
134      */
getElement(int column, int row)135     public Rational getElement(int column, int row) {
136         if (column < 0 || column >= COLUMNS) {
137             throw new IllegalArgumentException("column out of range");
138         } else if (row < 0 || row >= ROWS) {
139             throw new IllegalArgumentException("row out of range");
140         }
141 
142         int numerator = mElements[(row * COLUMNS + column) * RATIONAL_SIZE + OFFSET_NUMERATOR];
143         int denominator = mElements[(row * COLUMNS + column) * RATIONAL_SIZE + OFFSET_DENOMINATOR];
144 
145         return new Rational(numerator, denominator);
146     }
147 
148     /**
149      * Copy the {@link Rational} elements in row-major order from this matrix into the destination.
150      *
151      * @param destination
152      *          an array big enough to hold at least {@code 9} elements after the
153      *          {@code offset}
154      * @param offset
155      *          a non-negative offset into the array
156      * @throws NullPointerException
157      *          If {@code destination} was {@code null}
158      * @throws ArrayIndexOutOfBoundsException
159      *          If there's not enough room to write the elements at the specified destination and
160      *          offset.
161      */
copyElements(Rational[] destination, int offset)162     public void copyElements(Rational[] destination, int offset) {
163         checkArgumentNonnegative(offset, "offset must not be negative");
164         checkNotNull(destination, "destination must not be null");
165         if (destination.length - offset < COUNT) {
166             throw new ArrayIndexOutOfBoundsException("destination too small to fit elements");
167         }
168 
169         for (int i = 0, j = 0; i < COUNT; ++i, j += RATIONAL_SIZE) {
170             int numerator = mElements[j + OFFSET_NUMERATOR];
171             int denominator = mElements[j + OFFSET_DENOMINATOR];
172 
173             destination[i + offset] = new Rational(numerator, denominator);
174         }
175     }
176 
177     /**
178      * Copy the {@link Rational} elements in row-major order from this matrix into the destination.
179      *
180      * <p>Each element is stored as a contiguous rational packed as a
181      * {@code (numerator, denominator)} pair of ints, identical to the
182      * {@link ColorSpaceTransform#ColorSpaceTransform(int[]) constructor}.</p>
183      *
184      * @param destination
185      *          an array big enough to hold at least {@code 18} elements after the
186      *          {@code offset}
187      * @param offset
188      *          a non-negative offset into the array
189      * @throws NullPointerException
190      *          If {@code destination} was {@code null}
191      * @throws ArrayIndexOutOfBoundsException
192      *          If there's not enough room to write the elements at the specified destination and
193      *          offset.
194      *
195      * @see ColorSpaceTransform#ColorSpaceTransform(int[])
196      */
copyElements(int[] destination, int offset)197     public void copyElements(int[] destination, int offset) {
198         checkArgumentNonnegative(offset, "offset must not be negative");
199         checkNotNull(destination, "destination must not be null");
200         if (destination.length - offset < COUNT_INT) {
201             throw new ArrayIndexOutOfBoundsException("destination too small to fit elements");
202         }
203 
204         // Manual copy faster than System#arraycopy for very small loops
205         for (int i = 0; i < COUNT_INT; ++i) {
206             destination[i + offset] = mElements[i];
207         }
208     }
209 
210     /**
211      * Check if this {@link ColorSpaceTransform} is equal to another {@link ColorSpaceTransform}.
212      *
213      * <p>Two color space transforms are equal if and only if all of their elements are
214      * {@link Object#equals equal}.</p>
215      *
216      * @return {@code true} if the objects were equal, {@code false} otherwise
217      */
218     @Override
equals(final Object obj)219     public boolean equals(final Object obj) {
220         if (obj == null) {
221             return false;
222         }
223         if (this == obj) {
224             return true;
225         }
226         if (obj instanceof ColorSpaceTransform) {
227             final ColorSpaceTransform other = (ColorSpaceTransform) obj;
228             for (int i = 0, j = 0; i < COUNT; ++i, j += RATIONAL_SIZE) {
229                 int numerator = mElements[j + OFFSET_NUMERATOR];
230                 int denominator = mElements[j + OFFSET_DENOMINATOR];
231                 int numeratorOther = other.mElements[j + OFFSET_NUMERATOR];
232                 int denominatorOther = other.mElements[j + OFFSET_DENOMINATOR];
233                 Rational r = new Rational(numerator, denominator);
234                 Rational rOther = new Rational(numeratorOther, denominatorOther);
235                 if (!r.equals(rOther)) {
236                     return false;
237                 }
238             }
239             return true;
240         }
241         return false;
242     }
243 
244     /**
245      * {@inheritDoc}
246      */
247     @Override
hashCode()248     public int hashCode() {
249         return HashCodeHelpers.hashCode(mElements);
250     }
251 
252     /**
253      * Return the color space transform as a string representation.
254      *
255      *  <p> Example:
256      * {@code "ColorSpaceTransform([1/1, 0/1, 0/1], [0/1, 1/1, 0/1], [0/1, 0/1, 1/1])"} is an
257      * identity transform. Elements are printed in row major order. </p>
258      *
259      * @return string representation of color space transform
260      */
261     @Override
toString()262     public String toString() {
263         return String.format("ColorSpaceTransform%s", toShortString());
264     }
265 
266     /**
267      * Return the color space transform as a compact string representation.
268      *
269      *  <p> Example:
270      * {@code "([1/1, 0/1, 0/1], [0/1, 1/1, 0/1], [0/1, 0/1, 1/1])"} is an identity transform.
271      * Elements are printed in row major order. </p>
272      *
273      * @return compact string representation of color space transform
274      */
toShortString()275     private String toShortString() {
276         StringBuilder sb = new StringBuilder("(");
277         for (int row = 0, i = 0; row < ROWS; row++) {
278             sb.append("[");
279             for (int col = 0; col < COLUMNS; col++, i += RATIONAL_SIZE) {
280                 int numerator = mElements[i + OFFSET_NUMERATOR];
281                 int denominator = mElements[i + OFFSET_DENOMINATOR];
282                 sb.append(numerator);
283                 sb.append("/");
284                 sb.append(denominator);
285                 if (col < COLUMNS - 1) {
286                     sb.append(", ");
287                 }
288             }
289             sb.append("]");
290             if (row < ROWS - 1) {
291                 sb.append(", ");
292             }
293         }
294         sb.append(")");
295         return sb.toString();
296     }
297 
298     private final int[] mElements;
299 }
300