Added rounding code to getRow() as well and updated the tests accordingly.
[zxing.git] / javase / src / com / google / zxing / client / j2se / CommandLineRunner.java
1 /*
2  * Copyright 2007 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.BarcodeFormat;
20 import com.google.zxing.BinaryBitmap;
21 import com.google.zxing.DecodeHintType;
22 import com.google.zxing.LuminanceSource;
23 import com.google.zxing.MultiFormatReader;
24 import com.google.zxing.NotFoundException;
25 import com.google.zxing.Result;
26 import com.google.zxing.client.result.ParsedResult;
27 import com.google.zxing.client.result.ResultParser;
28 import com.google.zxing.common.BitArray;
29 import com.google.zxing.common.BitMatrix;
30 import com.google.zxing.common.HybridBinarizer;
31 import com.google.zxing.ResultPoint;
32
33 import java.awt.image.BufferedImage;
34 import java.io.File;
35 import java.io.FileNotFoundException;
36 import java.io.FileOutputStream;
37 import java.io.IOException;
38 import java.io.OutputStream;
39 import java.io.OutputStreamWriter;
40 import java.io.Writer;
41 import java.net.URI;
42 import java.net.URISyntaxException;
43 import java.nio.charset.Charset;
44 import java.util.Hashtable;
45 import java.util.Vector;
46
47 import javax.imageio.ImageIO;
48
49 /**
50  * <p>This simple command line utility decodes files, directories of files, or URIs which are passed
51  * as arguments. By default it uses the normal decoding algorithms, but you can pass --try_harder to
52  * request that hint. The raw text of each barcode is printed, and when running against directories,
53  * summary statistics are also displayed.</p>
54  *
55  * @author Sean Owen
56  * @author dswitkin@google.com (Daniel Switkin)
57  */
58 public final class CommandLineRunner {
59
60   private CommandLineRunner() {
61   }
62
63   public static void main(String[] args) throws Exception {
64     if (args == null || args.length == 0) {
65       printUsage();
66       return;
67     }
68
69     boolean tryHarder = false;
70     boolean pureBarcode = false;
71     boolean productsOnly = false;
72     boolean dumpResults = false;
73     boolean dumpBlackPoint = false;
74     int[] crop = null;
75     for (String arg : args) {
76       if ("--try_harder".equals(arg)) {
77         tryHarder = true;
78       } else if ("--pure_barcode".equals(arg)) {
79         pureBarcode = true;
80       } else if ("--products_only".equals(arg)) {
81         productsOnly = true;
82       } else if ("--dump_results".equals(arg)) {
83         dumpResults = true;
84       } else if ("--dump_black_point".equals(arg)) {
85         dumpBlackPoint = true;
86       } else if (arg.startsWith("--crop")) {
87         crop = new int[4];
88         String[] tokens = arg.substring(7).split(",");
89         for (int i = 0; i < crop.length; i++) {
90           crop[i] = Integer.parseInt(tokens[i]);
91         }
92       } else if (arg.startsWith("-")) {
93         System.err.println("Unknown command line option " + arg);
94         printUsage();
95         return;
96       }
97     }
98
99     Hashtable<DecodeHintType, Object> hints = buildHints(tryHarder, pureBarcode, productsOnly);
100     for (String arg : args) {
101       if (!arg.startsWith("--")) {
102         decodeOneArgument(arg, hints, dumpResults, dumpBlackPoint, crop);
103       }
104     }
105   }
106
107   // Manually turn on all formats, even those not yet considered production quality.
108   private static Hashtable<DecodeHintType, Object> buildHints(boolean tryHarder,
109                                                               boolean pureBarcode,
110                                                               boolean productsOnly) {
111     Hashtable<DecodeHintType, Object> hints = new Hashtable<DecodeHintType, Object>(3);
112     Vector<BarcodeFormat> vector = new Vector<BarcodeFormat>(8);
113     vector.addElement(BarcodeFormat.UPC_A);
114     vector.addElement(BarcodeFormat.UPC_E);
115     vector.addElement(BarcodeFormat.EAN_13);
116     vector.addElement(BarcodeFormat.EAN_8);
117     vector.addElement(BarcodeFormat.RSS14);
118     if (!productsOnly) {
119       vector.addElement(BarcodeFormat.CODE_39);
120       vector.addElement(BarcodeFormat.CODE_93);
121       vector.addElement(BarcodeFormat.CODE_128);
122       vector.addElement(BarcodeFormat.ITF);
123       vector.addElement(BarcodeFormat.QR_CODE);
124       vector.addElement(BarcodeFormat.DATA_MATRIX);
125       vector.addElement(BarcodeFormat.PDF417);
126       //vector.addElement(BarcodeFormat.CODABAR);
127     }
128     hints.put(DecodeHintType.POSSIBLE_FORMATS, vector);
129     if (tryHarder) {
130       hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
131     }
132     if (pureBarcode) {
133       hints.put(DecodeHintType.PURE_BARCODE, Boolean.TRUE);
134     }
135     return hints;
136   }
137
138   private static void printUsage() {
139     System.err.println("Decode barcode images using the ZXing library\n");
140     System.err.println("usage: CommandLineRunner { file | dir | url } [ options ]");
141     System.err.println("  --try_harder: Use the TRY_HARDER hint, default is normal (mobile) mode");
142     System.err.println("  --pure_barcode: Input image is a pure monochrome barcode image, not a photo");
143     System.err.println("  --products_only: Only decode the UPC and EAN families of barcodes");
144     System.err.println("  --dump_results: Write the decoded contents to input.txt");
145     System.err.println("  --dump_black_point: Compare black point algorithms as input.mono.png");
146     System.err.println("  --crop=left,top,width,height: Only examine cropped region of input image(s)");
147   }
148
149   private static void decodeOneArgument(String argument,
150                                         Hashtable<DecodeHintType, Object> hints,
151                                         boolean dumpResults,
152                                         boolean dumpBlackPoint,
153                                         int[] crop) throws IOException,
154       URISyntaxException {
155
156     File inputFile = new File(argument);
157     if (inputFile.exists()) {
158       if (inputFile.isDirectory()) {
159         int successful = 0;
160         int total = 0;
161         for (File input : inputFile.listFiles()) {
162           String filename = input.getName().toLowerCase();
163           // Skip hidden files and text files (the latter is found in the blackbox tests).
164           if (filename.startsWith(".") || filename.endsWith(".txt")) {
165             continue;
166           }
167           // Skip the results of dumping the black point.
168           if (filename.contains(".mono.png")) {
169             continue;
170           }
171           Result result = decode(input.toURI(), hints, dumpBlackPoint, crop);
172           if (result != null) {
173             successful++;
174             if (dumpResults) {
175               dumpResult(input, result);
176             }
177           }
178           total++;
179         }
180         System.out.println("\nDecoded " + successful + " files out of " + total +
181             " successfully (" + (successful * 100 / total) + "%)\n");
182       } else {
183         Result result = decode(inputFile.toURI(), hints, dumpBlackPoint, crop);
184         if (dumpResults) {
185           dumpResult(inputFile, result);
186         }
187       }
188     } else {
189       decode(new URI(argument), hints, dumpBlackPoint, crop);
190     }
191   }
192
193   private static void dumpResult(File input, Result result) throws IOException {
194     String name = input.getAbsolutePath();
195     int pos = name.lastIndexOf('.');
196     if (pos > 0) {
197       name = name.substring(0, pos);
198     }
199     File dump = new File(name + ".txt");
200     writeStringToFile(result.getText(), dump);
201   }
202
203   private static void writeStringToFile(String value, File file) throws IOException {
204     Writer out = new OutputStreamWriter(new FileOutputStream(file), Charset.forName("UTF8"));
205     try {
206       out.write(value);
207     } finally {
208       out.close();
209     }
210   }
211
212   private static Result decode(URI uri,
213                                Hashtable<DecodeHintType, Object> hints,
214                                boolean dumpBlackPoint,
215                                int[] crop) throws IOException {
216     BufferedImage image;
217     try {
218       image = ImageIO.read(uri.toURL());
219     } catch (IllegalArgumentException iae) {
220       throw new FileNotFoundException("Resource not found: " + uri);
221     }
222     if (image == null) {
223       System.err.println(uri.toString() + ": Could not load image");
224       return null;
225     }
226     try {
227       LuminanceSource source;
228       if (crop == null) {
229         source = new BufferedImageLuminanceSource(image);
230       } else {
231         source = new BufferedImageLuminanceSource(image, crop[0], crop[1], crop[2], crop[3]);
232       }
233       BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
234       if (dumpBlackPoint) {
235         dumpBlackPoint(uri, image, bitmap);
236       }
237       Result result = new MultiFormatReader().decode(bitmap, hints);
238       ParsedResult parsedResult = ResultParser.parseResult(result);
239       System.out.println(uri.toString() + " (format: " + result.getBarcodeFormat() +
240           ", type: " + parsedResult.getType() + "):\nRaw result:\n" + result.getText() +
241           "\nParsed result:\n" + parsedResult.getDisplayResult());
242
243       System.out.println("Also, there were " + result.getResultPoints().length + " result points.");
244       for (int i = 0; i < result.getResultPoints().length; i++) {
245         ResultPoint rp = result.getResultPoints()[i];
246         System.out.println("  Point " + i + ": (" + rp.getX() + "," + rp.getY() + ")");
247       }
248
249       return result;
250     } catch (NotFoundException nfe) {
251       System.out.println(uri.toString() + ": No barcode found");
252       return null;
253     } finally {
254       // Uncomment these lines when turning on exception tracking in ReaderException.
255       //System.out.println("Threw " + ReaderException.getExceptionCountAndReset() + " exceptions");
256       //System.out.println("Throwers:\n" + ReaderException.getThrowersAndReset());
257     }
258   }
259
260   // Writes out a single PNG which is three times the width of the input image, containing from left
261   // to right: the original image, the row sampling monochrome version, and the 2D sampling
262   // monochrome version.
263   // TODO: Update to compare different Binarizer implementations.
264   private static void dumpBlackPoint(URI uri, BufferedImage image, BinaryBitmap bitmap) {
265     String inputName = uri.getPath();
266     if (inputName.contains(".mono.png")) {
267       return;
268     }
269
270     int width = bitmap.getWidth();
271     int height = bitmap.getHeight();
272     int stride = width * 3;
273     int[] pixels = new int[stride * height];
274
275     // The original image
276     int[] argb = new int[width];
277     for (int y = 0; y < height; y++) {
278       image.getRGB(0, y, width, 1, argb, 0, width);
279       System.arraycopy(argb, 0, pixels, y * stride, width);
280     }
281
282     // Row sampling
283     BitArray row = new BitArray(width);
284     for (int y = 0; y < height; y++) {
285       try {
286         row = bitmap.getBlackRow(y, row);
287       } catch (NotFoundException nfe) {
288         // If fetching the row failed, draw a red line and keep going.
289         int offset = y * stride + width;
290         for (int x = 0; x < width; x++) {
291           pixels[offset + x] = 0xffff0000;
292         }
293         continue;
294       }
295
296       int offset = y * stride + width;
297       for (int x = 0; x < width; x++) {
298         if (row.get(x)) {
299           pixels[offset + x] = 0xff000000;
300         } else {
301           pixels[offset + x] = 0xffffffff;
302         }
303       }
304     }
305
306     // 2D sampling
307     try {
308       for (int y = 0; y < height; y++) {
309         BitMatrix matrix = bitmap.getBlackMatrix();
310         int offset = y * stride + width * 2;
311         for (int x = 0; x < width; x++) {
312           if (matrix.get(x, y)) {
313             pixels[offset + x] = 0xff000000;
314           } else {
315             pixels[offset + x] = 0xffffffff;
316           }
317         }
318       }
319     } catch (NotFoundException nfe) {
320     }
321
322     // Write the result
323     BufferedImage result = new BufferedImage(stride, height, BufferedImage.TYPE_INT_ARGB);
324     result.setRGB(0, 0, stride, height, pixels, 0, stride);
325
326     // Use the current working directory for URLs
327     String resultName = inputName;
328     if ("http".equals(uri.getScheme())) {
329       int pos = resultName.lastIndexOf('/');
330       if (pos > 0) {
331         resultName = '.' + resultName.substring(pos);
332       }
333     }
334     int pos = resultName.lastIndexOf('.');
335     if (pos > 0) {
336       resultName = resultName.substring(0, pos);
337     }
338     resultName += ".mono.png";
339     OutputStream outStream = null;
340     try {
341       outStream = new FileOutputStream(resultName);
342       ImageIO.write(result, "png", outStream);
343     } catch (FileNotFoundException e) {
344       System.err.println("Could not create " + resultName);
345     } catch (IOException e) {
346       System.err.println("Could not write to " + resultName);
347     } finally {
348       try {
349         if (outStream != null) {
350           outStream.close();
351         }
352       } catch (IOException ioe) {
353         // continue
354       }
355     }
356   }
357
358 }