DngCreator.java revision b8df8e07d6fc530c82d21ca3199411e2e60975b1
1/*
2 * Copyright 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;
18
19import android.graphics.Bitmap;
20import android.graphics.ImageFormat;
21import android.hardware.camera2.impl.CameraMetadataNative;
22import android.location.Location;
23import android.media.ExifInterface;
24import android.media.Image;
25import android.os.SystemClock;
26import android.util.Size;
27
28import java.io.IOException;
29import java.io.InputStream;
30import java.io.OutputStream;
31import java.nio.ByteBuffer;
32import java.text.DateFormat;
33import java.text.SimpleDateFormat;
34import java.util.TimeZone;
35
36/**
37 * The {@link DngCreator} class provides functions to write raw pixel data as a DNG file.
38 *
39 * <p>
40 * This class is designed to be used with the {@link android.graphics.ImageFormat#RAW_SENSOR}
41 * buffers available from {@link android.hardware.camera2.CameraDevice}, or with Bayer-type raw
42 * pixel data that is otherwise generated by an application.  The DNG metadata tags will be
43 * generated from a {@link android.hardware.camera2.CaptureResult} object or set directly.
44 * </p>
45 *
46 * <p>
47 * The DNG file format is a cross-platform file format that is used to store pixel data from
48 * camera sensors with minimal pre-processing applied.  DNG files allow for pixel data to be
49 * defined in a user-defined colorspace, and have associated metadata that allow for this
50 * pixel data to be converted to the standard CIE XYZ colorspace during post-processing.
51 * </p>
52 *
53 * <p>
54 * For more information on the DNG file format and associated metadata, please refer to the
55 * <a href=
56 * "https://wwwimages2.adobe.com/content/dam/Adobe/en/products/photoshop/pdfs/dng_spec_1.4.0.0.pdf">
57 * Adobe DNG 1.4.0.0 specification</a>.
58 * </p>
59 */
60public final class DngCreator implements AutoCloseable {
61
62    private static final String TAG = "DngCreator";
63    /**
64     * Create a new DNG object.
65     *
66     * <p>
67     * It is not necessary to call any set methods to write a well-formatted DNG file.
68     * </p>
69     * <p>
70     * DNG metadata tags will be generated from the corresponding parameters in the
71     * {@link android.hardware.camera2.CaptureResult} object.  This removes or overrides
72     * all previous tags set.
73     * </p>
74     *
75     * @param characteristics an object containing the static
76     *          {@link android.hardware.camera2.CameraCharacteristics}.
77     * @param metadata a metadata object to generate tags from.
78     */
79    public DngCreator(CameraCharacteristics characteristics, CaptureResult metadata) {
80        if (characteristics == null || metadata == null) {
81            throw new NullPointerException("Null argument to DngCreator constructor");
82        }
83
84        // Find current time
85        long currentTime = System.currentTimeMillis();
86
87        // Find boot time
88        long bootTimeMillis = currentTime - SystemClock.elapsedRealtime();
89
90        // Find capture time (nanos since boot)
91        Long timestamp = metadata.get(CaptureResult.SENSOR_TIMESTAMP);
92        long captureTime = currentTime;
93        if (timestamp != null) {
94            captureTime = timestamp / 1000000 + bootTimeMillis;
95        }
96
97        // Format for metadata
98        String formattedCaptureTime = sDateTimeStampFormat.format(captureTime);
99
100        nativeInit(characteristics.getNativeCopy(), metadata.getNativeCopy(),
101                formattedCaptureTime);
102    }
103
104    /**
105     * Set the orientation value to write.
106     *
107     * <p>
108     * This will be written as the TIFF "Orientation" tag {@code (0x0112)}.
109     * Calling this will override any prior settings for this tag.
110     * </p>
111     *
112     * @param orientation the orientation value to set, one of:
113     *                    <ul>
114     *                      <li>{@link android.media.ExifInterface#ORIENTATION_NORMAL}</li>
115     *                      <li>{@link android.media.ExifInterface#ORIENTATION_FLIP_HORIZONTAL}</li>
116     *                      <li>{@link android.media.ExifInterface#ORIENTATION_ROTATE_180}</li>
117     *                      <li>{@link android.media.ExifInterface#ORIENTATION_FLIP_VERTICAL}</li>
118     *                      <li>{@link android.media.ExifInterface#ORIENTATION_TRANSPOSE}</li>
119     *                      <li>{@link android.media.ExifInterface#ORIENTATION_ROTATE_90}</li>
120     *                      <li>{@link android.media.ExifInterface#ORIENTATION_TRANSVERSE}</li>
121     *                      <li>{@link android.media.ExifInterface#ORIENTATION_ROTATE_270}</li>
122     *                    </ul>
123     * @return this {@link #DngCreator} object.
124     */
125    public DngCreator setOrientation(int orientation) {
126
127        if (orientation < ExifInterface.ORIENTATION_UNDEFINED ||
128                orientation > ExifInterface.ORIENTATION_ROTATE_270) {
129            throw new IllegalArgumentException("Orientation " + orientation +
130                    " is not a valid EXIF orientation value");
131        }
132        nativeSetOrientation(orientation);
133        return this;
134    }
135
136    /**
137     * Set the thumbnail image.
138     *
139     * <p>
140     * Pixel data will be converted to a Baseline TIFF RGB image, with 8 bits per color channel.
141     * The alpha channel will be discarded.
142     * </p>
143     *
144     * <p>
145     * The given bitmap should not be altered while this object is in use.
146     * </p>
147     *
148     * @param pixels a {@link android.graphics.Bitmap} of pixel data.
149     * @return this {@link #DngCreator} object.
150     */
151    public DngCreator setThumbnail(Bitmap pixels) {
152        if (pixels == null) {
153            throw new NullPointerException("Null argument to setThumbnail");
154        }
155
156        Bitmap.Config config = pixels.getConfig();
157
158        if (config != Bitmap.Config.ARGB_8888) {
159            pixels = pixels.copy(Bitmap.Config.ARGB_8888, false);
160            if (pixels == null) {
161                throw new IllegalArgumentException("Unsupported Bitmap format " + config);
162            }
163            nativeSetThumbnailBitmap(pixels);
164        }
165
166        return this;
167    }
168
169    /**
170     * Set the thumbnail image.
171     *
172     * <p>
173     * Pixel data is interpreted as a {@link android.graphics.ImageFormat#YUV_420_888} image.
174     * </p>
175     *
176     * <p>
177     * The given image should not be altered while this object is in use.
178     * </p>
179     *
180     * @param pixels an {@link android.media.Image} object with the format
181     *               {@link android.graphics.ImageFormat#YUV_420_888}.
182     * @return this {@link #DngCreator} object.
183     */
184    public DngCreator setThumbnail(Image pixels) {
185        if (pixels == null) {
186            throw new NullPointerException("Null argument to setThumbnail");
187        }
188
189        int format = pixels.getFormat();
190        if (format != ImageFormat.YUV_420_888) {
191            throw new IllegalArgumentException("Unsupported image format " + format);
192        }
193
194        Image.Plane[] planes = pixels.getPlanes();
195        nativeSetThumbnailImage(pixels.getWidth(), pixels.getHeight(), planes[0].getBuffer(),
196                planes[0].getRowStride(), planes[0].getPixelStride(), planes[1].getBuffer(),
197                planes[1].getRowStride(), planes[1].getPixelStride(), planes[1].getBuffer(),
198                planes[1].getRowStride(), planes[1].getPixelStride());
199
200        return this;
201    }
202
203
204    /**
205     * Set image location metadata.
206     *
207     * <p>
208     * The given location object must contain at least a valid time, latitude, and longitude
209     * (equivalent to the values returned by {@link android.location.Location#getTime()},
210     * {@link android.location.Location#getLatitude()}, and
211     * {@link android.location.Location#getLongitude()} methods).
212     * </p>
213     *
214     * @param location an {@link android.location.Location} object to set.
215     * @return this {@link #DngCreator} object.
216     *
217     * @throws java.lang.IllegalArgumentException if the given location object doesn't
218     *          contain enough information to set location metadata.
219     */
220    public DngCreator setLocation(Location location) {
221        /*TODO*/
222        return this;
223    }
224
225    /**
226     * Set the user description string to write.
227     *
228     * <p>
229     * This is equivalent to setting the TIFF "ImageDescription" tag {@code (0x010E)}.
230     * </p>
231     *
232     * @param description the user description string.
233     * @return this {@link #DngCreator} object.
234     */
235    public DngCreator setDescription(String description) {
236        /*TODO*/
237        return this;
238    }
239
240    /**
241     * Write the {@link android.graphics.ImageFormat#RAW_SENSOR} pixel data to a DNG file with
242     * the currently configured metadata.
243     *
244     * <p>
245     * Raw pixel data must have 16 bits per pixel, and the input must contain at least
246     * {@code offset + 2 * width * height)} bytes.  The width and height of
247     * the input are taken from the width and height set in the {@link DngCreator} metadata tags,
248     * and will typically be equal to the width and height of
249     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE}.
250     * The pixel layout in the input is determined from the reported color filter arrangement (CFA)
251     * set in {@link CameraCharacteristics#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT}.  If insufficient
252     * metadata is available to write a well-formatted DNG file, an
253     * {@link java.lang.IllegalStateException} will be thrown.
254     * </p>
255     *
256     * @param dngOutput an {@link java.io.OutputStream} to write the DNG file to.
257     * @param size the {@link Size} of the image to write, in pixels.
258     * @param pixels an {@link java.io.InputStream} of pixel data to write.
259     * @param offset the offset of the raw image in bytes.  This indicates how many bytes will
260     *               be skipped in the input before any pixel data is read.
261     *
262     * @throws IOException if an error was encountered in the input or output stream.
263     * @throws java.lang.IllegalStateException if not enough metadata information has been
264     *          set to write a well-formatted DNG file.
265     * @throws java.lang.IllegalArgumentException if the size passed in does not match the
266     */
267    public void writeInputStream(OutputStream dngOutput, Size size, InputStream pixels, long offset)
268            throws IOException {
269        if (dngOutput == null || pixels == null) {
270            throw new NullPointerException("Null argument to writeImage");
271        }
272        nativeWriteInputStream(dngOutput, pixels, offset);
273    }
274
275    /**
276     * Write the {@link android.graphics.ImageFormat#RAW_SENSOR} pixel data to a DNG file with
277     * the currently configured metadata.
278     *
279     * <p>
280     * Raw pixel data must have 16 bits per pixel, and the input must contain at least
281     * {@code offset + 2 * width * height)} bytes.  The width and height of
282     * the input are taken from the width and height set in the {@link DngCreator} metadata tags,
283     * and will typically be equal to the width and height of
284     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE}.
285     * The pixel layout in the input is determined from the reported color filter arrangement (CFA)
286     * set in {@link CameraCharacteristics#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT}.  If insufficient
287     * metadata is available to write a well-formatted DNG file, an
288     * {@link java.lang.IllegalStateException} will be thrown.
289     * </p>
290     *
291     * @param dngOutput an {@link java.io.OutputStream} to write the DNG file to.
292     * @param size the {@link Size} of the image to write, in pixels.
293     * @param pixels an {@link java.nio.ByteBuffer} of pixel data to write.
294     * @param offset the offset of the raw image in bytes.  This indicates how many bytes will
295     *               be skipped in the input before any pixel data is read.
296     *
297     * @throws IOException if an error was encountered in the input or output stream.
298     * @throws java.lang.IllegalStateException if not enough metadata information has been
299     *          set to write a well-formatted DNG file.
300     */
301    public void writeByteBuffer(OutputStream dngOutput, Size size, ByteBuffer pixels, long offset)
302            throws IOException {
303        if (dngOutput == null || pixels == null) {
304            throw new NullPointerException("Null argument to writeImage");
305        }
306        nativeWriteByteBuffer(dngOutput, pixels, offset);
307    }
308
309    /**
310     * Write the pixel data to a DNG file with the currently configured metadata.
311     *
312     * <p>
313     * For this method to succeed, the {@link android.media.Image} input must contain
314     * {@link android.graphics.ImageFormat#RAW_SENSOR} pixel data, otherwise an
315     * {@link java.lang.IllegalArgumentException} will be thrown.
316     * </p>
317     *
318     * @param dngOutput an {@link java.io.OutputStream} to write the DNG file to.
319     * @param pixels an {@link android.media.Image} to write.
320     *
321     * @throws java.io.IOException if an error was encountered in the output stream.
322     * @throws java.lang.IllegalArgumentException if an image with an unsupported format was used.
323     * @throws java.lang.IllegalStateException if not enough metadata information has been
324     *          set to write a well-formatted DNG file.
325     */
326    public void writeImage(OutputStream dngOutput, Image pixels) throws IOException {
327        if (dngOutput == null || pixels == null) {
328            throw new NullPointerException("Null argument to writeImage");
329        }
330
331        int format = pixels.getFormat();
332        if (format != ImageFormat.RAW_SENSOR) {
333            throw new IllegalArgumentException("Unsupported image format " + format);
334        }
335
336        Image.Plane[] planes = pixels.getPlanes();
337        nativeWriteImage(dngOutput, pixels.getWidth(), pixels.getHeight(), planes[0].getBuffer(),
338                planes[0].getRowStride(), planes[0].getPixelStride());
339    }
340
341    @Override
342    public void close() {
343        nativeDestroy();
344    }
345
346    @Override
347    protected void finalize() throws Throwable {
348        try {
349            close();
350        } finally {
351            super.finalize();
352        }
353    }
354
355    private static final String TIFF_DATETIME_FORMAT = "yyyy:MM:dd kk:mm:ss";
356    private static final DateFormat sDateTimeStampFormat =
357            new SimpleDateFormat(TIFF_DATETIME_FORMAT);
358
359    static {
360        sDateTimeStampFormat.setTimeZone(TimeZone.getDefault());
361    }
362    /**
363     * This field is used by native code, do not access or modify.
364     */
365    private long mNativeContext;
366
367    private static native void nativeClassInit();
368
369    private synchronized native void nativeInit(CameraMetadataNative nativeCharacteristics,
370                                                CameraMetadataNative nativeResult,
371                                                String captureTime);
372
373    private synchronized native void nativeDestroy();
374
375    private synchronized native void nativeSetOrientation(int orientation);
376
377    private synchronized native void nativeSetThumbnailBitmap(Bitmap bitmap);
378
379    private synchronized native void nativeSetThumbnailImage(int width, int height,
380                                                             ByteBuffer yBuffer, int yRowStride,
381                                                             int yPixStride, ByteBuffer uBuffer,
382                                                             int uRowStride, int uPixStride,
383                                                             ByteBuffer vBuffer, int vRowStride,
384                                                             int vPixStride);
385
386    private synchronized native void nativeWriteImage(OutputStream out, int width, int height,
387                                                      ByteBuffer rawBuffer, int rowStride,
388                                                      int pixStride) throws IOException;
389
390    private synchronized native void nativeWriteByteBuffer(OutputStream out, ByteBuffer rawBuffer,
391                                                           long offset) throws IOException;
392
393    private synchronized native void nativeWriteInputStream(OutputStream out, InputStream rawStream,
394                                                            long offset) throws IOException;
395
396    static {
397        nativeClassInit();
398    }
399}
400