1 /* 2 * Copyright (C) 2021 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 package androidx.camera.extensions.impl.advanced; 18 19 import android.annotation.SuppressLint; 20 import android.content.Context; 21 import android.graphics.ImageFormat; 22 import android.media.Image; 23 import android.media.Image.Plane; 24 import android.media.ImageWriter; 25 import android.util.Log; 26 27 import java.nio.ByteBuffer; 28 29 // Jpeg compress input YUV and queue back in the client target surface. 30 public class JpegEncoder { 31 32 public final static int JPEG_DEFAULT_QUALITY = 100; 33 public final static int JPEG_DEFAULT_ROTATION = 0; 34 public static final int HAL_PIXEL_FORMAT_BLOB = 0x21; 35 36 /** 37 * Compresses a YCbCr image to jpeg, applying a crop and rotation. 38 * <p> 39 * The input is defined as a set of 3 planes of 8-bit samples, one plane for 40 * each channel of Y, Cb, Cr.<br> 41 * The Y plane is assumed to have the same width and height of the entire 42 * image.<br> 43 * The Cb and Cr planes are assumed to be downsampled by a factor of 2, to 44 * have dimensions (floor(width / 2), floor(height / 2)).<br> 45 * Each plane is specified by a direct java.nio.ByteBuffer, a pixel-stride, 46 * and a row-stride. So, the sample at coordinate (x, y) can be retrieved 47 * from byteBuffer[x * pixel_stride + y * row_stride]. 48 * <p> 49 * The pre-compression transformation is applied as follows: 50 * <ol> 51 * <li>The image is cropped to the rectangle from (cropLeft, cropTop) to 52 * (cropRight - 1, cropBottom - 1). So, a cropping-rectangle of (0, 0) - 53 * (width, height) is a no-op.</li> 54 * <li>The rotation is applied counter-clockwise relative to the coordinate 55 * space of the image, so a CCW rotation will appear CW when the image is 56 * rendered in scanline order. Only rotations which are multiples of 57 * 90-degrees are suppored, so the parameter 'rot90' specifies which 58 * multiple of 90 to rotate the image.</li> 59 * </ol> 60 * 61 * @param width the width of the image to compress 62 * @param height the height of the image to compress 63 * @param yBuf the buffer containing the Y component of the image 64 * @param yPStride the stride between adjacent pixels in the same row in 65 * yBuf 66 * @param yRStride the stride between adjacent rows in yBuf 67 * @param cbBuf the buffer containing the Cb component of the image 68 * @param cbPStride the stride between adjacent pixels in the same row in 69 * cbBuf 70 * @param cbRStride the stride between adjacent rows in cbBuf 71 * @param crBuf the buffer containing the Cr component of the image 72 * @param crPStride the stride between adjacent pixels in the same row in 73 * crBuf 74 * @param crRStride the stride between adjacent rows in crBuf 75 * @param outBuf a direct java.nio.ByteBuffer to hold the compressed jpeg. 76 * This must have enough capacity to store the result, or an 77 * error code will be returned. 78 * @param outBufCapacity the capacity of outBuf 79 * @param quality the jpeg-quality (1-100) to use 80 * @param cropLeft left-edge of the bounds of the image to crop to before 81 * rotation 82 * @param cropTop top-edge of the bounds of the image to crop to before 83 * rotation 84 * @param cropRight right-edge of the bounds of the image to crop to before 85 * rotation 86 * @param cropBottom bottom-edge of the bounds of the image to crop to 87 * before rotation 88 * @param rot90 the multiple of 90 to rotate the image CCW (after cropping) 89 */ compressJpegFromYUV420pNative( int width, int height, ByteBuffer yBuf, int yPStride, int yRStride, ByteBuffer cbBuf, int cbPStride, int cbRStride, ByteBuffer crBuf, int crPStride, int crRStride, ByteBuffer outBuf, int outBufCapacity, int quality, int cropLeft, int cropTop, int cropRight, int cropBottom, int rot90)90 public static native int compressJpegFromYUV420pNative( 91 int width, int height, 92 ByteBuffer yBuf, int yPStride, int yRStride, 93 ByteBuffer cbBuf, int cbPStride, int cbRStride, 94 ByteBuffer crBuf, int crPStride, int crRStride, 95 ByteBuffer outBuf, int outBufCapacity, 96 int quality, 97 int cropLeft, int cropTop, int cropRight, int cropBottom, 98 int rot90); 99 encodeToJpeg(Image yuvImage, Image jpegImage, int jpegOrientation, int jpegQuality)100 public static void encodeToJpeg(Image yuvImage, Image jpegImage, 101 int jpegOrientation, int jpegQuality) { 102 103 jpegOrientation = (360 - (jpegOrientation % 360)) / 90; 104 ByteBuffer jpegBuffer = jpegImage.getPlanes()[0].getBuffer(); 105 106 jpegBuffer.clear(); 107 108 int jpegCapacity = jpegImage.getWidth(); 109 110 Plane lumaPlane = yuvImage.getPlanes()[0]; 111 112 Plane crPlane = yuvImage.getPlanes()[1]; 113 Plane cbPlane = yuvImage.getPlanes()[2]; 114 115 JpegEncoder.compressJpegFromYUV420pNative( 116 yuvImage.getWidth(), yuvImage.getHeight(), 117 lumaPlane.getBuffer(), lumaPlane.getPixelStride(), lumaPlane.getRowStride(), 118 crPlane.getBuffer(), crPlane.getPixelStride(), crPlane.getRowStride(), 119 cbPlane.getBuffer(), cbPlane.getPixelStride(), cbPlane.getRowStride(), 120 jpegBuffer, jpegCapacity, jpegQuality, 121 0, 0, yuvImage.getWidth(), yuvImage.getHeight(), 122 jpegOrientation); 123 } 124 imageFormatToPublic(int format)125 public static int imageFormatToPublic(int format) { 126 switch (format) { 127 case HAL_PIXEL_FORMAT_BLOB: 128 return ImageFormat.JPEG; 129 case ImageFormat.JPEG: 130 throw new IllegalArgumentException( 131 "ImageFormat.JPEG is an unknown internal format"); 132 default: 133 return format; 134 } 135 } 136 }