Issue 573 draw points correctly when preview/screen size differ
[zxing.git] / android / src / com / google / zxing / client / android / ViewfinderView.java
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.Collection;
32 import java.util.HashSet;
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@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 = 100L;
44   private static final int OPAQUE = 0xFF;
45
46   private final Paint paint;
47   private Bitmap resultBitmap;
48   private final int maskColor;
49   private final int resultColor;
50   private final int frameColor;
51   private final int laserColor;
52   private final int resultPointColor;
53   private int scannerAlpha;
54   private Collection<ResultPoint> possibleResultPoints;
55   private Collection<ResultPoint> lastPossibleResultPoints;
56
57   // This constructor is used when the class is built from an XML resource.
58   public ViewfinderView(Context context, AttributeSet attrs) {
59     super(context, attrs);
60
61     // Initialize these once for performance rather than calling them every time in onDraw().
62     paint = new Paint();
63     Resources resources = getResources();
64     maskColor = resources.getColor(R.color.viewfinder_mask);
65     resultColor = resources.getColor(R.color.result_view);
66     frameColor = resources.getColor(R.color.viewfinder_frame);
67     laserColor = resources.getColor(R.color.viewfinder_laser);
68     resultPointColor = resources.getColor(R.color.possible_result_points);
69     scannerAlpha = 0;
70     possibleResultPoints = new HashSet<ResultPoint>(5);
71   }
72
73   @Override
74   public void onDraw(Canvas canvas) {
75     Rect frame = CameraManager.get().getFramingRect();
76     if (frame == null) {
77       return;
78     }
79     int width = canvas.getWidth();
80     int height = canvas.getHeight();
81
82     // Draw the exterior (i.e. outside the framing rect) darkened
83     paint.setColor(resultBitmap != null ? resultColor : maskColor);
84     canvas.drawRect(0, 0, width, frame.top, paint);
85     canvas.drawRect(0, frame.top, frame.left, frame.bottom + 1, paint);
86     canvas.drawRect(frame.right + 1, frame.top, width, frame.bottom + 1, paint);
87     canvas.drawRect(0, frame.bottom + 1, width, height, paint);
88
89     if (resultBitmap != null) {
90       // Draw the opaque result bitmap over the scanning rectangle
91       paint.setAlpha(OPAQUE);
92       canvas.drawBitmap(resultBitmap, null, frame, paint);
93     } else {
94
95       // Draw a two pixel solid black border inside the framing rect
96       paint.setColor(frameColor);
97       canvas.drawRect(frame.left, frame.top, frame.right + 1, frame.top + 2, paint);
98       canvas.drawRect(frame.left, frame.top + 2, frame.left + 2, frame.bottom - 1, paint);
99       canvas.drawRect(frame.right - 1, frame.top, frame.right + 1, frame.bottom - 1, paint);
100       canvas.drawRect(frame.left, frame.bottom - 1, frame.right + 1, frame.bottom + 1, paint);
101
102       // Draw a red "laser scanner" line through the middle to show decoding is active
103       paint.setColor(laserColor);
104       paint.setAlpha(SCANNER_ALPHA[scannerAlpha]);
105       scannerAlpha = (scannerAlpha + 1) % SCANNER_ALPHA.length;
106       int middle = frame.height() / 2 + frame.top;
107       canvas.drawRect(frame.left + 2, middle - 1, frame.right - 1, middle + 2, paint);
108       
109       Rect previewFrame = CameraManager.get().getFramingRectInPreview();
110       float scaleX = frame.width() / (float) previewFrame.width();
111       float scaleY = frame.height() / (float) previewFrame.height();
112
113       Collection<ResultPoint> currentPossible = possibleResultPoints;
114       Collection<ResultPoint> currentLast = lastPossibleResultPoints;
115       if (currentPossible.isEmpty()) {
116         lastPossibleResultPoints = null;
117       } else {
118         possibleResultPoints = new HashSet<ResultPoint>(5);
119         lastPossibleResultPoints = currentPossible;
120         paint.setAlpha(OPAQUE);
121         paint.setColor(resultPointColor);
122         for (ResultPoint point : currentPossible) {
123           canvas.drawCircle(frame.left + (int) (point.getX() * scaleX),
124                             frame.top + (int) (point.getY() * scaleY),
125                             6.0f, paint);
126         }
127       }
128       if (currentLast != null) {
129         paint.setAlpha(OPAQUE / 2);
130         paint.setColor(resultPointColor);
131         for (ResultPoint point : currentLast) {
132           canvas.drawCircle(frame.left + (int) (point.getX() * scaleX),
133                             frame.top + (int) (point.getY() * scaleY),
134                             3.0f, paint);
135         }
136       }
137
138       // Request another update at the animation interval, but only repaint the laser line,
139       // not the entire viewfinder mask.
140       postInvalidateDelayed(ANIMATION_DELAY, frame.left, frame.top, frame.right, frame.bottom);
141     }
142   }
143
144   public void drawViewfinder() {
145     resultBitmap = null;
146     invalidate();
147   }
148
149   /**
150    * Draw a bitmap with the result points highlighted instead of the live scanning display.
151    *
152    * @param barcode An image of the decoded barcode.
153    */
154   public void drawResultBitmap(Bitmap barcode) {
155     resultBitmap = barcode;
156     invalidate();
157   }
158
159   public void addPossibleResultPoint(ResultPoint point) {
160     possibleResultPoints.add(point);
161   }
162
163 }