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 com.android.camera.processing.imagebackend;
18
19import android.graphics.Bitmap;
20import android.graphics.BitmapFactory;
21import android.graphics.ImageFormat;
22import android.graphics.Rect;
23import android.graphics.YuvImage;
24import android.net.Uri;
25
26import com.android.camera.debug.Log;
27import com.android.camera.one.v2.camera2proxy.ImageProxy;
28import com.android.camera.session.CaptureSession;
29
30import java.io.ByteArrayOutputStream;
31import java.io.IOException;
32import java.nio.ByteBuffer;
33import java.util.List;
34import java.util.concurrent.Executor;
35
36/**
37 * TaskJpegEncode are the base class of tasks that wish to do JPEG
38 * encoding/decoding. Various helper functions are held in this class.
39 */
40public abstract class TaskJpegEncode extends TaskImageContainer {
41
42    protected final static Log.Tag TAG = new Log.Tag("TaskJpegEnc");
43
44    /**
45     * Constructor to use for NOT passing the image reference forward.
46     *
47     * @param otherTask Parent task that is spawning this task
48     * @param processingPriority Preferred processing priority for this task
49     */
50    public TaskJpegEncode(TaskImageContainer otherTask, ProcessingPriority processingPriority) {
51        super(otherTask, processingPriority);
52    }
53
54    /**
55     * Constructor to use for initial task definition or complex shared state
56     * sharing.
57     *
58     * @param image Image reference that is required for computation
59     * @param executor Executor to avoid thread control leakage
60     * @param imageTaskManager ImageBackend associated with
61     * @param preferredLane Preferred processing priority for this task
62     * @param captureSession Session associated for UI handling
63     */
64    public TaskJpegEncode(ImageToProcess image, Executor executor,
65            ImageTaskManager imageTaskManager,
66            TaskImageContainer.ProcessingPriority preferredLane, CaptureSession captureSession) {
67        super(image, executor, imageTaskManager, preferredLane, captureSession);
68    }
69
70    /**
71     * Converts the YUV420_888 Image into a packed NV21 of a single byte array,
72     * suitable for JPEG compression by the method convertNv21toJpeg. This
73     * version will allocate its own byte buffer memory.
74     *
75     * @param img image to be converted
76     * @return byte array of NV21 packed image
77     */
78    public byte[] convertYUV420ImageToPackedNV21(ImageProxy img) {
79        final List<ImageProxy.Plane> planeList = img.getPlanes();
80
81        ByteBuffer y_buffer = planeList.get(0).getBuffer();
82        ByteBuffer u_buffer = planeList.get(1).getBuffer();
83        ByteBuffer v_buffer = planeList.get(2).getBuffer();
84        byte[] dataCopy = new byte[y_buffer.capacity() + u_buffer.capacity() + v_buffer.capacity()];
85
86        return convertYUV420ImageToPackedNV21(img, dataCopy);
87    }
88
89    /**
90     * Converts the YUV420_888 Image into a packed NV21 of a single byte array,
91     * suitable for JPEG compression by the method convertNv21toJpeg. Creates a
92     * memory block with the y component at the head and interleaves the u,v
93     * components following the y component. Caller is responsible to allocate a
94     * large enough buffer for results.
95     *
96     * @param img image to be converted
97     * @param dataCopy buffer to write NV21 packed image
98     * @return byte array of NV21 packed image
99     */
100    public byte[] convertYUV420ImageToPackedNV21(ImageProxy img, byte[] dataCopy) {
101        // Get all the relevant information and then release the image.
102        final int w = img.getWidth();
103        final int h = img.getHeight();
104        final List<ImageProxy.Plane> planeList = img.getPlanes();
105
106        ByteBuffer y_buffer = planeList.get(0).getBuffer();
107        ByteBuffer u_buffer = planeList.get(1).getBuffer();
108        ByteBuffer v_buffer = planeList.get(2).getBuffer();
109        final int color_pixel_stride = planeList.get(1).getPixelStride();
110        final int y_size = y_buffer.capacity();
111        final int u_size = u_buffer.capacity();
112        final int data_offset = w * h;
113
114        for (int i = 0; i < y_size; i++) {
115            dataCopy[i] = (byte) (y_buffer.get(i) & 255);
116        }
117
118        for (int i = 0; i < u_size / color_pixel_stride; i++) {
119            dataCopy[data_offset + 2 * i] = v_buffer.get(i * color_pixel_stride);
120            dataCopy[data_offset + 2 * i + 1] = u_buffer.get(i * color_pixel_stride);
121        }
122
123        return dataCopy;
124    }
125
126    /**
127     * Creates a dummy shaded image for testing in packed NV21 format.
128     *
129     * @param dataCopy Buffer to contained shaded test image
130     * @param w Width of image
131     * @param h Height of Image
132     */
133    public void dummyConvertYUV420ImageToPackedNV21(byte[] dataCopy,
134            final int w, final int h) {
135        final int y_size = w * h;
136        final int data_offset = w * h;
137
138        for (int i = 0; i < y_size; i++) {
139            dataCopy[i] = (byte) ((((i % w) * 255) / w) & 255);
140            dataCopy[i] = 0;
141        }
142
143        for (int i = 0; i < h / 2; i++) {
144            for (int j = 0; j < w / 2; j++) {
145                int offset = data_offset + w * i + j * 2;
146                dataCopy[offset] = (byte) ((255 * i) / (h / 2) & 255);
147                dataCopy[offset + 1] = (byte) ((255 * j) / (w / 2) & 255);
148            }
149        }
150    }
151
152    /**
153     * Wraps the Android built-in YUV to Jpeg conversion routine. Pass in a
154     * valid NV21 image and get back a compressed JPEG buffer. A good default
155     * JPEG compression implementation that should be supported on all
156     * platforms.
157     *
158     * @param data_copy byte buffer that contains the NV21 image
159     * @param w width of NV21 image
160     * @param h height of N21 image
161     * @return byte array of compressed JPEG image
162     */
163    public byte[] convertNv21toJpeg(byte[] data_copy, int w, int h, int[] strides) {
164        Log.e(TAG, "TIMER_BEGIN NV21 to Jpeg Conversion.");
165        YuvImage yuvImage = new YuvImage(data_copy, ImageFormat.NV21, w, h, strides);
166
167        ByteArrayOutputStream postViewBytes = new ByteArrayOutputStream();
168
169        yuvImage.compressToJpeg(new Rect(0, 0, w, h), 90, postViewBytes);
170        try {
171            postViewBytes.flush();
172        } catch (IOException e) {
173            e.printStackTrace();
174        }
175
176        Log.e(TAG, "TIMER_END NV21 to Jpeg Conversion.");
177        return postViewBytes.toByteArray();
178    }
179
180    /**
181     * Implement cropping through the decompression and re-compression of the JPEG using
182     * the built-in Android bitmap utilities.
183     *
184     * @param jpegData Compressed Image to be cropped
185     * @param crop Crop to be applied
186     * @param recompressionQuality Recompression quality value for cropped JPEG Image
187     * @return JPEG compressed byte array representing the cropped image
188     */
189    public byte[] decompressCropAndRecompressJpegData(final byte[] jpegData, Rect crop,
190            int recompressionQuality) {
191        Bitmap original = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length);
192
193        final Bitmap croppedResult = Bitmap.createBitmap(original, crop.left, crop.top,
194                crop.width(), crop.height());;
195
196        ByteArrayOutputStream stream = new ByteArrayOutputStream();
197
198        croppedResult.compress(Bitmap.CompressFormat.JPEG, recompressionQuality, stream);
199        return stream.toByteArray();
200    }
201
202    /**
203     * Wraps the onResultCompressed listener for ease of use.
204     *
205     * @param id Unique content id
206     * @param input Specification of image input size
207     * @param result Specification of resultant input size
208     * @param data Container for uncompressed data that represents image
209     */
210    public void onJpegEncodeDone(long id, TaskImage input, TaskImage result, byte[] data,
211            TaskInfo.Destination aDestination) {
212        TaskInfo job = new TaskInfo(id, input, result, aDestination);
213        final ImageProcessorListener listener = mImageTaskManager.getProxyListener();
214        listener.onResultCompressed(job, new CompressedPayload(data));
215    }
216
217    /**
218     * Wraps the onResultUri listener for ease of use.
219     *
220     * @param id Unique content id
221     * @param input Specification of image input size
222     * @param result Specification of resultant input size
223     * @param imageUri URI of the saved image.
224     * @param destination Specifies the purpose of the image artifact
225     */
226    public void onUriResolved(long id, TaskImage input, TaskImage result, final Uri imageUri,
227            TaskInfo.Destination destination) {
228        final TaskInfo job = new TaskInfo(id, input, result, destination);
229        final ImageProcessorListener listener = mImageTaskManager.getProxyListener();
230        listener.onResultUri(job, imageUri);
231    }
232}
233