1 /* Copyright 2015 The TensorFlow Authors. All Rights Reserved. 2 3 Licensed under the Apache License, Version 2.0 (the "License"); 4 you may not use this file except in compliance with the License. 5 You may obtain a copy of the License at 6 7 http://www.apache.org/licenses/LICENSE-2.0 8 9 Unless required by applicable law or agreed to in writing, software 10 distributed under the License is distributed on an "AS IS" BASIS, 11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 See the License for the specific language governing permissions and 13 limitations under the License. 14 ==============================================================================*/ 15 16 package org.tensorflow.demo.env; 17 18 import android.graphics.Bitmap; 19 import android.graphics.Matrix; 20 import android.os.Environment; 21 import java.io.File; 22 import java.io.FileOutputStream; 23 24 /** 25 * Utility class for manipulating images. 26 **/ 27 public class ImageUtils { 28 @SuppressWarnings("unused") 29 private static final Logger LOGGER = new Logger(); 30 31 static { 32 try { 33 System.loadLibrary("tensorflow_demo"); 34 } catch (UnsatisfiedLinkError e) { 35 LOGGER.w("Native library not found, native RGB -> YUV conversion may be unavailable."); 36 } 37 } 38 39 /** 40 * Utility method to compute the allocated size in bytes of a YUV420SP image 41 * of the given dimensions. 42 */ getYUVByteSize(final int width, final int height)43 public static int getYUVByteSize(final int width, final int height) { 44 // The luminance plane requires 1 byte per pixel. 45 final int ySize = width * height; 46 47 // The UV plane works on 2x2 blocks, so dimensions with odd size must be rounded up. 48 // Each 2x2 block takes 2 bytes to encode, one each for U and V. 49 final int uvSize = ((width + 1) / 2) * ((height + 1) / 2) * 2; 50 51 return ySize + uvSize; 52 } 53 54 /** 55 * Saves a Bitmap object to disk for analysis. 56 * 57 * @param bitmap The bitmap to save. 58 */ saveBitmap(final Bitmap bitmap)59 public static void saveBitmap(final Bitmap bitmap) { 60 saveBitmap(bitmap, "preview.png"); 61 } 62 63 /** 64 * Saves a Bitmap object to disk for analysis. 65 * 66 * @param bitmap The bitmap to save. 67 * @param filename The location to save the bitmap to. 68 */ saveBitmap(final Bitmap bitmap, final String filename)69 public static void saveBitmap(final Bitmap bitmap, final String filename) { 70 final String root = 71 Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "tensorflow"; 72 LOGGER.i("Saving %dx%d bitmap to %s.", bitmap.getWidth(), bitmap.getHeight(), root); 73 final File myDir = new File(root); 74 75 if (!myDir.mkdirs()) { 76 LOGGER.i("Make dir failed"); 77 } 78 79 final String fname = filename; 80 final File file = new File(myDir, fname); 81 if (file.exists()) { 82 file.delete(); 83 } 84 try { 85 final FileOutputStream out = new FileOutputStream(file); 86 bitmap.compress(Bitmap.CompressFormat.PNG, 99, out); 87 out.flush(); 88 out.close(); 89 } catch (final Exception e) { 90 LOGGER.e(e, "Exception!"); 91 } 92 } 93 94 // This value is 2 ^ 18 - 1, and is used to clamp the RGB values before their ranges 95 // are normalized to eight bits. 96 static final int kMaxChannelValue = 262143; 97 98 // Always prefer the native implementation if available. 99 private static boolean useNativeConversion = true; 100 convertYUV420SPToARGB8888( byte[] input, int width, int height, int[] output)101 public static void convertYUV420SPToARGB8888( 102 byte[] input, 103 int width, 104 int height, 105 int[] output) { 106 if (useNativeConversion) { 107 try { 108 ImageUtils.convertYUV420SPToARGB8888(input, output, width, height, false); 109 return; 110 } catch (UnsatisfiedLinkError e) { 111 LOGGER.w( 112 "Native YUV420SP -> RGB implementation not found, falling back to Java implementation"); 113 useNativeConversion = false; 114 } 115 } 116 117 // Java implementation of YUV420SP to ARGB8888 converting 118 final int frameSize = width * height; 119 for (int j = 0, yp = 0; j < height; j++) { 120 int uvp = frameSize + (j >> 1) * width; 121 int u = 0; 122 int v = 0; 123 124 for (int i = 0; i < width; i++, yp++) { 125 int y = 0xff & input[yp]; 126 if ((i & 1) == 0) { 127 v = 0xff & input[uvp++]; 128 u = 0xff & input[uvp++]; 129 } 130 131 output[yp] = YUV2RGB(y, u, v); 132 } 133 } 134 } 135 YUV2RGB(int y, int u, int v)136 private static int YUV2RGB(int y, int u, int v) { 137 // Adjust and check YUV values 138 y = (y - 16) < 0 ? 0 : (y - 16); 139 u -= 128; 140 v -= 128; 141 142 // This is the floating point equivalent. We do the conversion in integer 143 // because some Android devices do not have floating point in hardware. 144 // nR = (int)(1.164 * nY + 2.018 * nU); 145 // nG = (int)(1.164 * nY - 0.813 * nV - 0.391 * nU); 146 // nB = (int)(1.164 * nY + 1.596 * nV); 147 int y1192 = 1192 * y; 148 int r = (y1192 + 1634 * v); 149 int g = (y1192 - 833 * v - 400 * u); 150 int b = (y1192 + 2066 * u); 151 152 // Clipping RGB values to be inside boundaries [ 0 , kMaxChannelValue ] 153 r = r > kMaxChannelValue ? kMaxChannelValue : (r < 0 ? 0 : r); 154 g = g > kMaxChannelValue ? kMaxChannelValue : (g < 0 ? 0 : g); 155 b = b > kMaxChannelValue ? kMaxChannelValue : (b < 0 ? 0 : b); 156 157 return 0xff000000 | ((r << 6) & 0xff0000) | ((g >> 2) & 0xff00) | ((b >> 10) & 0xff); 158 } 159 160 convertYUV420ToARGB8888( byte[] yData, byte[] uData, byte[] vData, int width, int height, int yRowStride, int uvRowStride, int uvPixelStride, int[] out)161 public static void convertYUV420ToARGB8888( 162 byte[] yData, 163 byte[] uData, 164 byte[] vData, 165 int width, 166 int height, 167 int yRowStride, 168 int uvRowStride, 169 int uvPixelStride, 170 int[] out) { 171 if (useNativeConversion) { 172 try { 173 convertYUV420ToARGB8888( 174 yData, uData, vData, out, width, height, yRowStride, uvRowStride, uvPixelStride, false); 175 return; 176 } catch (UnsatisfiedLinkError e) { 177 LOGGER.w( 178 "Native YUV420 -> RGB implementation not found, falling back to Java implementation"); 179 useNativeConversion = false; 180 } 181 } 182 183 int yp = 0; 184 for (int j = 0; j < height; j++) { 185 int pY = yRowStride * j; 186 int pUV = uvRowStride * (j >> 1); 187 188 for (int i = 0; i < width; i++) { 189 int uv_offset = pUV + (i >> 1) * uvPixelStride; 190 191 out[yp++] = YUV2RGB( 192 0xff & yData[pY + i], 193 0xff & uData[uv_offset], 194 0xff & vData[uv_offset]); 195 } 196 } 197 } 198 199 200 /** 201 * Converts YUV420 semi-planar data to ARGB 8888 data using the supplied width and height. The 202 * input and output must already be allocated and non-null. For efficiency, no error checking is 203 * performed. 204 * 205 * @param input The array of YUV 4:2:0 input data. 206 * @param output A pre-allocated array for the ARGB 8:8:8:8 output data. 207 * @param width The width of the input image. 208 * @param height The height of the input image. 209 * @param halfSize If true, downsample to 50% in each dimension, otherwise not. 210 */ convertYUV420SPToARGB8888( byte[] input, int[] output, int width, int height, boolean halfSize)211 private static native void convertYUV420SPToARGB8888( 212 byte[] input, int[] output, int width, int height, boolean halfSize); 213 214 /** 215 * Converts YUV420 semi-planar data to ARGB 8888 data using the supplied width 216 * and height. The input and output must already be allocated and non-null. 217 * For efficiency, no error checking is performed. 218 * 219 * @param y 220 * @param u 221 * @param v 222 * @param uvPixelStride 223 * @param width The width of the input image. 224 * @param height The height of the input image. 225 * @param halfSize If true, downsample to 50% in each dimension, otherwise not. 226 * @param output A pre-allocated array for the ARGB 8:8:8:8 output data. 227 */ convertYUV420ToARGB8888( byte[] y, byte[] u, byte[] v, int[] output, int width, int height, int yRowStride, int uvRowStride, int uvPixelStride, boolean halfSize)228 private static native void convertYUV420ToARGB8888( 229 byte[] y, 230 byte[] u, 231 byte[] v, 232 int[] output, 233 int width, 234 int height, 235 int yRowStride, 236 int uvRowStride, 237 int uvPixelStride, 238 boolean halfSize); 239 240 /** 241 * Converts YUV420 semi-planar data to RGB 565 data using the supplied width 242 * and height. The input and output must already be allocated and non-null. 243 * For efficiency, no error checking is performed. 244 * 245 * @param input The array of YUV 4:2:0 input data. 246 * @param output A pre-allocated array for the RGB 5:6:5 output data. 247 * @param width The width of the input image. 248 * @param height The height of the input image. 249 */ convertYUV420SPToRGB565( byte[] input, byte[] output, int width, int height)250 private static native void convertYUV420SPToRGB565( 251 byte[] input, byte[] output, int width, int height); 252 253 /** 254 * Converts 32-bit ARGB8888 image data to YUV420SP data. This is useful, for 255 * instance, in creating data to feed the classes that rely on raw camera 256 * preview frames. 257 * 258 * @param input An array of input pixels in ARGB8888 format. 259 * @param output A pre-allocated array for the YUV420SP output data. 260 * @param width The width of the input image. 261 * @param height The height of the input image. 262 */ convertARGB8888ToYUV420SP( int[] input, byte[] output, int width, int height)263 private static native void convertARGB8888ToYUV420SP( 264 int[] input, byte[] output, int width, int height); 265 266 /** 267 * Converts 16-bit RGB565 image data to YUV420SP data. This is useful, for 268 * instance, in creating data to feed the classes that rely on raw camera 269 * preview frames. 270 * 271 * @param input An array of input pixels in RGB565 format. 272 * @param output A pre-allocated array for the YUV420SP output data. 273 * @param width The width of the input image. 274 * @param height The height of the input image. 275 */ convertRGB565ToYUV420SP( byte[] input, byte[] output, int width, int height)276 private static native void convertRGB565ToYUV420SP( 277 byte[] input, byte[] output, int width, int height); 278 279 /** 280 * Returns a transformation matrix from one reference frame into another. 281 * Handles cropping (if maintaining aspect ratio is desired) and rotation. 282 * 283 * @param srcWidth Width of source frame. 284 * @param srcHeight Height of source frame. 285 * @param dstWidth Width of destination frame. 286 * @param dstHeight Height of destination frame. 287 * @param applyRotation Amount of rotation to apply from one frame to another. 288 * Must be a multiple of 90. 289 * @param maintainAspectRatio If true, will ensure that scaling in x and y remains constant, 290 * cropping the image if necessary. 291 * @return The transformation fulfilling the desired requirements. 292 */ getTransformationMatrix( final int srcWidth, final int srcHeight, final int dstWidth, final int dstHeight, final int applyRotation, final boolean maintainAspectRatio)293 public static Matrix getTransformationMatrix( 294 final int srcWidth, 295 final int srcHeight, 296 final int dstWidth, 297 final int dstHeight, 298 final int applyRotation, 299 final boolean maintainAspectRatio) { 300 final Matrix matrix = new Matrix(); 301 302 if (applyRotation != 0) { 303 if (applyRotation % 90 != 0) { 304 LOGGER.w("Rotation of %d % 90 != 0", applyRotation); 305 } 306 307 // Translate so center of image is at origin. 308 matrix.postTranslate(-srcWidth / 2.0f, -srcHeight / 2.0f); 309 310 // Rotate around origin. 311 matrix.postRotate(applyRotation); 312 } 313 314 // Account for the already applied rotation, if any, and then determine how 315 // much scaling is needed for each axis. 316 final boolean transpose = (Math.abs(applyRotation) + 90) % 180 == 0; 317 318 final int inWidth = transpose ? srcHeight : srcWidth; 319 final int inHeight = transpose ? srcWidth : srcHeight; 320 321 // Apply scaling if necessary. 322 if (inWidth != dstWidth || inHeight != dstHeight) { 323 final float scaleFactorX = dstWidth / (float) inWidth; 324 final float scaleFactorY = dstHeight / (float) inHeight; 325 326 if (maintainAspectRatio) { 327 // Scale by minimum factor so that dst is filled completely while 328 // maintaining the aspect ratio. Some image may fall off the edge. 329 final float scaleFactor = Math.max(scaleFactorX, scaleFactorY); 330 matrix.postScale(scaleFactor, scaleFactor); 331 } else { 332 // Scale exactly to fill dst from src. 333 matrix.postScale(scaleFactorX, scaleFactorY); 334 } 335 } 336 337 if (applyRotation != 0) { 338 // Translate back from origin centered reference to destination frame. 339 matrix.postTranslate(dstWidth / 2.0f, dstHeight / 2.0f); 340 } 341 342 return matrix; 343 } 344 } 345