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.CameraCharacteristics;
22import android.hardware.camera2.utils.HashCodeHelpers;
23
24import java.util.Arrays;
25
26/**
27 * Immutable class to store the input to output formats
28 * {@link CameraCharacteristics#SCALER_AVAILABLE_INPUT_OUTPUT_FORMATS_MAP map} to be used for with
29 * camera image reprocessing.
30 *
31 * <p>
32 * The mapping of image formats that are supported by this camera device for input streams,
33 * to their corresponding output formats.</p>
34 *
35 * <p>
36 * Attempting to configure an input stream with output streams not listed as available in this map
37 * is not valid.
38 * </p>
39 *
40 * @see CameraCharacteristics#SCALER_AVAILABLE_INPUT_OUTPUT_FORMATS_MAP
41 * @see CameraCharacteristics#SCALER_AVAILABLE_STREAM_CONFIGURATIONS
42 *
43 * <!-- hide this until we expose input streams through public API -->
44 * @hide
45 */
46public final class ReprocessFormatsMap {
47    /**
48     * Create a new {@link ReprocessFormatsMap}
49     *
50     * <p>This value is encoded as a variable-size array-of-arrays.
51     * The inner array always contains {@code [format, length, ...]} where ... has length elements.
52     * An inner array is followed by another inner array if the total metadata entry size hasn't
53     * yet been exceeded.</p>
54     *
55     * <p>Entry must not be {@code null}. Empty array is acceptable.</p>
56     *
57     * <p>The entry array ownership is passed to this instance after construction; do not
58     * write to it afterwards.</p>
59     *
60     * @param entry Array of ints, not yet deserialized (not-null)
61     *
62     * @throws IllegalArgumentException
63     *              if the data was poorly formatted
64     *              (missing output format length or too few output formats)
65     *              or if any of the input/formats were not valid
66     * @throws NullPointerException
67     *              if entry was null
68     *
69     * @see StreamConfigurationMap#checkArgumentFormatInternal
70     *
71     * @hide
72     */
73    public ReprocessFormatsMap(final int[] entry) {
74        checkNotNull(entry, "entry must not be null");
75
76        int numInputs = 0;
77        int left = entry.length;
78        for (int i = 0; i < entry.length; ) {
79            int inputFormat = StreamConfigurationMap.checkArgumentFormatInternal(entry[i]);
80
81            left--;
82            i++;
83
84            if (left < 1) {
85                throw new IllegalArgumentException(
86                        String.format("Input %x had no output format length listed", inputFormat));
87            }
88
89            final int length = entry[i];
90            left--;
91            i++;
92
93            for (int j = 0; j < length; ++j) {
94                int outputFormat = entry[i + j];
95                StreamConfigurationMap.checkArgumentFormatInternal(outputFormat);
96            }
97
98            if (length > 0) {
99                if (left < length) {
100                    throw new IllegalArgumentException(
101                            String.format(
102                                    "Input %x had too few output formats listed (actual: %d, " +
103                                    "expected: %d)", inputFormat, left, length));
104                }
105
106                i += length;
107                left -= length;
108            }
109
110            numInputs++;
111        }
112
113        mEntry = entry;
114        mInputCount = numInputs;
115    }
116
117    /**
118     * Get a list of all input image formats that can be used to reprocess an input
119     * stream into an output stream.
120     *
121     * <p>Use this input format to look up the available output formats with {@link #getOutputs}.
122     * </p>
123     *
124     * @return an array of inputs (possibly empty, but never {@code null})
125     *
126     * @see ImageFormat
127     * @see #getOutputs
128     */
129    public int[] getInputs() {
130        final int[] inputs = new int[mInputCount];
131
132        int left = mEntry.length;
133        for (int i = 0, j = 0; i < mEntry.length; j++) {
134            final int format = mEntry[i];
135
136            left--;
137            i++;
138
139            if (left < 1) {
140                throw new AssertionError(
141                        String.format("Input %x had no output format length listed", format));
142            }
143
144            final int length = mEntry[i];
145            left--;
146            i++;
147
148            if (length > 0) {
149                if (left < length) {
150                    throw new AssertionError(
151                            String.format(
152                                    "Input %x had too few output formats listed (actual: %d, " +
153                                    "expected: %d)", format, left, length));
154                }
155
156                i += length;
157                left -= length;
158            }
159
160            inputs[j] = format;
161        }
162
163        return StreamConfigurationMap.imageFormatToPublic(inputs);
164    }
165
166    /**
167     * Get the list of output formats that can be reprocessed into from the input {@code format}.
168     *
169     * <p>The input {@code format} must be one of the formats returned by {@link #getInputs}.</p>
170     *
171     * @param format an input format
172     *
173     * @return list of output image formats
174     *
175     * @see ImageFormat
176     * @see #getInputs
177     */
178    public int[] getOutputs(final int format) {
179
180        int left = mEntry.length;
181        for (int i = 0; i < mEntry.length; ) {
182            final int inputFormat = mEntry[i];
183
184            left--;
185            i++;
186
187            if (left < 1) {
188                throw new AssertionError(
189                        String.format("Input %x had no output format length listed", format));
190            }
191
192            final int length = mEntry[i];
193            left--;
194            i++;
195
196            if (length > 0) {
197                if (left < length) {
198                    throw new AssertionError(
199                            String.format(
200                                    "Input %x had too few output formats listed (actual: %d, " +
201                                    "expected: %d)", format, left, length));
202                }
203            }
204
205            if (inputFormat == format) {
206                int[] outputs = new int[length];
207
208                // Copying manually faster than System.arraycopy for small arrays
209                for (int k = 0; k < length; ++k) {
210                    outputs[k] = mEntry[i + k];
211                }
212
213                return StreamConfigurationMap.imageFormatToPublic(outputs);
214            }
215
216            i += length;
217            left -= length;
218
219        }
220
221        throw new IllegalArgumentException(
222                String.format("Input format %x was not one in #getInputs", format));
223    }
224
225    /**
226     * Check if this {@link ReprocessFormatsMap} is equal to another
227     * {@link ReprocessFormatsMap}.
228     *
229     * <p>These two objects are only equal if and only if each of the respective elements is equal.
230     * </p>
231     *
232     * @return {@code true} if the objects were equal, {@code false} otherwise
233     */
234    @Override
235    public boolean equals(final Object obj) {
236        if (obj == null) {
237            return false;
238        }
239        if (this == obj) {
240            return true;
241        }
242        if (obj instanceof ReprocessFormatsMap) {
243            final ReprocessFormatsMap other = (ReprocessFormatsMap) obj;
244            // Do not compare anything besides mEntry, since the rest of the values are derived
245            return Arrays.equals(mEntry, other.mEntry);
246        }
247        return false;
248    }
249
250    /**
251     * {@inheritDoc}
252     */
253    @Override
254    public int hashCode() {
255        // Do not hash anything besides mEntry since the rest of the values are derived
256        return HashCodeHelpers.hashCode(mEntry);
257    }
258
259    private final int[] mEntry;
260    /*
261     * Dependent fields: values are derived from mEntry
262     */
263    private final int mInputCount;
264}
265