1 /* 2 * Copyright (C) 2008 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 17 package com.google.zxing.client.android; 18 19 import com.google.zxing.ResultPoint; 20 import com.google.zxing.client.android.camera.CameraManager; 21 22 import android.content.Context; 23 import android.content.res.Resources; 24 import android.graphics.Bitmap; 25 import android.graphics.Canvas; 26 import android.graphics.Paint; 27 import android.graphics.Rect; 28 import android.util.AttributeSet; 29 import android.view.View; 30 31 import java.util.ArrayList; 32 import java.util.List; 33 34 /** 35 * This view is overlaid on top of the camera preview. It adds the viewfinder rectangle and partial 36 * transparency outside it, as well as the laser scanner animation and result points. 37 * 38 * @author dswitkin (at) google.com (Daniel Switkin) 39 */ 40 public final class ViewfinderView extends View { 41 42 private static final int[] SCANNER_ALPHA = {0, 64, 128, 192, 255, 192, 128, 64}; 43 private static final long ANIMATION_DELAY = 80L; 44 private static final int CURRENT_POINT_OPACITY = 0xA0; 45 private static final int MAX_RESULT_POINTS = 20; 46 private static final int POINT_SIZE = 6; 47 48 private CameraManager cameraManager; 49 private final Paint paint; 50 private Bitmap resultBitmap; 51 private final int maskColor; 52 private final int resultColor; 53 private final int frameColor; 54 private final int laserColor; 55 private final int resultPointColor; 56 private int scannerAlpha; 57 private List<ResultPoint> possibleResultPoints; 58 private List<ResultPoint> lastPossibleResultPoints; 59 60 // This constructor is used when the class is built from an XML resource. 61 public ViewfinderView(Context context, AttributeSet attrs) { 62 super(context, attrs); 63 64 // Initialize these once for performance rather than calling them every time in onDraw(). 65 paint = new Paint(Paint.ANTI_ALIAS_FLAG); 66 Resources resources = getResources(); 67 maskColor = resources.getColor(R.color.viewfinder_mask); 68 resultColor = resources.getColor(R.color.result_view); 69 frameColor = resources.getColor(R.color.viewfinder_frame); 70 laserColor = resources.getColor(R.color.viewfinder_laser); 71 resultPointColor = resources.getColor(R.color.possible_result_points); 72 scannerAlpha = 0; 73 possibleResultPoints = new ArrayList<ResultPoint>(5); 74 lastPossibleResultPoints = null; 75 } 76 77 public void setCameraManager(CameraManager cameraManager) { 78 this.cameraManager = cameraManager; 79 } 80 81 @Override 82 public void onDraw(Canvas canvas) { 83 Rect frame = cameraManager.getFramingRect(); 84 if (frame == null) { 85 return; 86 } 87 int width = canvas.getWidth(); 88 int height = canvas.getHeight(); 89 90 // Draw the exterior (i.e. outside the framing rect) darkened 91 paint.setColor(resultBitmap != null ? resultColor : maskColor); 92 canvas.drawRect(0, 0, width, frame.top, paint); 93 canvas.drawRect(0, frame.top, frame.left, frame.bottom + 1, paint); 94 canvas.drawRect(frame.right + 1, frame.top, width, frame.bottom + 1, paint); 95 canvas.drawRect(0, frame.bottom + 1, width, height, paint); 96 97 if (resultBitmap != null) { 98 // Draw the opaque result bitmap over the scanning rectangle 99 paint.setAlpha(CURRENT_POINT_OPACITY); 100 canvas.drawBitmap(resultBitmap, null, frame, paint); 101 } else { 102 103 // Draw a two pixel solid black border inside the framing rect 104 paint.setColor(frameColor); 105 canvas.drawRect(frame.left, frame.top, frame.right + 1, frame.top + 2, paint); 106 canvas.drawRect(frame.left, frame.top + 2, frame.left + 2, frame.bottom - 1, paint); 107 canvas.drawRect(frame.right - 1, frame.top, frame.right + 1, frame.bottom - 1, paint); 108 canvas.drawRect(frame.left, frame.bottom - 1, frame.right + 1, frame.bottom + 1, paint); 109 110 // Draw a red "laser scanner" line through the middle to show decoding is active 111 paint.setColor(laserColor); 112 paint.setAlpha(SCANNER_ALPHA[scannerAlpha]); 113 scannerAlpha = (scannerAlpha + 1) % SCANNER_ALPHA.length; 114 int middle = frame.height() / 2 + frame.top; 115 canvas.drawRect(frame.left + 2, middle - 1, frame.right - 1, middle + 2, paint); 116 117 Rect previewFrame = cameraManager.getFramingRectInPreview(); 118 float scaleX = frame.width() / (float) previewFrame.width(); 119 float scaleY = frame.height() / (float) previewFrame.height(); 120 121 List<ResultPoint> currentPossible = possibleResultPoints; 122 List<ResultPoint> currentLast = lastPossibleResultPoints; 123 int frameLeft = frame.left; 124 int frameTop = frame.top; 125 if (currentPossible.isEmpty()) { 126 lastPossibleResultPoints = null; 127 } else { 128 possibleResultPoints = new ArrayList<ResultPoint>(5); 129 lastPossibleResultPoints = currentPossible; 130 paint.setAlpha(CURRENT_POINT_OPACITY); 131 paint.setColor(resultPointColor); 132 synchronized (currentPossible) { 133 for (ResultPoint point : currentPossible) { 134 canvas.drawCircle(frameLeft + (int) (point.getX() * scaleX), 135 frameTop + (int) (point.getY() * scaleY), 136 POINT_SIZE, paint); 137 } 138 } 139 } 140 if (currentLast != null) { 141 paint.setAlpha(CURRENT_POINT_OPACITY / 2); 142 paint.setColor(resultPointColor); 143 synchronized (currentLast) { 144 for (ResultPoint point : currentLast) { 145 canvas.drawCircle(frameLeft + (int) (point.getX() * scaleX), 146 frameTop + (int) (point.getY() * scaleY), 147 POINT_SIZE / 2, paint); 148 } 149 } 150 } 151 152 // Request another update at the animation interval, but only repaint the laser line, 153 // not the entire viewfinder mask. 154 postInvalidateDelayed(ANIMATION_DELAY, 155 frame.left - POINT_SIZE, 156 frame.top - POINT_SIZE, 157 frame.right + POINT_SIZE, 158 frame.bottom + POINT_SIZE); 159 } 160 } 161 162 public void drawViewfinder() { 163 Bitmap resultBitmap = this.resultBitmap; 164 this.resultBitmap = null; 165 if (resultBitmap != null) { 166 resultBitmap.recycle(); 167 } 168 invalidate(); 169 } 170 171 /** 172 * Draw a bitmap with the result points highlighted instead of the live scanning display. 173 * 174 * @param barcode An image of the decoded barcode. 175 */ 176 public void drawResultBitmap(Bitmap barcode) { 177 resultBitmap = barcode; 178 invalidate(); 179 } 180 181 public void addPossibleResultPoint(ResultPoint point) { 182 List<ResultPoint> points = possibleResultPoints; 183 synchronized (point) { 184 points.add(point); 185 int size = points.size(); 186 if (size > MAX_RESULT_POINTS) { 187 // trim it 188 points.subList(0, size - MAX_RESULT_POINTS / 2).clear(); 189 } 190 } 191 } 192 193 } 194