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 17 package com.android.camera.processing.imagebackend; 18 19 import android.graphics.Rect; 20 import com.android.camera.debug.Log; 21 import com.android.camera.one.v2.camera2proxy.ImageProxy; 22 import com.android.camera.session.CaptureSession; 23 import com.android.camera.util.Size; 24 25 import java.nio.ByteBuffer; 26 import java.util.List; 27 import java.util.concurrent.Executor; 28 29 /** 30 * Implements the conversion of a YUV_420_888 image to subsampled image targeted 31 * toward a given resolution. The task automatically calculates the largest 32 * integer sub-sample factor that is greater than the target resolution. There 33 * are four different thumbnail types: 34 * <ol> 35 * <li>DEBUG_SQUARE_ASPECT_CIRCULAR_INSET: a center-weighted circularly cropped 36 * gradient image</li> 37 * <li>SQUARE_ASPECT_CIRCULAR_INSET: a center-weighted circularly cropped 38 * sub-sampled image</li> 39 * <li>SQUARE_ASPECT_NO_INSET: a center-weighted square cropped sub-sampled 40 * image</li> 41 * <li>MAINTAIN_ASPECT_NO_INSET: a sub-sampled image without cropping (except to 42 * maintain even values of width and height for the image</li> 43 * </ol> 44 * This task does NOT implement rotation at the byte-level, since it is best 45 * implemented when displayed at the view level. 46 */ 47 public class TaskConvertImageToRGBPreview extends TaskImageContainer { 48 public enum ThumbnailShape { 49 DEBUG_SQUARE_ASPECT_CIRCULAR_INSET, 50 SQUARE_ASPECT_CIRCULAR_INSET, 51 SQUARE_ASPECT_NO_INSET, 52 MAINTAIN_ASPECT_NO_INSET, 53 } 54 55 // 24 bit-vector to be written for images that are out of bounds. 56 public final static int OUT_OF_BOUNDS_COLOR = 0x00000000; 57 58 /** 59 * Quick n' Dirty YUV to RGB conversion 60 * <ol> 61 * <li>R = Y + 1.402V'</li> 62 * <li>G = Y - 0.344U'- 0.714V'</li> 63 * <li>B = Y + 1.770U'</li> 64 * </ol> 65 * to be calculated at compile time. 66 */ 67 public final static int SHIFT_APPROXIMATION = 8; 68 public final static double SHIFTED_BITS_AS_VALUE = (double) (1 << SHIFT_APPROXIMATION); 69 public final static int V_FACTOR_FOR_R = (int) (1.402 * SHIFTED_BITS_AS_VALUE); 70 public final static int U_FACTOR_FOR_G = (int) (-0.344 * SHIFTED_BITS_AS_VALUE); 71 public final static int V_FACTOR_FOR_G = (int) (-0.714 * SHIFTED_BITS_AS_VALUE); 72 public final static int U_FACTOR_FOR_B = (int) (1.772 * SHIFTED_BITS_AS_VALUE); 73 74 protected final static Log.Tag TAG = new Log.Tag("TaskRGBPreview"); 75 76 protected final ThumbnailShape mThumbnailShape; 77 protected final Size mTargetSize; 78 79 /** 80 * Constructor 81 * 82 * @param image Image that the computation is dependent on 83 * @param executor Executor to fire off an events 84 * @param imageTaskManager Image task manager that allows reference counting 85 * and task spawning 86 * @param captureSession Capture session that bound to this image 87 * @param targetSize Approximate viewable pixel dimensions of the desired 88 * preview Image (Resultant image may NOT be of this width) 89 * @param thumbnailShape the desired thumbnail shape for resultant image 90 * artifact 91 */ 92 TaskConvertImageToRGBPreview(ImageToProcess image, Executor executor, 93 ImageTaskManager imageTaskManager, ProcessingPriority processingPriority, 94 CaptureSession captureSession, Size targetSize, ThumbnailShape thumbnailShape) { 95 super(image, executor, imageTaskManager, processingPriority, captureSession); 96 mTargetSize = targetSize; 97 mThumbnailShape = thumbnailShape; 98 } 99 100 public void logWrapper(String message) { 101 Log.v(TAG, message); 102 } 103 104 /** 105 * Return the closest minimal value of the parameter that is evenly divisible by two. 106 */ 107 private static int quantizeBy2(int value) { 108 return (value / 2) * 2; 109 } 110 111 /** 112 * Way to calculate the resultant image sizes of inscribed circles: 113 * colorInscribedDataCircleFromYuvImage, 114 * dummyColorInscribedDataCircleFromYuvImage, colorDataCircleFromYuvImage 115 * 116 * @param height height of the input image 117 * @param width width of the input image 118 * @return height/width of the resultant square image TODO: Refactor 119 * functions in question to return the image size as a tuple for 120 * these functions, or re-use an general purpose holder object. 121 */ 122 protected int inscribedCircleRadius(int width, int height) { 123 return (Math.min(height, width) / 2) + 1; 124 } 125 126 /** 127 * Calculates the best integer subsample from a given height and width to a 128 * target width and height It is assumed that the exact scaling will be done 129 * with the Android Bitmap framework; this subsample value is to best 130 * convert raw images into the lowest resolution raw images in visually 131 * lossless manner without changing the aspect ratio or creating subsample 132 * artifacts. 133 * 134 * @param imageSize Dimensions of the original image 135 * @param targetSize Target dimensions of the resultant image 136 * @return inscribed image as ARGB_8888 137 */ 138 protected int calculateBestSubsampleFactor(Size imageSize, Size targetSize) { 139 int maxSubsample = Math.min(imageSize.getWidth() / targetSize.getWidth(), 140 imageSize.getHeight() / targetSize.getHeight()); 141 if (maxSubsample < 1) { 142 return 1; 143 } 144 145 // Make sure the resultant image width/height is divisible by 2 to 146 // account 147 // for chroma subsampled images such as YUV 148 for (int i = maxSubsample; i >= 1; i--) { 149 if (((imageSize.getWidth() % (2 * i) == 0) 150 && (imageSize.getHeight() % (2 * i) == 0))) { 151 return i; 152 } 153 } 154 155 return 1; // If all fails, don't do the subsample. 156 } 157 158 /** 159 * Calculates the memory offset of a YUV 420 plane, given the parameters of 160 * the separate YUV color planes and the fact that UV components may be 161 * subsampled by a factor of 2. 162 * 163 * @param inscribedXMin X location that you want to start sampling on the 164 * input image in terms of input pixels 165 * @param inscribedYMin Y location that you want to start sampling on the 166 * input image in terms of input pixels 167 * @param subsample Subsample factor applied to the input image 168 * @param colorSubsample Color subsample due to the YUV color space (In YUV, 169 * it's 1 for Y, 2 for UV) 170 * @param rowStride Row Stride of the color plane in terms of bytes 171 * @param pixelStride Pixel Stride of the color plane in terms of bytes 172 * @param inputHorizontalOffset Horizontal Input Offset for sampling that 173 * you wish to add in terms of input pixels 174 * @param inputVerticalOffset Vertical Input Offset for sampling that you 175 * wish to add in terms of input pixels 176 * @return value of the corresponding memory offset. 177 */ 178 protected static int calculateMemoryOffsetFromPixelOffsets(int inscribedXMin, 179 int inscribedYMin, int subsample, int colorSubsample, 180 int rowStride, int pixelStride, int inputHorizontalOffset, int inputVerticalOffset) { 181 return inputVerticalOffset * (rowStride / subsample) 182 + inputHorizontalOffset * (pixelStride / subsample) 183 + (inscribedYMin / colorSubsample) * rowStride 184 + (inscribedXMin / colorSubsample) * pixelStride; 185 } 186 187 /** 188 * Converts an Android Image to a inscribed circle bitmap of ARGB_8888 in a 189 * super-optimized loop unroll. Guarantees only one subsampled pass over the 190 * YUV data. This version of the function should be used in production and 191 * also feathers the edges with 50% alpha on its edges. <br> 192 * NOTE: To get the size of the resultant bitmap, you need to call 193 * inscribedCircleRadius(w, h) outside of this function. Runs in ~10-15ms 194 * for 4K image with a subsample of 13. <br> 195 * <p> 196 * <b>Crop Treatment: </b>Since this class does a lot of memory offset 197 * calculation, it is critical that it doesn't poke strange memory locations on 198 * strange crop values. Crop is always applied before any rotation. Out-of-bound 199 * crop boundaries are accepted, but treated mathematically as intersection with 200 * the Image rectangle. If this intersection is null, the result is minimal 2x2 201 * images. 202 * <p> 203 * <b>Known Image Artifacts</b> Since this class produces bitmaps that are 204 * transient on the screen, the implementation is geared toward efficiency 205 * rather than image quality. The image created is a straight, dumb integer 206 * subsample of the YUV space with an acceptable color conversion, but w/o any 207 * sort of re-sampling. So, expect the usual aliasing noise. Furthermore, when a 208 * subsample factor of n is chosen, the resultant UV pixels will have the same 209 * subsampling, even though the RGBA artifact produces could produce an 210 * effective resample of (n/2) in the U,V color space. For cases where subsample 211 * is odd-valued, there will be pixel-to-pixel color bleeding, which may be 212 * apparent in sharp color edges. But since our eyes are pretty bad at color 213 * edges anyway, it may be an acceptable trade-off for run-time efficiency on an 214 * image artifact that has a short lifetime on the screen. 215 * </p> 216 * TODO: Implement horizontal alpha feathering of the edge of the image. 217 * 218 * @param img YUV420_888 Image to convert 219 * @param subsample width/height subsample factor 220 * @return inscribed image as ARGB_8888 221 */ 222 protected int[] colorInscribedDataCircleFromYuvImage(ImageProxy img, int subsample) { 223 Rect defaultCrop = new Rect(0, 0, img.getWidth(), img.getHeight()); 224 225 return colorInscribedDataCircleFromYuvImage(img, defaultCrop, subsample); 226 } 227 228 protected int[] colorInscribedDataCircleFromYuvImage(ImageProxy img, Rect crop, int subsample) { 229 crop = guaranteedSafeCrop(img, crop); 230 final List<ImageProxy.Plane> planeList = img.getPlanes(); 231 if (planeList.size() != 3) { 232 throw new IllegalArgumentException("Incorrect number planes (" + planeList.size() 233 + ") in YUV Image Object"); 234 } 235 236 int inputWidth = crop.width(); 237 int inputHeight = crop.height(); 238 int outputWidth = inputWidth / subsample; 239 int outputHeight = inputHeight / subsample; 240 int w = outputWidth; 241 int h = outputHeight; 242 int r = inscribedCircleRadius(w, h); 243 244 final int inscribedXMin; 245 final int inscribedXMax; 246 final int inscribedYMin; 247 final int inscribedYMax; 248 // To minimize color bleeding, always quantize the start coordinates by 2. 249 final int inputVerticalOffset = quantizeBy2(crop.top); 250 final int inputHorizontalOffset = quantizeBy2(crop.left); 251 252 // Set up input read boundaries. 253 if (w > h) { 254 inscribedYMin = 0; 255 inscribedYMax = h; 256 // since we're 2x2 blocks we need to quantize these values by 2 257 inscribedXMin = quantizeBy2(w / 2 - r); 258 inscribedXMax = quantizeBy2(w / 2 + r); 259 } else { 260 inscribedXMin = 0; 261 inscribedXMax = w; 262 // since we're 2x2 blocks we need to quantize these values by 2 263 inscribedYMin = quantizeBy2(h / 2 - r); 264 inscribedYMax = quantizeBy2(h / 2 + r); 265 } 266 267 ByteBuffer buf0 = planeList.get(0).getBuffer(); 268 ByteBuffer bufU = planeList.get(1).getBuffer(); // Downsampled by 2 269 ByteBuffer bufV = planeList.get(2).getBuffer(); // Downsampled by 2 270 int yByteStride = planeList.get(0).getRowStride() * subsample; 271 int uByteStride = planeList.get(1).getRowStride() * subsample; 272 int vByteStride = planeList.get(2).getRowStride() * subsample; 273 int yPixelStride = planeList.get(0).getPixelStride() * subsample; 274 int uPixelStride = planeList.get(1).getPixelStride() * subsample; 275 int vPixelStride = planeList.get(2).getPixelStride() * subsample; 276 int outputPixelStride = r * 2; 277 int centerY = h / 2; 278 int centerX = w / 2; 279 280 int len = r * r * 4; 281 int[] colors = new int[len]; 282 int alpha = 255 << 24; 283 284 logWrapper("TIMER_BEGIN Starting Native Java YUV420-to-RGB Circular Conversion"); 285 logWrapper("\t Y-Plane Size=" + w + "x" + h); 286 logWrapper("\t U-Plane Size=" + planeList.get(1).getRowStride() + " Pixel Stride=" 287 + planeList.get(1).getPixelStride()); 288 logWrapper("\t V-Plane Size=" + planeList.get(2).getRowStride() + " Pixel Stride=" 289 + planeList.get(2).getPixelStride()); 290 // Take in vertical lines by factor of two because of the u/v component 291 // subsample 292 for (int j = inscribedYMin; j < inscribedYMax; j += 2) { 293 int offsetColor = (j - inscribedYMin) * (outputPixelStride); 294 int offsetY = calculateMemoryOffsetFromPixelOffsets(inscribedXMin, j, subsample, 295 1 /* YComponent */, yByteStride, yPixelStride, inputHorizontalOffset, 296 inputVerticalOffset); 297 int offsetU = calculateMemoryOffsetFromPixelOffsets(inscribedXMin, j, subsample, 298 2 /* U Component downsampled by 2 */, uByteStride, uPixelStride, 299 inputHorizontalOffset / 2, inputVerticalOffset / 2); 300 int offsetV = calculateMemoryOffsetFromPixelOffsets(inscribedXMin, j, subsample, 301 2 /* v Component downsampled by 2 */, vByteStride, vPixelStride, 302 inputHorizontalOffset / 2, inputVerticalOffset / 2); 303 304 // Parametrize the circle boundaries w.r.t. the y component. 305 // Find the subsequence of pixels we need for each horizontal raster 306 // line. 307 int circleHalfWidth0 = 308 (int) (Math.sqrt((float) (r * r - (j - centerY) * (j - centerY))) + 0.5f); 309 int circleMin0 = centerX - (circleHalfWidth0); 310 int circleMax0 = centerX + circleHalfWidth0; 311 int circleHalfWidth1 = (int) (Math.sqrt((float) (r * r - (j + 1 - centerY) 312 * (j + 1 - centerY))) + 0.5f); 313 int circleMin1 = centerX - (circleHalfWidth1); 314 int circleMax1 = centerX + circleHalfWidth1; 315 316 // Take in horizontal lines by factor of two because of the u/v 317 // component subsample 318 // and everything as 2x2 blocks. 319 for (int i = inscribedXMin; i < inscribedXMax; i += 2, offsetY += 2 * yPixelStride, 320 offsetColor += 2, offsetU += uPixelStride, offsetV += vPixelStride) { 321 // Note i and j are in terms of pixels of the subsampled image 322 // offsetY, offsetU, and offsetV are in terms of bytes of the 323 // image 324 // offsetColor, output_pixel stride are in terms of the packed 325 // output image 326 if ((i > circleMax0 && i > circleMax1) || (i + 1 < circleMin0 && i < circleMin1)) { 327 colors[offsetColor] = OUT_OF_BOUNDS_COLOR; 328 colors[offsetColor + 1] = OUT_OF_BOUNDS_COLOR; 329 colors[offsetColor + outputPixelStride] = OUT_OF_BOUNDS_COLOR; 330 colors[offsetColor + outputPixelStride + 1] = OUT_OF_BOUNDS_COLOR; 331 continue; 332 } 333 334 // calculate the RGB component of the u/v channels and use it 335 // for all pixels in the 2x2 block 336 int u = (int) (bufU.get(offsetU) & 255) - 128; 337 int v = (int) (bufV.get(offsetV) & 255) - 128; 338 int redDiff = (v * V_FACTOR_FOR_R) >> SHIFT_APPROXIMATION; 339 int greenDiff = 340 ((u * U_FACTOR_FOR_G + v * V_FACTOR_FOR_G) >> SHIFT_APPROXIMATION); 341 int blueDiff = (u * U_FACTOR_FOR_B) >> SHIFT_APPROXIMATION; 342 343 if (i > circleMax0 || i < circleMin0) { 344 colors[offsetColor] = OUT_OF_BOUNDS_COLOR; 345 } else { 346 // Do a little alpha feathering on the edges 347 int alpha00 = (i == circleMax0 || i == circleMin0) ? (128 << 24) : (255 << 24); 348 349 int y00 = (int) (buf0.get(offsetY) & 255); 350 351 int green00 = y00 + greenDiff; 352 int blue00 = y00 + blueDiff; 353 int red00 = y00 + redDiff; 354 355 // Get the railing correct 356 if (green00 < 0) { 357 green00 = 0; 358 } 359 if (red00 < 0) { 360 red00 = 0; 361 } 362 if (blue00 < 0) { 363 blue00 = 0; 364 } 365 366 if (green00 > 255) { 367 green00 = 255; 368 } 369 if (red00 > 255) { 370 red00 = 255; 371 } 372 if (blue00 > 255) { 373 blue00 = 255; 374 } 375 376 colors[offsetColor] = (red00 & 255) << 16 | (green00 & 255) << 8 377 | (blue00 & 255) | alpha00; 378 } 379 380 if (i + 1 > circleMax0 || i + 1 < circleMin0) { 381 colors[offsetColor + 1] = OUT_OF_BOUNDS_COLOR; 382 } else { 383 int alpha01 = ((i + 1) == circleMax0 || (i + 1) == circleMin0) ? (128 << 24) 384 : (255 << 24); 385 int y01 = (int) (buf0.get(offsetY + yPixelStride) & 255); 386 int green01 = y01 + greenDiff; 387 int blue01 = y01 + blueDiff; 388 int red01 = y01 + redDiff; 389 390 // Get the railing correct 391 if (green01 < 0) { 392 green01 = 0; 393 } 394 if (red01 < 0) { 395 red01 = 0; 396 } 397 if (blue01 < 0) { 398 blue01 = 0; 399 } 400 401 if (green01 > 255) { 402 green01 = 255; 403 } 404 if (red01 > 255) { 405 red01 = 255; 406 } 407 if (blue01 > 255) { 408 blue01 = 255; 409 } 410 colors[offsetColor + 1] = (red01 & 255) << 16 | (green01 & 255) << 8 411 | (blue01 & 255) | alpha01; 412 } 413 414 if (i > circleMax1 || i < circleMin1) { 415 colors[offsetColor + outputPixelStride] = OUT_OF_BOUNDS_COLOR; 416 } else { 417 int alpha10 = (i == circleMax1 || i == circleMin1) ? (128 << 24) : (255 << 24); 418 int y10 = (int) (buf0.get(offsetY + yByteStride) & 255); 419 int green10 = y10 + greenDiff; 420 int blue10 = y10 + blueDiff; 421 int red10 = y10 + redDiff; 422 423 // Get the railing correct 424 if (green10 < 0) { 425 green10 = 0; 426 } 427 if (red10 < 0) { 428 red10 = 0; 429 } 430 if (blue10 < 0) { 431 blue10 = 0; 432 } 433 if (green10 > 255) { 434 green10 = 255; 435 } 436 if (red10 > 255) { 437 red10 = 255; 438 } 439 if (blue10 > 255) { 440 blue10 = 255; 441 } 442 443 colors[offsetColor + outputPixelStride] = (red10 & 255) << 16 444 | (green10 & 255) << 8 | (blue10 & 255) | alpha10; 445 } 446 447 if (i + 1 > circleMax1 || i + 1 < circleMin1) { 448 colors[offsetColor + outputPixelStride + 1] = OUT_OF_BOUNDS_COLOR; 449 } else { 450 int alpha11 = ((i + 1) == circleMax1 || (i + 1) == circleMin1) ? (128 << 24) 451 : (255 << 24); 452 int y11 = (int) (buf0.get(offsetY + yByteStride + yPixelStride) & 255); 453 int green11 = y11 + greenDiff; 454 int blue11 = y11 + blueDiff; 455 int red11 = y11 + redDiff; 456 457 // Get the railing correct 458 if (green11 < 0) { 459 green11 = 0; 460 } 461 if (red11 < 0) { 462 red11 = 0; 463 } 464 if (blue11 < 0) { 465 blue11 = 0; 466 } 467 468 if (green11 > 255) { 469 green11 = 255; 470 } 471 472 if (red11 > 255) { 473 red11 = 255; 474 } 475 if (blue11 > 255) { 476 blue11 = 255; 477 } 478 colors[offsetColor + outputPixelStride + 1] = (red11 & 255) << 16 479 | (green11 & 255) << 8 | (blue11 & 255) | alpha11; 480 } 481 482 } 483 } 484 logWrapper("TIMER_END Starting Native Java YUV420-to-RGB Circular Conversion"); 485 486 return colors; 487 } 488 489 /** 490 * Converts an Android Image to a subsampled image of ARGB_8888 data in a 491 * super-optimized loop unroll. Guarantees only one subsampled pass over the 492 * YUV data. No crop is applied. 493 * 494 * @param img YUV420_888 Image to convert 495 * @param subsample width/height subsample factor 496 * @param enableSquareInscribe true, output is an cropped square output; 497 * false, output maintains aspect ratio of input image 498 * @return inscribed image as ARGB_8888 499 */ 500 protected int[] colorSubSampleFromYuvImage(ImageProxy img, int subsample, 501 boolean enableSquareInscribe) { 502 Rect defaultCrop = new Rect(0, 0, img.getWidth(), img.getHeight()); 503 504 return colorSubSampleFromYuvImage(img, defaultCrop, subsample, enableSquareInscribe); 505 } 506 507 /** 508 * Converts an Android Image to a subsampled image of ARGB_8888 data in a 509 * super-optimized loop unroll. Guarantees only one subsampled pass over the 510 * YUV data. 511 * <p> 512 * <b>Crop Treatment: </b>Since this class does a lot of memory offset 513 * calculation, it is critical that it doesn't poke strange memory locations on 514 * strange crop values. Crop is always applied before any rotation. Out-of-bound 515 * crop boundaries are accepted, but treated mathematically as intersection with 516 * the Image rectangle. If this intersection is null, the result is minimal 2x2 517 * images. 518 * <p> 519 * <b>Known Image Artifacts</b> Since this class produces bitmaps that are 520 * transient on the screen, the implementation is geared toward efficiency 521 * rather than image quality. The image created is a straight, dumb integer 522 * subsample of the YUV space with an acceptable color conversion, but w/o any 523 * sort of re-sampling. So, expect the usual aliasing noise. Furthermore, when a 524 * subsample factor of n is chosen, the resultant UV pixels will have the same 525 * subsampling, even though the RGBA artifact produces could produce an 526 * effective resample of (n/2) in the U,V color space. For cases where subsample 527 * is odd-valued, there will be pixel-to-pixel color bleeding, which may be 528 * apparent in sharp color edges. But since our eyes are pretty bad at color 529 * edges anyway, it may be an acceptable trade-off for run-time efficiency on an 530 * image artifact that has a short lifetime on the screen. 531 * </p> 532 * 533 * @param img YUV420_888 Image to convert 534 * @param crop crop to be applied. 535 * @param subsample width/height subsample factor 536 * @param enableSquareInscribe true, output is an cropped square output; 537 * false, output maintains aspect ratio of input image 538 * @return inscribed image as ARGB_8888 539 */ 540 protected int[] colorSubSampleFromYuvImage(ImageProxy img, Rect crop, int subsample, 541 boolean enableSquareInscribe) { 542 crop = guaranteedSafeCrop(img, crop); 543 final List<ImageProxy.Plane> planeList = img.getPlanes(); 544 if (planeList.size() != 3) { 545 throw new IllegalArgumentException("Incorrect number planes (" + planeList.size() 546 + ") in YUV Image Object"); 547 } 548 549 int inputWidth = crop.width(); 550 int inputHeight = crop.height(); 551 int outputWidth = inputWidth / subsample; 552 int outputHeight = inputHeight / subsample; 553 554 // Set up input read boundaries. 555 556 ByteBuffer bufY = planeList.get(0).getBuffer(); 557 ByteBuffer bufU = planeList.get(1).getBuffer(); // Downsampled by 2 558 ByteBuffer bufV = planeList.get(2).getBuffer(); // Downsampled by 2 559 int yByteStride = planeList.get(0).getRowStride() * subsample; 560 int uByteStride = planeList.get(1).getRowStride() * subsample; 561 int vByteStride = planeList.get(2).getRowStride() * subsample; 562 int yPixelStride = planeList.get(0).getPixelStride() * subsample; 563 int uPixelStride = planeList.get(1).getPixelStride() * subsample; 564 int vPixelStride = planeList.get(2).getPixelStride() * subsample; 565 566 567 // Set up default input read boundaries. 568 final int outputPixelStride; 569 final int len; 570 final int inscribedXMin; 571 final int inscribedXMax; 572 final int inscribedYMin; 573 final int inscribedYMax; 574 final int inputVerticalOffset = quantizeBy2(crop.top); 575 final int inputHorizontalOffset = quantizeBy2(crop.left); 576 577 if (enableSquareInscribe) { 578 int r = inscribedCircleRadius(outputWidth, outputHeight); 579 len = r * r * 4; 580 outputPixelStride = r * 2; 581 582 if (outputWidth > outputHeight) { 583 // since we're 2x2 blocks we need to quantize these values by 2 584 inscribedXMin = quantizeBy2(outputWidth / 2 - r); 585 inscribedXMax = quantizeBy2(outputWidth / 2 + r); 586 inscribedYMin = 0; 587 inscribedYMax = outputHeight; 588 } else { 589 inscribedXMin = 0; 590 inscribedXMax = outputWidth; 591 // since we're 2x2 blocks we need to quantize these values by 2 592 inscribedYMin = quantizeBy2(outputHeight / 2 - r); 593 inscribedYMax = quantizeBy2(outputHeight / 2 + r); 594 } 595 } else { 596 outputPixelStride = outputWidth; 597 len = outputWidth * outputHeight; 598 inscribedXMin = 0; 599 inscribedXMax = quantizeBy2(outputWidth); 600 inscribedYMin = 0; 601 inscribedYMax = quantizeBy2(outputHeight); 602 } 603 604 int[] colors = new int[len]; 605 int alpha = 255 << 24; 606 607 logWrapper("TIMER_BEGIN Starting Native Java YUV420-to-RGB Rectangular Conversion"); 608 logWrapper("\t Y-Plane Size=" + outputWidth + "x" + outputHeight); 609 logWrapper("\t U-Plane Size=" + planeList.get(1).getRowStride() + " Pixel Stride=" 610 + planeList.get(1).getPixelStride()); 611 logWrapper("\t V-Plane Size=" + planeList.get(2).getRowStride() + " Pixel Stride=" 612 + planeList.get(2).getPixelStride()); 613 // Take in vertical lines by factor of two because of the u/v component 614 // subsample 615 for (int j = inscribedYMin; j < inscribedYMax; j += 2) { 616 int offsetColor = (j - inscribedYMin) * (outputPixelStride); 617 int offsetY = calculateMemoryOffsetFromPixelOffsets(inscribedXMin, j, subsample, 618 1 /* YComponent */, yByteStride, yPixelStride, inputHorizontalOffset, 619 inputVerticalOffset); 620 int offsetU = calculateMemoryOffsetFromPixelOffsets(inscribedXMin, j, subsample, 621 2 /* U Component downsampled by 2 */, uByteStride, uPixelStride, 622 inputHorizontalOffset / 2, inputVerticalOffset / 2); 623 int offsetV = calculateMemoryOffsetFromPixelOffsets(inscribedXMin, j, subsample, 624 2 /* v Component downsampled by 2 */, vByteStride, vPixelStride, 625 inputHorizontalOffset / 2, inputVerticalOffset / 2); 626 627 // Take in horizontal lines by factor of two because of the u/v 628 // component subsample 629 // and everything as 2x2 blocks. 630 for (int i = inscribedXMin; i < inscribedXMax; i += 2, offsetY += 2 * yPixelStride, 631 offsetColor += 2, offsetU += uPixelStride, offsetV += vPixelStride) { 632 // Note i and j are in terms of pixels of the subsampled image 633 // offsetY, offsetU, and offsetV are in terms of bytes of the 634 // image 635 // offsetColor, output_pixel stride are in terms of the packed 636 // output image 637 638 // calculate the RGB component of the u/v channels and use it 639 // for all pixels in the 2x2 block 640 int u = (int) (bufU.get(offsetU) & 255) - 128; 641 int v = (int) (bufV.get(offsetV) & 255) - 128; 642 int redDiff = (v * V_FACTOR_FOR_R) >> SHIFT_APPROXIMATION; 643 int greenDiff = ((u * U_FACTOR_FOR_G + v * V_FACTOR_FOR_G) >> SHIFT_APPROXIMATION); 644 int blueDiff = (u * U_FACTOR_FOR_B) >> SHIFT_APPROXIMATION; 645 646 // Do a little alpha feathering on the edges 647 int alpha00 = (255 << 24); 648 649 int y00 = (int) (bufY.get(offsetY) & 255); 650 651 int green00 = y00 + greenDiff; 652 int blue00 = y00 + blueDiff; 653 int red00 = y00 + redDiff; 654 655 // Get the railing correct 656 if (green00 < 0) { 657 green00 = 0; 658 } 659 if (red00 < 0) { 660 red00 = 0; 661 } 662 if (blue00 < 0) { 663 blue00 = 0; 664 } 665 666 if (green00 > 255) { 667 green00 = 255; 668 } 669 if (red00 > 255) { 670 red00 = 255; 671 } 672 if (blue00 > 255) { 673 blue00 = 255; 674 } 675 676 colors[offsetColor] = (red00 & 255) << 16 | (green00 & 255) << 8 677 | (blue00 & 255) | alpha00; 678 679 int alpha01 = (255 << 24); 680 int y01 = (int) (bufY.get(offsetY + yPixelStride) & 255); 681 int green01 = y01 + greenDiff; 682 int blue01 = y01 + blueDiff; 683 int red01 = y01 + redDiff; 684 685 // Get the railing correct 686 if (green01 < 0) { 687 green01 = 0; 688 } 689 if (red01 < 0) { 690 red01 = 0; 691 } 692 if (blue01 < 0) { 693 blue01 = 0; 694 } 695 696 if (green01 > 255) { 697 green01 = 255; 698 } 699 if (red01 > 255) { 700 red01 = 255; 701 } 702 if (blue01 > 255) { 703 blue01 = 255; 704 } 705 colors[offsetColor + 1] = (red01 & 255) << 16 | (green01 & 255) << 8 706 | (blue01 & 255) | alpha01; 707 708 int alpha10 = (255 << 24); 709 int y10 = (int) (bufY.get(offsetY + yByteStride) & 255); 710 int green10 = y10 + greenDiff; 711 int blue10 = y10 + blueDiff; 712 int red10 = y10 + redDiff; 713 714 // Get the railing correct 715 if (green10 < 0) { 716 green10 = 0; 717 } 718 if (red10 < 0) { 719 red10 = 0; 720 } 721 if (blue10 < 0) { 722 blue10 = 0; 723 } 724 if (green10 > 255) { 725 green10 = 255; 726 } 727 if (red10 > 255) { 728 red10 = 255; 729 } 730 if (blue10 > 255) { 731 blue10 = 255; 732 } 733 734 colors[offsetColor + outputPixelStride] = (red10 & 255) << 16 735 | (green10 & 255) << 8 | (blue10 & 255) | alpha10; 736 737 int alpha11 = (255 << 24); 738 int y11 = (int) (bufY.get(offsetY + yByteStride + yPixelStride) & 255); 739 int green11 = y11 + greenDiff; 740 int blue11 = y11 + blueDiff; 741 int red11 = y11 + redDiff; 742 743 // Get the railing correct 744 if (green11 < 0) { 745 green11 = 0; 746 } 747 if (red11 < 0) { 748 red11 = 0; 749 } 750 if (blue11 < 0) { 751 blue11 = 0; 752 } 753 754 if (green11 > 255) { 755 green11 = 255; 756 } 757 758 if (red11 > 255) { 759 red11 = 255; 760 } 761 if (blue11 > 255) { 762 blue11 = 255; 763 } 764 colors[offsetColor + outputPixelStride + 1] = (red11 & 255) << 16 765 | (green11 & 255) << 8 | (blue11 & 255) | alpha11; 766 } 767 } 768 logWrapper("TIMER_END Starting Native Java YUV420-to-RGB Rectangular Conversion"); 769 770 return colors; 771 } 772 773 /** 774 * DEBUG IMAGE FUNCTION Converts an Android Image to a inscribed circle 775 * bitmap, currently wired to the test pattern. Will subsample and optimize 776 * the image given a target resolution. 777 * 778 * @param img YUV420_888 Image to convert 779 * @param subsample width/height subsample factor 780 * @return inscribed image as ARGB_8888 781 */ 782 protected int[] dummyColorInscribedDataCircleFromYuvImage(ImageProxy img, int subsample) { 783 logWrapper("RUNNING DUMMY dummyColorInscribedDataCircleFromYuvImage"); 784 int w = img.getWidth() / subsample; 785 int h = img.getHeight() / subsample; 786 int r = inscribedCircleRadius(w, h); 787 int len = r * r * 4; 788 int[] colors = new int[len]; 789 790 // Make a fun test pattern. 791 for (int i = 0; i < len; i++) { 792 int x = i % (2 * r); 793 int y = i / (2 * r); 794 colors[i] = (255 << 24) | ((x & 255) << 16) | ((y & 255) << 8); 795 } 796 797 return colors; 798 } 799 800 /** 801 * Calculates the input Task Image specification an ImageProxy 802 * 803 * @param img Specified ImageToProcess 804 * @return Calculated specification 805 */ 806 protected TaskImage calculateInputImage(ImageToProcess img, Rect cropApplied) { 807 return new TaskImage(img.rotation, img.proxy.getWidth(), img.proxy.getHeight(), 808 img.proxy.getFormat(), cropApplied); 809 } 810 811 /** 812 * Calculates the resultant Task Image specification, given the shape 813 * selected at the time of task construction 814 * 815 * @param img Specified image to process 816 * @param subsample Amount of subsampling to be applied 817 * @return Calculated Specification 818 */ 819 protected TaskImage calculateResultImage(ImageToProcess img, int subsample) { 820 final Rect safeCrop = guaranteedSafeCrop(img.proxy, img.crop); 821 int resultWidth, resultHeight; 822 823 if (mThumbnailShape == ThumbnailShape.MAINTAIN_ASPECT_NO_INSET) { 824 resultWidth = safeCrop.width() / subsample; 825 resultHeight = safeCrop.height() / subsample; 826 } else { 827 final int radius = inscribedCircleRadius(safeCrop.width() / subsample, safeCrop.height() 828 / subsample); 829 resultWidth = 2 * radius; 830 resultHeight = 2 * radius; 831 } 832 833 return new TaskImage(img.rotation, resultWidth, resultHeight, 834 TaskImage.EXTRA_USER_DEFINED_FORMAT_ARGB_8888, 835 null /* Crop already applied */); 836 837 } 838 839 /** 840 * Runs the correct image conversion routine, based upon the selected 841 * thumbnail shape. 842 * 843 * @param img Image to be converted 844 * @param subsample Amount of image subsampling 845 * @return an ARGB_888 packed array ready for Bitmap conversion 846 */ 847 protected int[] runSelectedConversion(ImageProxy img, Rect crop, int subsample) { 848 switch (mThumbnailShape) { 849 case DEBUG_SQUARE_ASPECT_CIRCULAR_INSET: 850 return dummyColorInscribedDataCircleFromYuvImage(img, subsample); 851 case SQUARE_ASPECT_CIRCULAR_INSET: 852 return colorInscribedDataCircleFromYuvImage(img, crop, subsample); 853 case SQUARE_ASPECT_NO_INSET: 854 return colorSubSampleFromYuvImage(img, crop, subsample, true); 855 case MAINTAIN_ASPECT_NO_INSET: 856 return colorSubSampleFromYuvImage(img, crop, subsample, false); 857 default: 858 return null; 859 } 860 } 861 862 /** 863 * Runnable implementation 864 */ 865 @Override 866 public void run() { 867 ImageToProcess img = mImage; 868 Rect safeCrop = guaranteedSafeCrop(img.proxy, img.crop); 869 870 final TaskImage inputImage = calculateInputImage(img, safeCrop); 871 final int subsample = calculateBestSubsampleFactor( 872 new Size(safeCrop.width(), safeCrop.height()), 873 mTargetSize); 874 final TaskImage resultImage = calculateResultImage(img, subsample); 875 final int[] convertedImage; 876 877 try { 878 onStart(mId, inputImage, resultImage, TaskInfo.Destination.FAST_THUMBNAIL); 879 880 logWrapper("TIMER_END Rendering preview YUV buffer available, w=" 881 + img.proxy.getWidth() 882 / subsample + " h=" + img.proxy.getHeight() / subsample + " of subsample " 883 + subsample); 884 885 convertedImage = runSelectedConversion(img.proxy, safeCrop, subsample); 886 } finally { 887 // Signal backend that reference has been released 888 mImageTaskManager.releaseSemaphoreReference(img, mExecutor); 889 } 890 onPreviewDone(resultImage, inputImage, convertedImage, TaskInfo.Destination.FAST_THUMBNAIL); 891 } 892 893 /** 894 * Wraps the onResultUncompressed listener function 895 * 896 * @param resultImage Image specification of result image 897 * @param inputImage Image specification of the input image 898 * @param colors Uncompressed data buffer 899 * @param destination Specifies the purpose of this image processing 900 * artifact 901 */ 902 public void onPreviewDone(TaskImage resultImage, TaskImage inputImage, int[] colors, 903 TaskInfo.Destination destination) { 904 TaskInfo job = new TaskInfo(mId, inputImage, resultImage, destination); 905 final ImageProcessorListener listener = mImageTaskManager.getProxyListener(); 906 907 listener.onResultUncompressed(job, new UncompressedPayload(colors)); 908 } 909 910 } 911