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
17package android.hardware.camera2.params;
18
19import static com.android.internal.util.Preconditions.*;
20
21import android.hardware.camera2.CameraMetadata;
22import android.hardware.camera2.utils.HashCodeHelpers;
23import android.util.Rational;
24
25import 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 */
36public 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     */
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     */
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     */
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     */
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     */
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
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
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
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     */
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