1/*
2 * Copyright (C) 2011 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
18package android.filterfw.core;
19
20import android.filterfw.core.KeyValueMap;
21import android.filterfw.core.MutableFrameFormat;
22
23import java.util.Arrays;
24import java.util.Map.Entry;
25
26/**
27 * @hide
28 */
29public class FrameFormat {
30
31    public static final int TYPE_UNSPECIFIED = 0;
32    public static final int TYPE_BIT         = 1;
33    public static final int TYPE_BYTE        = 2;
34    public static final int TYPE_INT16       = 3;
35    public static final int TYPE_INT32       = 4;
36    public static final int TYPE_FLOAT       = 5;
37    public static final int TYPE_DOUBLE      = 6;
38    public static final int TYPE_POINTER     = 7;
39    public static final int TYPE_OBJECT      = 8;
40
41    public static final int TARGET_UNSPECIFIED  = 0;
42    public static final int TARGET_SIMPLE       = 1;
43    public static final int TARGET_NATIVE       = 2;
44    public static final int TARGET_GPU          = 3;
45    public static final int TARGET_VERTEXBUFFER = 4;
46    public static final int TARGET_RS           = 5;
47
48    public static final int SIZE_UNSPECIFIED = 0;
49
50    // TODO: When convenience formats are used, consider changing this to 0 and have the convenience
51    // intializers use a proper BPS.
52    public static final int BYTES_PER_SAMPLE_UNSPECIFIED = 1;
53
54    protected static final int SIZE_UNKNOWN = -1;
55
56    protected int mBaseType = TYPE_UNSPECIFIED;
57    protected int mBytesPerSample = 1;
58    protected int mSize = SIZE_UNKNOWN;
59    protected int mTarget = TARGET_UNSPECIFIED;
60    protected int[] mDimensions;
61    protected KeyValueMap mMetaData;
62    protected Class mObjectClass;
63
64    protected FrameFormat() {
65    }
66
67    public FrameFormat(int baseType, int target) {
68        mBaseType = baseType;
69        mTarget = target;
70        initDefaults();
71    }
72
73    public static FrameFormat unspecified() {
74        return new FrameFormat(TYPE_UNSPECIFIED, TARGET_UNSPECIFIED);
75    }
76
77    public int getBaseType() {
78        return mBaseType;
79    }
80
81    public boolean isBinaryDataType() {
82        return mBaseType >= TYPE_BIT && mBaseType <= TYPE_DOUBLE;
83    }
84
85    public int getBytesPerSample() {
86        return mBytesPerSample;
87    }
88
89    public int getValuesPerSample() {
90        return mBytesPerSample / bytesPerSampleOf(mBaseType);
91    }
92
93    public int getTarget() {
94        return mTarget;
95    }
96
97    public int[] getDimensions() {
98        return mDimensions;
99    }
100
101    public int getDimension(int i) {
102        return mDimensions[i];
103    }
104
105    public int getDimensionCount() {
106        return mDimensions == null ? 0 : mDimensions.length;
107    }
108
109    public boolean hasMetaKey(String key) {
110        return mMetaData != null ? mMetaData.containsKey(key) : false;
111    }
112
113    public boolean hasMetaKey(String key, Class expectedClass) {
114        if (mMetaData != null && mMetaData.containsKey(key)) {
115            if (!expectedClass.isAssignableFrom(mMetaData.get(key).getClass())) {
116                throw new RuntimeException(
117                    "FrameFormat meta-key '" + key + "' is of type " +
118                    mMetaData.get(key).getClass() + " but expected to be of type " +
119                    expectedClass + "!");
120            }
121            return true;
122        }
123        return false;
124    }
125
126    public Object getMetaValue(String key) {
127        return mMetaData != null ? mMetaData.get(key) : null;
128    }
129
130    public int getNumberOfDimensions() {
131        return mDimensions != null ? mDimensions.length : 0;
132    }
133
134    public int getLength() {
135        return (mDimensions != null && mDimensions.length >= 1) ? mDimensions[0] : -1;
136    }
137
138    public int getWidth() {
139        return getLength();
140    }
141
142    public int getHeight() {
143        return (mDimensions != null && mDimensions.length >= 2) ? mDimensions[1] : -1;
144    }
145
146    public int getDepth() {
147        return (mDimensions != null && mDimensions.length >= 3) ? mDimensions[2] : -1;
148    }
149
150    public int getSize() {
151        if (mSize == SIZE_UNKNOWN) mSize = calcSize(mDimensions);
152        return mSize;
153    }
154
155    public Class getObjectClass() {
156        return mObjectClass;
157    }
158
159    public MutableFrameFormat mutableCopy() {
160        MutableFrameFormat result = new MutableFrameFormat();
161        result.setBaseType(getBaseType());
162        result.setTarget(getTarget());
163        result.setBytesPerSample(getBytesPerSample());
164        result.setDimensions(getDimensions());
165        result.setObjectClass(getObjectClass());
166        result.mMetaData = mMetaData == null ? null : (KeyValueMap)mMetaData.clone();
167        return result;
168    }
169
170    @Override
171    public boolean equals(Object object) {
172        if (this == object) {
173            return true;
174        }
175
176        if (!(object instanceof FrameFormat)) {
177            return false;
178        }
179
180        FrameFormat format = (FrameFormat)object;
181        return format.mBaseType == mBaseType &&
182                format.mTarget == mTarget &&
183                format.mBytesPerSample == mBytesPerSample &&
184                Arrays.equals(format.mDimensions, mDimensions) &&
185                format.mMetaData.equals(mMetaData);
186    }
187
188    @Override
189    public int hashCode() {
190        return 4211 ^ mBaseType ^ mBytesPerSample ^ getSize();
191    }
192
193    public boolean isCompatibleWith(FrameFormat specification) {
194        // Check base type
195        if (specification.getBaseType() != TYPE_UNSPECIFIED
196            && getBaseType() != specification.getBaseType()) {
197            return false;
198        }
199
200        // Check target
201        if (specification.getTarget() != TARGET_UNSPECIFIED
202            && getTarget() != specification.getTarget()) {
203            return false;
204        }
205
206        // Check bytes per sample
207        if (specification.getBytesPerSample() != BYTES_PER_SAMPLE_UNSPECIFIED
208            && getBytesPerSample() != specification.getBytesPerSample()) {
209            return false;
210        }
211
212        // Check number of dimensions
213        if (specification.getDimensionCount() > 0
214            && getDimensionCount() != specification.getDimensionCount()) {
215            return false;
216        }
217
218        // Check dimensions
219        for (int i = 0; i < specification.getDimensionCount(); ++i) {
220            int specDim = specification.getDimension(i);
221            if (specDim != SIZE_UNSPECIFIED && getDimension(i) != specDim) {
222                return false;
223            }
224        }
225
226        // Check class
227        if (specification.getObjectClass() != null) {
228            if (getObjectClass() == null
229                || !specification.getObjectClass().isAssignableFrom(getObjectClass())) {
230                return false;
231            }
232        }
233
234        // Check meta-data
235        if (specification.mMetaData != null) {
236            for (String specKey : specification.mMetaData.keySet()) {
237                if (mMetaData == null
238                || !mMetaData.containsKey(specKey)
239                || !mMetaData.get(specKey).equals(specification.mMetaData.get(specKey))) {
240                    return false;
241                }
242            }
243        }
244
245        // Passed all the tests
246        return true;
247    }
248
249    public boolean mayBeCompatibleWith(FrameFormat specification) {
250        // Check base type
251        if (specification.getBaseType() != TYPE_UNSPECIFIED
252            && getBaseType() != TYPE_UNSPECIFIED
253            && getBaseType() != specification.getBaseType()) {
254            return false;
255        }
256
257        // Check target
258        if (specification.getTarget() != TARGET_UNSPECIFIED
259            && getTarget() != TARGET_UNSPECIFIED
260            && getTarget() != specification.getTarget()) {
261            return false;
262        }
263
264        // Check bytes per sample
265        if (specification.getBytesPerSample() != BYTES_PER_SAMPLE_UNSPECIFIED
266            && getBytesPerSample() != BYTES_PER_SAMPLE_UNSPECIFIED
267            && getBytesPerSample() != specification.getBytesPerSample()) {
268            return false;
269        }
270
271        // Check number of dimensions
272        if (specification.getDimensionCount() > 0
273            && getDimensionCount() > 0
274            && getDimensionCount() != specification.getDimensionCount()) {
275            return false;
276        }
277
278        // Check dimensions
279        for (int i = 0; i < specification.getDimensionCount(); ++i) {
280            int specDim = specification.getDimension(i);
281            if (specDim != SIZE_UNSPECIFIED
282                && getDimension(i) != SIZE_UNSPECIFIED
283                && getDimension(i) != specDim) {
284                return false;
285            }
286        }
287
288        // Check class
289        if (specification.getObjectClass() != null && getObjectClass() != null) {
290            if (!specification.getObjectClass().isAssignableFrom(getObjectClass())) {
291                return false;
292            }
293        }
294
295        // Check meta-data
296        if (specification.mMetaData != null && mMetaData != null) {
297            for (String specKey : specification.mMetaData.keySet()) {
298                if (mMetaData.containsKey(specKey)
299                    && !mMetaData.get(specKey).equals(specification.mMetaData.get(specKey))) {
300                    return false;
301                }
302            }
303        }
304
305        // Passed all the tests
306        return true;
307    }
308
309    public static int bytesPerSampleOf(int baseType) {
310        // Defaults based on base-type
311        switch (baseType) {
312            case TYPE_BIT:
313            case TYPE_BYTE:
314                return 1;
315            case TYPE_INT16:
316                return 2;
317            case TYPE_INT32:
318            case TYPE_FLOAT:
319            case TYPE_POINTER:
320                return 4;
321            case TYPE_DOUBLE:
322                return 8;
323            default:
324                return 1;
325        }
326    }
327
328    public static String dimensionsToString(int[] dimensions) {
329        StringBuffer buffer = new StringBuffer();
330        if (dimensions != null) {
331            int n = dimensions.length;
332            for (int i = 0; i < n; ++i) {
333                if (dimensions[i] == SIZE_UNSPECIFIED) {
334                    buffer.append("[]");
335                } else {
336                    buffer.append("[" + String.valueOf(dimensions[i]) + "]");
337                }
338            }
339        }
340        return buffer.toString();
341    }
342
343    public static String baseTypeToString(int baseType) {
344        switch (baseType) {
345            case TYPE_UNSPECIFIED: return "unspecified";
346            case TYPE_BIT:         return "bit";
347            case TYPE_BYTE:        return "byte";
348            case TYPE_INT16:       return "int";
349            case TYPE_INT32:       return "int";
350            case TYPE_FLOAT:       return "float";
351            case TYPE_DOUBLE:      return "double";
352            case TYPE_POINTER:     return "pointer";
353            case TYPE_OBJECT:      return "object";
354            default:               return "unknown";
355        }
356    }
357
358    public static String targetToString(int target) {
359        switch (target) {
360            case TARGET_UNSPECIFIED:  return "unspecified";
361            case TARGET_SIMPLE:       return "simple";
362            case TARGET_NATIVE:       return "native";
363            case TARGET_GPU:          return "gpu";
364            case TARGET_VERTEXBUFFER: return "vbo";
365            case TARGET_RS:           return "renderscript";
366            default:                  return "unknown";
367        }
368    }
369
370    public static String metaDataToString(KeyValueMap metaData) {
371        if (metaData == null) {
372            return "";
373        } else {
374            StringBuffer buffer = new StringBuffer();
375            buffer.append("{ ");
376            for (Entry<String, Object> entry : metaData.entrySet()) {
377                buffer.append(entry.getKey() + ": " + entry.getValue() + " ");
378            }
379            buffer.append("}");
380            return buffer.toString();
381        }
382    }
383
384    public static int readTargetString(String targetString) {
385        if (targetString.equalsIgnoreCase("CPU") || targetString.equalsIgnoreCase("NATIVE")) {
386            return FrameFormat.TARGET_NATIVE;
387        } else if (targetString.equalsIgnoreCase("GPU")) {
388            return FrameFormat.TARGET_GPU;
389        } else if (targetString.equalsIgnoreCase("SIMPLE")) {
390            return FrameFormat.TARGET_SIMPLE;
391        } else if (targetString.equalsIgnoreCase("VERTEXBUFFER")) {
392            return FrameFormat.TARGET_VERTEXBUFFER;
393        } else if (targetString.equalsIgnoreCase("UNSPECIFIED")) {
394            return FrameFormat.TARGET_UNSPECIFIED;
395        } else {
396            throw new RuntimeException("Unknown target type '" + targetString + "'!");
397        }
398    }
399
400    // TODO: FromString
401
402    public String toString() {
403        int valuesPerSample = getValuesPerSample();
404        String sampleCountString = valuesPerSample == 1 ? "" : String.valueOf(valuesPerSample);
405        String targetString = mTarget == TARGET_UNSPECIFIED ? "" : (targetToString(mTarget) + " ");
406        String classString = mObjectClass == null
407            ? ""
408            : (" class(" + mObjectClass.getSimpleName() + ") ");
409
410        return targetString
411            + baseTypeToString(mBaseType)
412            + sampleCountString
413            + dimensionsToString(mDimensions)
414            + classString
415            + metaDataToString(mMetaData);
416    }
417
418    private void initDefaults() {
419        mBytesPerSample = bytesPerSampleOf(mBaseType);
420    }
421
422    // Core internal methods ///////////////////////////////////////////////////////////////////////
423    int calcSize(int[] dimensions) {
424        if (dimensions != null && dimensions.length > 0) {
425            int size = getBytesPerSample();
426            for (int dim : dimensions) {
427                size *= dim;
428            }
429            return size;
430        }
431        return 0;
432    }
433
434    boolean isReplaceableBy(FrameFormat format) {
435        return mTarget == format.mTarget
436            && getSize() == format.getSize()
437            && Arrays.equals(format.mDimensions, mDimensions);
438    }
439}
440