1/*
2 * Copyright 2009 ZXing authors
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.google.zxing.client.android;
18
19import com.google.zxing.LuminanceSource;
20
21import android.graphics.Bitmap;
22
23/**
24 * This object extends LuminanceSource around an array of YUV data returned from the camera driver,
25 * with the option to crop to a rectangle within the full data. This can be used to exclude
26 * superfluous pixels around the perimeter and speed up decoding.
27 *
28 * It works for any pixel format where the Y channel is planar and appears first, including
29 * YCbCr_420_SP and YCbCr_422_SP.
30 *
31 * @author dswitkin@google.com (Daniel Switkin)
32 */
33public final class PlanarYUVLuminanceSource extends LuminanceSource {
34
35  private final byte[] yuvData;
36  private final int dataWidth;
37  private final int dataHeight;
38  private final int left;
39  private final int top;
40
41  public PlanarYUVLuminanceSource(byte[] yuvData, int dataWidth, int dataHeight, int left, int top,
42      int width, int height, boolean reverseHorizontal) {
43    super(width, height);
44
45    if (left + width > dataWidth || top + height > dataHeight) {
46      throw new IllegalArgumentException("Crop rectangle does not fit within image data.");
47    }
48
49    this.yuvData = yuvData;
50    this.dataWidth = dataWidth;
51    this.dataHeight = dataHeight;
52    this.left = left;
53    this.top = top;
54    if (reverseHorizontal) {
55      reverseHorizontal(width, height);
56    }
57  }
58
59  @Override
60  public byte[] getRow(int y, byte[] row) {
61    if (y < 0 || y >= getHeight()) {
62      throw new IllegalArgumentException("Requested row is outside the image: " + y);
63    }
64    int width = getWidth();
65    if (row == null || row.length < width) {
66      row = new byte[width];
67    }
68    int offset = (y + top) * dataWidth + left;
69    System.arraycopy(yuvData, offset, row, 0, width);
70    return row;
71  }
72
73  @Override
74  public byte[] getMatrix() {
75    int width = getWidth();
76    int height = getHeight();
77
78    // If the caller asks for the entire underlying image, save the copy and give them the
79    // original data. The docs specifically warn that result.length must be ignored.
80    if (width == dataWidth && height == dataHeight) {
81      return yuvData;
82    }
83
84    int area = width * height;
85    byte[] matrix = new byte[area];
86    int inputOffset = top * dataWidth + left;
87
88    // If the width matches the full width of the underlying data, perform a single copy.
89    if (width == dataWidth) {
90      System.arraycopy(yuvData, inputOffset, matrix, 0, area);
91      return matrix;
92    }
93
94    // Otherwise copy one cropped row at a time.
95    byte[] yuv = yuvData;
96    for (int y = 0; y < height; y++) {
97      int outputOffset = y * width;
98      System.arraycopy(yuv, inputOffset, matrix, outputOffset, width);
99      inputOffset += dataWidth;
100    }
101    return matrix;
102  }
103
104  @Override
105  public boolean isCropSupported() {
106    return true;
107  }
108
109  public Bitmap renderCroppedGreyscaleBitmap() {
110    int width = getWidth();
111    int height = getHeight();
112    int[] pixels = new int[width * height];
113    byte[] yuv = yuvData;
114    int inputOffset = top * dataWidth + left;
115
116    for (int y = 0; y < height; y++) {
117      int outputOffset = y * width;
118      for (int x = 0; x < width; x++) {
119        int grey = yuv[inputOffset + x] & 0xff;
120        pixels[outputOffset + x] = 0xFF000000 | (grey * 0x00010101);
121      }
122      inputOffset += dataWidth;
123    }
124
125    Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
126    bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
127    return bitmap;
128  }
129
130  private void reverseHorizontal(int width, int height) {
131    byte[] yuvData = this.yuvData;
132    for (int y = 0, rowStart = top * dataWidth + left; y < height; y++, rowStart += dataWidth) {
133      int middle = rowStart + width / 2;
134      for (int x1 = rowStart, x2 = rowStart + width - 1; x1 < middle; x1++, x2--) {
135        byte temp = yuvData[x1];
136        yuvData[x1] = yuvData[x2];
137        yuvData[x2] = temp;
138      }
139    }
140  }
141
142}
143