93b0211d3aa6da72b9051b4681dddbaf34249ce5
[zxing.git] / javase / src / com / google / zxing / client / j2se / BufferedImageMonochromeBitmapSource.java
1 /*
2  * Copyright 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.j2se;
18
19 import com.google.zxing.MonochromeBitmapSource;
20 import com.google.zxing.common.BaseMonochromeBitmapSource;
21
22 import java.awt.geom.AffineTransform;
23 import java.awt.image.AffineTransformOp;
24 import java.awt.image.BufferedImage;
25 import java.awt.image.BufferedImageOp;
26
27 /**
28  * <p>An implementation based upon {@link BufferedImage}. This provides access to the
29  * underlying image as if it were a monochrome image. Behind the scenes, it is evaluating
30  * the luminance of the underlying image by retrieving its pixels' RGB values.</p>
31  *
32  * <p>This may also be used to construct a {@link MonochromeBitmapSource}
33  * based on a region of a {@link BufferedImage}; see
34  * {@link #BufferedImageMonochromeBitmapSource(BufferedImage, int, int, int, int)}.</p>
35  *
36  * @author srowen@google.com (Sean Owen), Daniel Switkin (dswitkin@google.com)
37  */
38 public final class BufferedImageMonochromeBitmapSource extends BaseMonochromeBitmapSource {
39
40   private final BufferedImage image;
41   private final int left;
42   private final int top;
43   private final int width;
44   private final int height;
45   private int[] rgbRow;
46   private int cachedRow;
47
48   /**
49    * Creates an instance that uses the entire given image as a source of pixels to decode.
50    *
51    * @param image image to decode
52    */
53   public BufferedImageMonochromeBitmapSource(BufferedImage image) {
54     this(image, 0, 0, image.getWidth(), image.getHeight());
55     rgbRow = new int[image.getWidth()];
56     cachedRow = -1;
57   }
58
59   /**
60    * Creates an instance that uses only a region of the given image as a source of pixels to decode.
61    *
62    * @param image image to decode a region of
63    * @param left x coordinate of leftmost pixels to decode
64    * @param top y coordinate of topmost pixels to decode
65    * @param right one more than the x coordinate of rightmost pixels to decode. That is, we will decode
66    *  pixels whose x coordinate is in [left,right)
67    * @param bottom likewise, one more than the y coordinate of the bottommost pixels to decode
68    */
69   public BufferedImageMonochromeBitmapSource(BufferedImage image, int left, int top, int right, int bottom) {
70     this.image = image;
71     int sourceHeight = image.getHeight();
72     int sourceWidth = image.getWidth();
73     if (left < 0 || top < 0 || right > sourceWidth || bottom > sourceHeight || right <= left || bottom <= top) {
74       throw new IllegalArgumentException("Invalid bounds: (" + top + ',' + left + ") (" + right + ',' + bottom + ')');
75     }
76     this.left = left;
77     this.top = top;
78     this.width = right - left;
79     this.height = bottom - top;
80     rgbRow = new int[width];
81     cachedRow = -1;
82   }
83
84   /**
85    * @return underlying {@link BufferedImage} behind this instance. Note that even if this instance
86    *  only uses a subset of the full image, the returned value here represents the entire backing image.
87    */
88   public BufferedImage getImage() {
89     return image;
90   }
91
92   public int getHeight() {
93     return height;
94   }
95
96   public int getWidth() {
97     return width;
98   }
99
100   @Override
101   public MonochromeBitmapSource rotateCounterClockwise() {
102     if (!isRotateSupported()) {
103       throw new IllegalStateException("Rotate not supported");
104     }
105     int sourceWidth = image.getWidth();
106     int sourceHeight = image.getHeight();
107     // 90 degrees counterclockwise:
108     AffineTransform transform = new AffineTransform(0.0, -1.0, 1.0, 0.0, 0.0, sourceWidth);
109     BufferedImageOp op = new AffineTransformOp(transform, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
110     // Note width/height are flipped since we are rotating 90 degrees:
111     BufferedImage rotatedImage = new BufferedImage(sourceHeight, sourceWidth, image.getType());
112     op.filter(image, rotatedImage);
113     return new BufferedImageMonochromeBitmapSource(rotatedImage,
114                                                    top,
115                                                    sourceWidth - (left + width),
116                                                    top + height,
117                                                    sourceWidth - left);
118   }
119
120   @Override
121   public boolean isRotateSupported() {
122     // Can't run AffineTransforms on images of unknown format
123     return image.getType() != BufferedImage.TYPE_CUSTOM;
124   }
125
126   /**
127    * Extracts luminance from a pixel from this source. By default, the source is assumed to use RGB,
128    * so this implementation computes luminance is a function of a red, green and blue components as
129    * follows:
130    *
131    * <code>Y = 0.299R + 0.587G + 0.114B</code>
132    *
133    * where R, G, and B are values in [0,1].
134    */
135   public int getLuminance(int x, int y) {
136     int pixel;
137     if (cachedRow == y) {
138       pixel = rgbRow[x];
139     } else {
140       pixel = image.getRGB(left + x, top + y);
141     }
142
143     // Coefficients add up to 1024 to make the divide into a fast shift
144     return (306 * ((pixel >> 16) & 0xFF) +
145         601 * ((pixel >> 8) & 0xFF) +
146         117 * (pixel & 0xFF)) >> 10;
147   }
148
149   public void cacheRowForLuminance(int y) {
150     if (y != cachedRow) {
151       image.getRGB(left, top + y, width, 1, rgbRow, 0, width);
152       cachedRow = y;
153     }
154   }
155
156 }