二维码编解码程序

This commit is contained in:
xuenhua 2024-12-17 11:43:40 +08:00
commit 20dc2d6bec
181 changed files with 27346 additions and 0 deletions

6
.classpath Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="output" path="bin"/>
</classpath>

17
.project Normal file
View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>zxing</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

53
build.xml Normal file
View File

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2007 ZXing authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<project name="javase" default="build">
<target name="init">
<tstamp/>
<fail message="Please build 'core' first">
<condition>
<not>
<available file="../core/core.jar" type="file"/>
</not>
</condition>
</fail>
</target>
<target name="build" depends="init">
<mkdir dir="build"/>
<javac srcdir="src"
destdir="build"
source="1.5"
target="1.5"
optimize="true"
debug="true"
deprecation="true">
<classpath>
<pathelement location="../core/core.jar"/>
</classpath>
</javac>
<jar jarfile="javase.jar" basedir="build"/>
</target>
<target name="clean">
<delete dir="build"/>
<delete file="javase.jar"/>
</target>
</project>

118
pom.xml Normal file
View File

@ -0,0 +1,118 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (C) 2010 ZXing authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
<packaging>jar</packaging>
<name>ZXing Java SE extensions</name>
<version>1.6-SNAPSHOT</version>
<description>Java SE-specific extensions to core ZXing library</description>
<url>http://code.google.com/p/zxing</url>
<inceptionYear>2007</inceptionYear>
<issueManagement>
<system>Google Code</system>
<url>http://code.google.com/p/zxing/issues/list</url>
</issueManagement>
<licenses>
<license>
<name>The Apache Software License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<developers>
<developer>
<name>Daniel Switkin</name>
<email>dswitkin@gmail.com</email>
</developer>
<developer>
<name>Sean Owen</name>
<email>srowen@gmail.com</email>
</developer>
</developers>
<scm>
<connection>scm:svn:http://zxing.googlecode.com/svn/trunk</connection>
<developerConnection>scm:svn:https://zxing.googlecode.com/svn/trunk</developerConnection>
<url>http://zxing.googlecode.com/svn/trunk</url>
</scm>
<dependencies>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>1.6-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<sourceDirectory>src</sourceDirectory>
<outputDirectory>build</outputDirectory>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>6</source>
<target>6</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-release-plugin</artifactId>
</plugin>
</plugins>
</build>
<distributionManagement>
<repository>
<id>sonatype-nexus-staging</id>
<name>Sonatype Nexus Staging</name>
<url>http://oss.sonatype.org/content/groups/google-with-staging/</url>
</repository>
<snapshotRepository>
<id>sonatype-nexus-snapshots</id>
<name>Sonatype Nexus Snapshots</name>
<url>http://oss.sonatype.org/content/repositories/google-snapshots/</url>
</snapshotRepository>
</distributionManagement>
<profiles>
<profile>
<id>release-sign-artifacts</id>
<activation>
<property>
<name>performRelease</name>
<value>true</value>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@ -0,0 +1,103 @@
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing;
import java.util.Hashtable;
/**
* Enumerates barcode formats known to this package.
*
* @author Sean Owen
*/
public final class BarcodeFormat {
// No, we can't use an enum here. J2ME doesn't support it.
private static final Hashtable VALUES = new Hashtable();
/** QR Code 2D barcode format. */
public static final BarcodeFormat QR_CODE = new BarcodeFormat("QR_CODE");
/** Data Matrix 2D barcode format. */
public static final BarcodeFormat DATA_MATRIX = new BarcodeFormat("DATA_MATRIX");
/** UPC-E 1D format. */
public static final BarcodeFormat UPC_E = new BarcodeFormat("UPC_E");
/** UPC-A 1D format. */
public static final BarcodeFormat UPC_A = new BarcodeFormat("UPC_A");
/** EAN-8 1D format. */
public static final BarcodeFormat EAN_8 = new BarcodeFormat("EAN_8");
/** EAN-13 1D format. */
public static final BarcodeFormat EAN_13 = new BarcodeFormat("EAN_13");
/** UPC/EAN extension format. Not a stand-alone format. */
public static final BarcodeFormat UPC_EAN_EXTENSION = new BarcodeFormat("UPC_EAN_EXTENSION");
/** Code 128 1D format. */
public static final BarcodeFormat CODE_128 = new BarcodeFormat("CODE_128");
/** Code 39 1D format. */
public static final BarcodeFormat CODE_39 = new BarcodeFormat("CODE_39");
/** Code 93 1D format. */
public static final BarcodeFormat CODE_93 = new BarcodeFormat("CODE_93");
/** CODABAR 1D format. */
public static final BarcodeFormat CODABAR = new BarcodeFormat("CODABAR");
/** ITF (Interleaved Two of Five) 1D format. */
public static final BarcodeFormat ITF = new BarcodeFormat("ITF");
/** RSS 14 */
public static final BarcodeFormat RSS14 = new BarcodeFormat("RSS14");
/** PDF417 format. */
public static final BarcodeFormat PDF417 = new BarcodeFormat("PDF417");
/** RSS EXPANDED */
public static final BarcodeFormat RSS_EXPANDED = new BarcodeFormat("RSS_EXPANDED");
private final String name;
private BarcodeFormat(String name) {
this.name = name;
VALUES.put(name, this);
}
public String getName() {
return name;
}
public String toString() {
return name;
}
public static BarcodeFormat valueOf(String name) {
if (name == null || name.length() == 0) {
throw new IllegalArgumentException();
}
BarcodeFormat format = (BarcodeFormat) VALUES.get(name);
if (format == null) {
throw new IllegalArgumentException();
}
return format;
}
}

View File

@ -0,0 +1,80 @@
/*
* Copyright 2009 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing;
import com.google.zxing.common.BitArray;
import com.google.zxing.common.BitMatrix;
/**
* This class hierarchy provides a set of methods to convert luminance data to 1 bit data.
* It allows the algorithm to vary polymorphically, for example allowing a very expensive
* thresholding technique for servers and a fast one for mobile. It also permits the implementation
* to vary, e.g. a JNI version for Android and a Java fallback version for other platforms.
*
* @author dswitkin@google.com (Daniel Switkin)
*/
public abstract class Binarizer {
private final LuminanceSource source;
protected Binarizer(LuminanceSource source) {
if (source == null) {
throw new IllegalArgumentException("Source must be non-null.");
}
this.source = source;
}
public LuminanceSource getLuminanceSource() {
return source;
}
/**
* Converts one row of luminance data to 1 bit data. May actually do the conversion, or return
* cached data. Callers should assume this method is expensive and call it as seldom as possible.
* This method is intended for decoding 1D barcodes and may choose to apply sharpening.
* For callers which only examine one row of pixels at a time, the same BitArray should be reused
* and passed in with each call for performance. However it is legal to keep more than one row
* at a time if needed.
*
* @param y The row to fetch, 0 <= y < bitmap height.
* @param row An optional preallocated array. If null or too small, it will be ignored.
* If used, the Binarizer will call BitArray.clear(). Always use the returned object.
* @return The array of bits for this row (true means black).
*/
public abstract BitArray getBlackRow(int y, BitArray row) throws NotFoundException;
/**
* Converts a 2D array of luminance data to 1 bit data. As above, assume this method is expensive
* and do not call it repeatedly. This method is intended for decoding 2D barcodes and may or
* may not apply sharpening. Therefore, a row from this matrix may not be identical to one
* fetched using getBlackRow(), so don't mix and match between them.
*
* @return The 2D array of bits for the image (true means black).
*/
public abstract BitMatrix getBlackMatrix() throws NotFoundException;
/**
* Creates a new object with the same type as this Binarizer implementation, but with pristine
* state. This is needed because Binarizer implementations may be stateful, e.g. keeping a cache
* of 1 bit data. See Effective Java for why we can't use Java's clone() method.
*
* @param source The LuminanceSource this Binarizer will operate on.
* @return A new concrete Binarizer implementation object.
*/
public abstract Binarizer createBinarizer(LuminanceSource source);
}

View File

@ -0,0 +1,128 @@
/*
* Copyright 2009 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing;
import com.google.zxing.common.BitArray;
import com.google.zxing.common.BitMatrix;
/**
* This class is the core bitmap class used by ZXing to represent 1 bit data. Reader objects
* accept a BinaryBitmap and attempt to decode it.
*
* @author dswitkin@google.com (Daniel Switkin)
*/
public final class BinaryBitmap {
private final Binarizer binarizer;
private BitMatrix matrix;
public BinaryBitmap(Binarizer binarizer) {
if (binarizer == null) {
throw new IllegalArgumentException("Binarizer must be non-null.");
}
this.binarizer = binarizer;
matrix = null;
}
/**
* @return The width of the bitmap.
*/
public int getWidth() {
return binarizer.getLuminanceSource().getWidth();
}
/**
* @return The height of the bitmap.
*/
public int getHeight() {
return binarizer.getLuminanceSource().getHeight();
}
/**
* Converts one row of luminance data to 1 bit data. May actually do the conversion, or return
* cached data. Callers should assume this method is expensive and call it as seldom as possible.
* This method is intended for decoding 1D barcodes and may choose to apply sharpening.
*
* @param y The row to fetch, 0 <= y < bitmap height.
* @param row An optional preallocated array. If null or too small, it will be ignored.
* If used, the Binarizer will call BitArray.clear(). Always use the returned object.
* @return The array of bits for this row (true means black).
*/
public BitArray getBlackRow(int y, BitArray row) throws NotFoundException {
return binarizer.getBlackRow(y, row);
}
/**
* Converts a 2D array of luminance data to 1 bit. As above, assume this method is expensive
* and do not call it repeatedly. This method is intended for decoding 2D barcodes and may or
* may not apply sharpening. Therefore, a row from this matrix may not be identical to one
* fetched using getBlackRow(), so don't mix and match between them.
*
* @return The 2D array of bits for the image (true means black).
*/
public BitMatrix getBlackMatrix() throws NotFoundException {
// The matrix is created on demand the first time it is requested, then cached. There are two
// reasons for this:
// 1. This work will never be done if the caller only installs 1D Reader objects, or if a
// 1D Reader finds a barcode before the 2D Readers run.
// 2. This work will only be done once even if the caller installs multiple 2D Readers.
if (matrix == null) {
matrix = binarizer.getBlackMatrix();
}
return matrix;
}
/**
* @return Whether this bitmap can be cropped.
*/
public boolean isCropSupported() {
return binarizer.getLuminanceSource().isCropSupported();
}
/**
* Returns a new object with cropped image data. Implementations may keep a reference to the
* original data rather than a copy. Only callable if isCropSupported() is true.
*
* @param left The left coordinate, 0 <= left < getWidth().
* @param top The top coordinate, 0 <= top <= getHeight().
* @param width The width of the rectangle to crop.
* @param height The height of the rectangle to crop.
* @return A cropped version of this object.
*/
public BinaryBitmap crop(int left, int top, int width, int height) {
LuminanceSource newSource = binarizer.getLuminanceSource().crop(left, top, width, height);
return new BinaryBitmap(binarizer.createBinarizer(newSource));
}
/**
* @return Whether this bitmap supports counter-clockwise rotation.
*/
public boolean isRotateSupported() {
return binarizer.getLuminanceSource().isRotateSupported();
}
/**
* Returns a new object with rotated image data. Only callable if isRotateSupported() is true.
*
* @return A rotated version of this object.
*/
public BinaryBitmap rotateCounterClockwise() {
LuminanceSource newSource = binarizer.getLuminanceSource().rotateCounterClockwise();
return new BinaryBitmap(binarizer.createBinarizer(newSource));
}
}

View File

@ -0,0 +1,37 @@
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing;
/**
* Thrown when a barcode was successfully detected and decoded, but
* was not returned because its checksum feature failed.
*
* @author Sean Owen
*/
public final class ChecksumException extends ReaderException {
private static final ChecksumException instance = new ChecksumException();
private ChecksumException() {
// do nothing
}
public static ChecksumException getChecksumInstance() {
return instance;
}
}

View File

@ -0,0 +1,79 @@
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing;
/**
* Encapsulates a type of hint that a caller may pass to a barcode reader to help it
* more quickly or accurately decode it. It is up to implementations to decide what,
* if anything, to do with the information that is supplied.
*
* @author Sean Owen
* @author dswitkin@google.com (Daniel Switkin)
* @see Reader#decode(BinaryBitmap,java.util.Hashtable)
*/
public final class DecodeHintType {
// No, we can't use an enum here. J2ME doesn't support it.
/**
* Unspecified, application-specific hint. Maps to an unspecified {@link Object}.
*/
public static final DecodeHintType OTHER = new DecodeHintType();
/**
* Image is a pure monochrome image of a barcode. Doesn't matter what it maps to;
* use {@link Boolean#TRUE}.
*/
public static final DecodeHintType PURE_BARCODE = new DecodeHintType();
/**
* Image is known to be of one of a few possible formats.
* Maps to a {@link java.util.Vector} of {@link BarcodeFormat}s.
*/
public static final DecodeHintType POSSIBLE_FORMATS = new DecodeHintType();
/**
* Spend more time to try to find a barcode; optimize for accuracy, not speed.
* Doesn't matter what it maps to; use {@link Boolean#TRUE}.
*/
public static final DecodeHintType TRY_HARDER = new DecodeHintType();
/**
* Specifies what character encoding to use when decoding, where applicable (type String)
*/
public static final DecodeHintType CHARACTER_SET = new DecodeHintType();
/**
* Allowed lengths of encoded data -- reject anything else. Maps to an int[].
*/
public static final DecodeHintType ALLOWED_LENGTHS = new DecodeHintType();
/**
* Assume Code 39 codes employ a check digit. Maps to {@link Boolean}.
*/
public static final DecodeHintType ASSUME_CODE_39_CHECK_DIGIT = new DecodeHintType();
/**
* The caller needs to be notified via callback when a possible {@link ResultPoint}
* is found. Maps to a {@link ResultPointCallback}.
*/
public static final DecodeHintType NEED_RESULT_POINT_CALLBACK = new DecodeHintType();
private DecodeHintType() {
}
}

View File

@ -0,0 +1,39 @@
/*
* Copyright 2008 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing;
/**
* These are a set of hints that you may pass to Writers to specify their behavior.
*
* @author dswitkin@google.com (Daniel Switkin)
*/
public final class EncodeHintType {
/**
* Specifies what degree of error correction to use, for example in QR Codes (type Integer).
*/
public static final EncodeHintType ERROR_CORRECTION = new EncodeHintType();
/**
* Specifies what character encoding to use where applicable (type String)
*/
public static final EncodeHintType CHARACTER_SET = new EncodeHintType();
private EncodeHintType() {
}
}

View File

@ -0,0 +1,38 @@
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing;
/**
* Thrown when a barcode was successfully detected, but some aspect of
* the content did not conform to the barcode's format rules. This could have
* been due to a mis-detection.
*
* @author Sean Owen
*/
public final class FormatException extends ReaderException {
private static final FormatException instance = new FormatException();
private FormatException() {
// do nothing
}
public static FormatException getFormatInstance() {
return instance;
}
}

View File

@ -0,0 +1,113 @@
/*
* Copyright 2009 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing;
/**
* The purpose of this class hierarchy is to abstract different bitmap implementations across
* platforms into a standard interface for requesting greyscale luminance values. The interface
* only provides immutable methods; therefore crop and rotation create copies. This is to ensure
* that one Reader does not modify the original luminance source and leave it in an unknown state
* for other Readers in the chain.
*
* @author dswitkin@google.com (Daniel Switkin)
*/
public abstract class LuminanceSource {
private final int width;
private final int height;
protected LuminanceSource(int width, int height) {
this.width = width;
this.height = height;
}
/**
* Fetches one row of luminance data from the underlying platform's bitmap. Values range from
* 0 (black) to 255 (white). Because Java does not have an unsigned byte type, callers will have
* to bitwise and with 0xff for each value. It is preferable for implementations of this method
* to only fetch this row rather than the whole image, since no 2D Readers may be installed and
* getMatrix() may never be called.
*
* @param y The row to fetch, 0 <= y < getHeight().
* @param row An optional preallocated array. If null or too small, it will be ignored.
* Always use the returned object, and ignore the .length of the array.
* @return An array containing the luminance data.
*/
public abstract byte[] getRow(int y, byte[] row);
/**
* Fetches luminance data for the underlying bitmap. Values should be fetched using:
* int luminance = array[y * width + x] & 0xff;
*
* @return A row-major 2D array of luminance values. Do not use result.length as it may be
* larger than width * height bytes on some platforms. Do not modify the contents
* of the result.
*/
public abstract byte[] getMatrix();
/**
* @return The width of the bitmap.
*/
public final int getWidth() {
return width;
}
/**
* @return The height of the bitmap.
*/
public final int getHeight() {
return height;
}
/**
* @return Whether this subclass supports cropping.
*/
public boolean isCropSupported() {
return false;
}
/**
* Returns a new object with cropped image data. Implementations may keep a reference to the
* original data rather than a copy. Only callable if isCropSupported() is true.
*
* @param left The left coordinate, 0 <= left < getWidth().
* @param top The top coordinate, 0 <= top <= getHeight().
* @param width The width of the rectangle to crop.
* @param height The height of the rectangle to crop.
* @return A cropped version of this object.
*/
public LuminanceSource crop(int left, int top, int width, int height) {
throw new RuntimeException("This luminance source does not support cropping.");
}
/**
* @return Whether this subclass supports counter-clockwise rotation.
*/
public boolean isRotateSupported() {
return false;
}
/**
* Returns a new object with rotated image data. Only callable if isRotateSupported() is true.
*
* @return A rotated version of this object.
*/
public LuminanceSource rotateCounterClockwise() {
throw new RuntimeException("This luminance source does not support rotation.");
}
}

View File

@ -0,0 +1,166 @@
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing;
import com.google.zxing.datamatrix.DataMatrixReader;
import com.google.zxing.oned.MultiFormatOneDReader;
import com.google.zxing.pdf417.PDF417Reader;
import com.google.zxing.qrcode.QRCodeReader;
import java.util.Hashtable;
import java.util.Vector;
/**
* MultiFormatReader is a convenience class and the main entry point into the library for most uses.
* By default it attempts to decode all barcode formats that the library supports. Optionally, you
* can provide a hints object to request different behavior, for example only decoding QR codes.
*
* @author Sean Owen
* @author dswitkin@google.com (Daniel Switkin)
*/
public final class MultiFormatReader implements Reader {
private Hashtable hints;
private Vector readers;
/**
* This version of decode honors the intent of Reader.decode(BinaryBitmap) in that it
* passes null as a hint to the decoders. However, that makes it inefficient to call repeatedly.
* Use setHints() followed by decodeWithState() for continuous scan applications.
*
* @param image The pixel data to decode
* @return The contents of the image
* @throws NotFoundException Any errors which occurred
*/
public Result decode(BinaryBitmap image) throws NotFoundException {
setHints(null);
return decodeInternal(image);
}
/**
* Decode an image using the hints provided. Does not honor existing state.
*
* @param image The pixel data to decode
* @param hints The hints to use, clearing the previous state.
* @return The contents of the image
* @throws NotFoundException Any errors which occurred
*/
public Result decode(BinaryBitmap image, Hashtable hints) throws NotFoundException {
setHints(hints);
return decodeInternal(image);
}
/**
* Decode an image using the state set up by calling setHints() previously. Continuous scan
* clients will get a <b>large</b> speed increase by using this instead of decode().
*
* @param image The pixel data to decode
* @return The contents of the image
* @throws NotFoundException Any errors which occurred
*/
public Result decodeWithState(BinaryBitmap image) throws NotFoundException {
// Make sure to set up the default state so we don't crash
if (readers == null) {
setHints(null);
}
return decodeInternal(image);
}
/**
* This method adds state to the MultiFormatReader. By setting the hints once, subsequent calls
* to decodeWithState(image) can reuse the same set of readers without reallocating memory. This
* is important for performance in continuous scan clients.
*
* @param hints The set of hints to use for subsequent calls to decode(image)
*/
public void setHints(Hashtable hints) {
this.hints = hints;
boolean tryHarder = hints != null && hints.containsKey(DecodeHintType.TRY_HARDER);
Vector formats = hints == null ? null : (Vector) hints.get(DecodeHintType.POSSIBLE_FORMATS);
readers = new Vector();
if (formats != null) {
boolean addOneDReader =
formats.contains(BarcodeFormat.UPC_A) ||
formats.contains(BarcodeFormat.UPC_E) ||
formats.contains(BarcodeFormat.EAN_13) ||
formats.contains(BarcodeFormat.EAN_8) ||
//formats.contains(BarcodeFormat.CODABAR) ||
formats.contains(BarcodeFormat.CODE_39) ||
formats.contains(BarcodeFormat.CODE_93) ||
formats.contains(BarcodeFormat.CODE_128) ||
formats.contains(BarcodeFormat.ITF) ||
formats.contains(BarcodeFormat.RSS14) ||
formats.contains(BarcodeFormat.RSS_EXPANDED);
// Put 1D readers upfront in "normal" mode
if (addOneDReader && !tryHarder) {
readers.addElement(new MultiFormatOneDReader(hints));
}
if (formats.contains(BarcodeFormat.QR_CODE)) {
readers.addElement(new QRCodeReader());
}
if (formats.contains(BarcodeFormat.DATA_MATRIX)) {
readers.addElement(new DataMatrixReader());
}
if (formats.contains(BarcodeFormat.PDF417)) {
readers.addElement(new PDF417Reader());
}
// At end in "try harder" mode
if (addOneDReader && tryHarder) {
readers.addElement(new MultiFormatOneDReader(hints));
}
}
if (readers.isEmpty()) {
if (!tryHarder) {
readers.addElement(new MultiFormatOneDReader(hints));
}
readers.addElement(new QRCodeReader());
readers.addElement(new DataMatrixReader());
// TODO: Enable once PDF417 has passed QA
//readers.addElement(new PDF417Reader());
if (tryHarder) {
readers.addElement(new MultiFormatOneDReader(hints));
}
}
}
public void reset() {
int size = readers.size();
for (int i = 0; i < size; i++) {
Reader reader = (Reader) readers.elementAt(i);
reader.reset();
}
}
private Result decodeInternal(BinaryBitmap image) throws NotFoundException {
int size = readers.size();
for (int i = 0; i < size; i++) {
Reader reader = (Reader) readers.elementAt(i);
try {
return reader.decode(image, hints);
} catch (ReaderException re) {
// continue
}
}
throw NotFoundException.getNotFoundInstance();
}
}

View File

@ -0,0 +1,65 @@
/*
* Copyright 2008 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.oned.Code128Writer;
import com.google.zxing.oned.Code39Writer;
import com.google.zxing.oned.EAN13Writer;
import com.google.zxing.oned.EAN8Writer;
import com.google.zxing.oned.ITFWriter;
import com.google.zxing.qrcode.QRCodeWriter;
import java.util.Hashtable;
/**
* This is a factory class which finds the appropriate Writer subclass for the BarcodeFormat
* requested and encodes the barcode with the supplied contents.
*
* @author dswitkin@google.com (Daniel Switkin)
*/
public final class MultiFormatWriter implements Writer {
public BitMatrix encode(String contents, BarcodeFormat format, int width,
int height) throws WriterException {
return encode(contents, format, width, height, null);
}
public BitMatrix encode(String contents, BarcodeFormat format, int width, int height,
Hashtable hints) throws WriterException {
Writer writer;
if (format == BarcodeFormat.EAN_8) {
writer = new EAN8Writer();
} else if (format == BarcodeFormat.EAN_13) {
writer = new EAN13Writer();
} else if (format == BarcodeFormat.QR_CODE) {
writer = new QRCodeWriter();
} else if (format == BarcodeFormat.CODE_39) {
writer = new Code39Writer();
} else if (format == BarcodeFormat.CODE_128) {
writer = new Code128Writer();
} else if (format == BarcodeFormat.ITF) {
writer = new ITFWriter();
} else {
throw new IllegalArgumentException("No encoder available for format " + format);
}
return writer.encode(contents, format, width, height, hints);
}
}

View File

@ -0,0 +1,37 @@
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing;
/**
* Thrown when a barcode was not found in the image. It might have been
* partially detected but could not be confirmed.
*
* @author Sean Owen
*/
public final class NotFoundException extends ReaderException {
private static final NotFoundException instance = new NotFoundException();
private NotFoundException() {
// do nothing
}
public static NotFoundException getNotFoundInstance() {
return instance;
}
}

View File

@ -0,0 +1,64 @@
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing;
import java.util.Hashtable;
/**
* Implementations of this interface can decode an image of a barcode in some format into
* the String it encodes. For example, {@link com.google.zxing.qrcode.QRCodeReader} can
* decode a QR code. The decoder may optionally receive hints from the caller which may help
* it decode more quickly or accurately.
*
* See {@link com.google.zxing.MultiFormatReader}, which attempts to determine what barcode
* format is present within the image as well, and then decodes it accordingly.
*
* @author Sean Owen
* @author dswitkin@google.com (Daniel Switkin)
*/
public interface Reader {
/**
* Locates and decodes a barcode in some format within an image.
*
* @param image image of barcode to decode
* @return String which the barcode encodes
* @throws NotFoundException if the barcode cannot be located or decoded for any reason
*/
Result decode(BinaryBitmap image) throws NotFoundException, ChecksumException, FormatException;
/**
* Locates and decodes a barcode in some format within an image. This method also accepts
* hints, each possibly associated to some data, which may help the implementation decode.
*
* @param image image of barcode to decode
* @param hints passed as a {@link java.util.Hashtable} from {@link com.google.zxing.DecodeHintType}
* to arbitrary data. The
* meaning of the data depends upon the hint type. The implementation may or may not do
* anything with these hints.
* @return String which the barcode encodes
* @throws NotFoundException if the barcode cannot be located or decoded for any reason
*/
Result decode(BinaryBitmap image, Hashtable hints) throws NotFoundException, ChecksumException, FormatException;
/**
* Resets any internal state the implementation has after a decode, to prepare it
* for reuse.
*/
void reset();
}

View File

@ -0,0 +1,98 @@
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing;
/**
* The general exception class throw when something goes wrong during decoding of a barcode.
* This includes, but is not limited to, failing checksums / error correction algorithms, being
* unable to locate finder timing patterns, and so on.
*
* @author Sean Owen
*/
public abstract class ReaderException extends Exception {
// TODO: Currently we throw up to 400 ReaderExceptions while scanning a single 240x240 image before
// rejecting it. This involves a lot of overhead and memory allocation, and affects both performance
// and latency on continuous scan clients. In the future, we should change all the decoders not to
// throw exceptions for routine events, like not finding a barcode on a given row. Instead, we
// should return error codes back to the callers, and simply delete this class. In the mean time, I
// have altered this class to be as lightweight as possible, by ignoring the exception string, and
// by disabling the generation of stack traces, which is especially time consuming. These are just
// temporary measures, pending the big cleanup.
//private static final ReaderException instance = new ReaderException();
// EXCEPTION TRACKING SUPPORT
// Identifies who is throwing exceptions and how often. To use:
//
// 1. Uncomment these lines and the code below which uses them.
// 2. Uncomment the two corresponding lines in j2se/CommandLineRunner.decode()
// 3. Change core to build as Java 1.5 temporarily
// private static int exceptionCount = 0;
// private static Map<String,Integer> throwers = new HashMap<String,Integer>(32);
ReaderException() {
// do nothing
}
//public static ReaderException getInstance() {
// Exception e = new Exception();
// // Take the stack frame before this one.
// StackTraceElement stack = e.getStackTrace()[1];
// String key = stack.getClassName() + "." + stack.getMethodName() + "(), line " +
// stack.getLineNumber();
// if (throwers.containsKey(key)) {
// Integer value = throwers.get(key);
// value++;
// throwers.put(key, value);
// } else {
// throwers.put(key, 1);
// }
// exceptionCount++;
//return instance;
//}
// public static int getExceptionCountAndReset() {
// int temp = exceptionCount;
// exceptionCount = 0;
// return temp;
// }
//
// public static String getThrowersAndReset() {
// StringBuilder builder = new StringBuilder(1024);
// Object[] keys = throwers.keySet().toArray();
// for (int x = 0; x < keys.length; x++) {
// String key = (String) keys[x];
// Integer value = throwers.get(key);
// builder.append(key);
// builder.append(": ");
// builder.append(value);
// builder.append("\n");
// }
// throwers.clear();
// return builder.toString();
// }
// Prevent stack traces from being taken
// srowen says: huh, my IDE is saying this is not an override. native methods can't be overridden?
// This, at least, does not hurt. Because we use a singleton pattern here, it doesn't matter anyhow.
public final Throwable fillInStackTrace() {
return null;
}
}

View File

@ -0,0 +1,143 @@
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing;
import java.util.Enumeration;
import java.util.Hashtable;
/**
* <p>Encapsulates the result of decoding a barcode within an image.</p>
*
* @author Sean Owen
*/
public final class Result {
private final String text;
private final byte[] rawBytes;
private ResultPoint[] resultPoints;
private final BarcodeFormat format;
private Hashtable resultMetadata;
private final long timestamp;
public Result(String text,
byte[] rawBytes,
ResultPoint[] resultPoints,
BarcodeFormat format) {
this(text, rawBytes, resultPoints, format, System.currentTimeMillis());
}
public Result(String text,
byte[] rawBytes,
ResultPoint[] resultPoints,
BarcodeFormat format,
long timestamp) {
if (text == null && rawBytes == null) {
throw new IllegalArgumentException("Text and bytes are null");
}
this.text = text;
this.rawBytes = rawBytes;
this.resultPoints = resultPoints;
this.format = format;
this.resultMetadata = null;
this.timestamp = timestamp;
}
/**
* @return raw text encoded by the barcode, if applicable, otherwise <code>null</code>
*/
public String getText() {
return text;
}
/**
* @return raw bytes encoded by the barcode, if applicable, otherwise <code>null</code>
*/
public byte[] getRawBytes() {
return rawBytes;
}
/**
* @return points related to the barcode in the image. These are typically points
* identifying finder patterns or the corners of the barcode. The exact meaning is
* specific to the type of barcode that was decoded.
*/
public ResultPoint[] getResultPoints() {
return resultPoints;
}
/**
* @return {@link BarcodeFormat} representing the format of the barcode that was decoded
*/
public BarcodeFormat getBarcodeFormat() {
return format;
}
/**
* @return {@link Hashtable} mapping {@link ResultMetadataType} keys to values. May be
* <code>null</code>. This contains optional metadata about what was detected about the barcode,
* like orientation.
*/
public Hashtable getResultMetadata() {
return resultMetadata;
}
public void putMetadata(ResultMetadataType type, Object value) {
if (resultMetadata == null) {
resultMetadata = new Hashtable(3);
}
resultMetadata.put(type, value);
}
public void putAllMetadata(Hashtable metadata) {
if (metadata != null) {
if (resultMetadata == null) {
resultMetadata = metadata;
} else {
Enumeration e = metadata.keys();
while (e.hasMoreElements()) {
ResultMetadataType key = (ResultMetadataType) e.nextElement();
Object value = metadata.get(key);
resultMetadata.put(key, value);
}
}
}
}
public void addResultPoints(ResultPoint[] newPoints) {
if (resultPoints == null) {
resultPoints = newPoints;
} else if (newPoints != null && newPoints.length > 0) {
ResultPoint[] allPoints = new ResultPoint[resultPoints.length + newPoints.length];
System.arraycopy(resultPoints, 0, allPoints, 0, resultPoints.length);
System.arraycopy(newPoints, 0, allPoints, resultPoints.length, newPoints.length);
resultPoints = allPoints;
}
}
public long getTimestamp() {
return timestamp;
}
public String toString() {
if (text == null) {
return "[" + rawBytes.length + " bytes]";
} else {
return text;
}
}
}

View File

@ -0,0 +1,109 @@
/*
* Copyright 2008 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing;
import java.util.Hashtable;
/**
* Represents some type of metadata about the result of the decoding that the decoder
* wishes to communicate back to the caller.
*
* @author Sean Owen
*/
public final class ResultMetadataType {
// No, we can't use an enum here. J2ME doesn't support it.
private static final Hashtable VALUES = new Hashtable();
// No, we can't use an enum here. J2ME doesn't support it.
/**
* Unspecified, application-specific metadata. Maps to an unspecified {@link Object}.
*/
public static final ResultMetadataType OTHER = new ResultMetadataType("OTHER");
/**
* Denotes the likely approximate orientation of the barcode in the image. This value
* is given as degrees rotated clockwise from the normal, upright orientation.
* For example a 1D barcode which was found by reading top-to-bottom would be
* said to have orientation "90". This key maps to an {@link Integer} whose
* value is in the range [0,360).
*/
public static final ResultMetadataType ORIENTATION = new ResultMetadataType("ORIENTATION");
/**
* <p>2D barcode formats typically encode text, but allow for a sort of 'byte mode'
* which is sometimes used to encode binary data. While {@link Result} makes available
* the complete raw bytes in the barcode for these formats, it does not offer the bytes
* from the byte segments alone.</p>
*
* <p>This maps to a {@link java.util.Vector} of byte arrays corresponding to the
* raw bytes in the byte segments in the barcode, in order.</p>
*/
public static final ResultMetadataType BYTE_SEGMENTS = new ResultMetadataType("BYTE_SEGMENTS");
/**
* Error correction level used, if applicable. The value type depends on the
* format, but is typically a String.
*/
public static final ResultMetadataType ERROR_CORRECTION_LEVEL = new ResultMetadataType("ERROR_CORRECTION_LEVEL");
/**
* For some periodicals, indicates the issue number as an {@link Integer}.
*/
public static final ResultMetadataType ISSUE_NUMBER = new ResultMetadataType("ISSUE_NUMBER");
/**
* For some products, indicates the suggested retail price in the barcode as a
* formatted {@link String}.
*/
public static final ResultMetadataType SUGGESTED_PRICE = new ResultMetadataType("SUGGESTED_PRICE");
/**
* For some products, the possible country of manufacture as a {@link String} denoting the
* ISO country code. Some map to multiple possible countries, like "US/CA".
*/
public static final ResultMetadataType POSSIBLE_COUNTRY = new ResultMetadataType("POSSIBLE_COUNTRY");
private final String name;
private ResultMetadataType(String name) {
this.name = name;
VALUES.put(name, this);
}
public String getName() {
return name;
}
public String toString() {
return name;
}
public static ResultMetadataType valueOf(String name) {
if (name == null || name.length() == 0) {
throw new IllegalArgumentException();
}
ResultMetadataType format = (ResultMetadataType) VALUES.get(name);
if (format == null) {
throw new IllegalArgumentException();
}
return format;
}
}

View File

@ -0,0 +1,127 @@
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing;
/**
* <p>Encapsulates a point of interest in an image containing a barcode. Typically, this
* would be the location of a finder pattern or the corner of the barcode, for example.</p>
*
* @author Sean Owen
*/
public class ResultPoint {
private final float x;
private final float y;
public ResultPoint(float x, float y) {
this.x = x;
this.y = y;
}
public final float getX() {
return x;
}
public final float getY() {
return y;
}
public boolean equals(Object other) {
if (other instanceof ResultPoint) {
ResultPoint otherPoint = (ResultPoint) other;
return x == otherPoint.x && y == otherPoint.y;
}
return false;
}
public int hashCode() {
return 31 * Float.floatToIntBits(x) + Float.floatToIntBits(y);
}
public String toString() {
StringBuffer result = new StringBuffer(25);
result.append('(');
result.append(x);
result.append(',');
result.append(y);
result.append(')');
return result.toString();
}
/**
* <p>Orders an array of three ResultPoints in an order [A,B,C] such that AB < AC and
* BC < AC and the angle between BC and BA is less than 180 degrees.
*/
public static void orderBestPatterns(ResultPoint[] patterns) {
// Find distances between pattern centers
float zeroOneDistance = distance(patterns[0], patterns[1]);
float oneTwoDistance = distance(patterns[1], patterns[2]);
float zeroTwoDistance = distance(patterns[0], patterns[2]);
ResultPoint pointA, pointB, pointC;
// Assume one closest to other two is B; A and C will just be guesses at first
if (oneTwoDistance >= zeroOneDistance && oneTwoDistance >= zeroTwoDistance) {
pointB = patterns[0];
pointA = patterns[1];
pointC = patterns[2];
} else if (zeroTwoDistance >= oneTwoDistance && zeroTwoDistance >= zeroOneDistance) {
pointB = patterns[1];
pointA = patterns[0];
pointC = patterns[2];
} else {
pointB = patterns[2];
pointA = patterns[0];
pointC = patterns[1];
}
// Use cross product to figure out whether A and C are correct or flipped.
// This asks whether BC x BA has a positive z component, which is the arrangement
// we want for A, B, C. If it's negative, then we've got it flipped around and
// should swap A and C.
if (crossProductZ(pointA, pointB, pointC) < 0.0f) {
ResultPoint temp = pointA;
pointA = pointC;
pointC = temp;
}
patterns[0] = pointA;
patterns[1] = pointB;
patterns[2] = pointC;
}
/**
* @return distance between two points
*/
public static float distance(ResultPoint pattern1, ResultPoint pattern2) {
float xDiff = pattern1.getX() - pattern2.getX();
float yDiff = pattern1.getY() - pattern2.getY();
return (float) Math.sqrt((double) (xDiff * xDiff + yDiff * yDiff));
}
/**
* Returns the z component of the cross product between vectors BC and BA.
*/
private static float crossProductZ(ResultPoint pointA, ResultPoint pointB, ResultPoint pointC) {
float bX = pointB.x;
float bY = pointB.y;
return ((pointC.x - bX) * (pointA.y - bY)) - ((pointC.y - bY) * (pointA.x - bX));
}
}

View File

@ -0,0 +1,29 @@
/*
* Copyright 2009 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing;
/**
* Callback which is invoked when a possible result point (significant
* point in the barcode image such as a corner) is found.
*
* @see DecodeHintType#NEED_RESULT_POINT_CALLBACK
*/
public interface ResultPointCallback {
void foundPossibleResultPoint(ResultPoint point);
}

View File

@ -0,0 +1,219 @@
/*
* Copyright 2010 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing;
import java.io.BufferedReader;
import java.io.Closeable;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* <p>A utility which auto-translates English strings in Android string resources using
* Google Translate.</p>
*
* <p>Pass the Android client res/ directory as first argument, and optionally message keys
* who should be forced to retranslate.
* Usage: <code>StringsResourceTranslator android/res/ [key_1 ...]</p>
*
* @author Sean Owen
*/
public final class StringsResourceTranslator {
private static final Charset UTF8 = Charset.forName("UTF-8");
private static final Pattern ENTRY_PATTERN = Pattern.compile("<string name=\"([^\"]+)\">([^<]+)</string>");
private static final Pattern STRINGS_FILE_NAME_PATTERN = Pattern.compile("values-(.+)");
private static final Pattern TRANSLATE_RESPONSE_PATTERN = Pattern.compile(
"\\{\"translatedText\":\"([^\"]+)\"\\}");
private static final String APACHE_2_LICENSE =
"<!--\n" +
" Copyright (C) 2010 ZXing authors\n" +
'\n' +
" Licensed under the Apache License, Version 2.0 (the \"License\");\n" +
" you may not use this file except in compliance with the License.\n" +
" You may obtain a copy of the License at\n" +
'\n' +
" http://www.apache.org/licenses/LICENSE-2.0\n" +
'\n' +
" Unless required by applicable law or agreed to in writing, software\n" +
" distributed under the License is distributed on an \"AS IS\" BASIS,\n" +
" WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n" +
" See the License for the specific language governing permissions and\n" +
" limitations under the License.\n" +
" -->\n";
private static final Map<String,String> LANGUAGE_CODE_MASSAGINGS = new HashMap<String,String>(4);
static {
LANGUAGE_CODE_MASSAGINGS.put("ja-rJP", "ja");
LANGUAGE_CODE_MASSAGINGS.put("zh-rCN", "zh-cn");
LANGUAGE_CODE_MASSAGINGS.put("zh-rTW", "zh-tw");
}
private StringsResourceTranslator() {}
public static void main(String[] args) throws IOException {
File resDir = new File(args[0]);
File valueDir = new File(resDir, "values");
File stringsFile = new File(valueDir, "strings.xml");
Collection<String> forceRetranslation = Arrays.asList(args).subList(1, args.length);
File[] translatedValuesDirs = resDir.listFiles(new FileFilter() {
public boolean accept(File file) {
return file.isDirectory() && file.getName().startsWith("values-");
}
});
for (File translatedValuesDir : translatedValuesDirs) {
File translatedStringsFile = new File(translatedValuesDir, "strings.xml");
translate(stringsFile, translatedStringsFile, forceRetranslation);
}
}
private static void translate(File englishFile, File translatedFile, Collection<String> forceRetranslation)
throws IOException {
SortedMap<String,String> english = readLines(englishFile);
SortedMap<String,String> translated = readLines(translatedFile);
String parentName = translatedFile.getParentFile().getName();
Matcher stringsFileNameMatcher = STRINGS_FILE_NAME_PATTERN.matcher(parentName);
stringsFileNameMatcher.find();
String language = stringsFileNameMatcher.group(1);
String massagedLanguage = LANGUAGE_CODE_MASSAGINGS.get(language);
if (massagedLanguage != null) {
language = massagedLanguage;
}
System.out.println("Translating " + language);
File resultTempFile = File.createTempFile(parentName, ".xml");
boolean anyChange = false;
Writer out = null;
try {
out = new OutputStreamWriter(new FileOutputStream(resultTempFile), UTF8);
out.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
out.write(APACHE_2_LICENSE);
out.write("<resources>\n");
for (Map.Entry<String,String> englishEntry : english.entrySet()) {
String key = englishEntry.getKey();
out.write(" <string name=\"");
out.write(key);
out.write("\">");
String translatedString = translated.get(key);
if (translatedString == null || forceRetranslation.contains(key)) {
anyChange = true;
translatedString = translateString(englishEntry.getValue(), language);
}
out.write(translatedString);
out.write("</string>\n");
}
out.write("</resources>\n");
out.flush();
} finally {
quietClose(out);
}
if (anyChange) {
System.out.println(" Writing translations");
translatedFile.delete();
resultTempFile.renameTo(translatedFile);
}
}
private static String translateString(String english, String language) throws IOException {
System.out.println(" Need translation for " + english);
URL translateURL = new URL(
"http://ajax.googleapis.com/ajax/services/language/translate?v=1.0&q=" +
URLEncoder.encode(english, "UTF-8") +
"&langpair=en%7C" + language);
StringBuilder translateResult = new StringBuilder();
Reader in = null;
try {
in = new InputStreamReader(translateURL.openStream(), UTF8);
char[] buffer = new char[1024];
int charsRead;
while ((charsRead = in.read(buffer)) > 0) {
translateResult.append(buffer, 0, charsRead);
}
} finally {
quietClose(in);
}
Matcher m = TRANSLATE_RESPONSE_PATTERN.matcher(translateResult);
if (!m.find()) {
throw new IOException("No translate result");
}
String translation = m.group(1);
System.out.println(" Got translation " + translation);
return translation;
}
private static SortedMap<String,String> readLines(File file) throws IOException {
SortedMap<String,String> entries = new TreeMap<String,String>();
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), UTF8));
String line;
while ((line = reader.readLine()) != null) {
Matcher m = ENTRY_PATTERN.matcher(line);
if (m.find()) {
String key = m.group(1);
String value = m.group(2);
entries.put(key, value);
}
}
return entries;
} finally {
quietClose(reader);
}
}
private static void quietClose(Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (IOException ioe) {
// continue
}
}
}
}

View File

@ -0,0 +1,54 @@
/*
* Copyright 2008 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing;
import com.google.zxing.common.BitMatrix;
import java.util.Hashtable;
/**
* The base class for all objects which encode/generate a barcode image.
*
* @author dswitkin@google.com (Daniel Switkin)
*/
public interface Writer {
/**
* Encode a barcode using the default settings.
*
* @param contents The contents to encode in the barcode
* @param format The barcode format to generate
* @param width The preferred width in pixels
* @param height The preferred height in pixels
* @return The generated barcode as a Matrix of unsigned bytes (0 == black, 255 == white)
*/
BitMatrix encode(String contents, BarcodeFormat format, int width, int height)
throws WriterException;
/**
*
* @param contents The contents to encode in the barcode
* @param format The barcode format to generate
* @param width The preferred width in pixels
* @param height The preferred height in pixels
* @param hints Additional parameters to supply to the encoder
* @return The generated barcode as a Matrix of unsigned bytes (0 == black, 255 == white)
*/
BitMatrix encode(String contents, BarcodeFormat format, int width, int height, Hashtable hints)
throws WriterException;
}

View File

@ -0,0 +1,35 @@
/*
* Copyright 2008 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing;
/**
* A base class which covers the range of exceptions which may occur when encoding a barcode using
* the Writer framework.
*
* @author dswitkin@google.com (Daniel Switkin)
*/
public final class WriterException extends Exception {
public WriterException() {
super();
}
public WriterException(String message) {
super(message);
}
}

View File

@ -0,0 +1,148 @@
/*
* Copyright 2009 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.client.j2se;
import com.google.zxing.LuminanceSource;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.geom.AffineTransform;
/**
* This LuminanceSource implementation is meant for J2SE clients and our blackbox unit tests.
*
* @author dswitkin@google.com (Daniel Switkin)
* @author Sean Owen
*/
public final class BufferedImageLuminanceSource extends LuminanceSource {
private final BufferedImage image;
private final int left;
private final int top;
private int[] rgbData;
public BufferedImageLuminanceSource(BufferedImage image) {
this(image, 0, 0, image.getWidth(), image.getHeight());
}
public BufferedImageLuminanceSource(BufferedImage image, int left, int top, int width,
int height) {
super(width, height);
int sourceWidth = image.getWidth();
int sourceHeight = image.getHeight();
if (left + width > sourceWidth || top + height > sourceHeight) {
throw new IllegalArgumentException("Crop rectangle does not fit within image data.");
}
this.image = image;
this.left = left;
this.top = top;
}
// These methods use an integer calculation for luminance derived from:
// <code>Y = 0.299R + 0.587G + 0.114B</code>
@Override
public byte[] getRow(int y, byte[] row) {
if (y < 0 || y >= getHeight()) {
throw new IllegalArgumentException("Requested row is outside the image: " + y);
}
int width = getWidth();
if (row == null || row.length < width) {
row = new byte[width];
}
if (rgbData == null || rgbData.length < width) {
rgbData = new int[width];
}
image.getRGB(left, top + y, width, 1, rgbData, 0, width);
for (int x = 0; x < width; x++) {
int pixel = rgbData[x];
int luminance = (306 * ((pixel >> 16) & 0xFF) +
601 * ((pixel >> 8) & 0xFF) +
117 * (pixel & 0xFF) +
(0x200)) >> 10; // 0x200 = 1<<9, half an lsb of the result to force rounding
row[x] = (byte) luminance;
}
return row;
}
@Override
public byte[] getMatrix() {
int width = getWidth();
int height = getHeight();
int area = width * height;
byte[] matrix = new byte[area];
int[] rgb = new int[area];
image.getRGB(left, top, width, height, rgb, 0, width);
for (int y = 0; y < height; y++) {
int offset = y * width;
for (int x = 0; x < width; x++) {
int pixel = rgb[offset + x];
int luminance = (306 * ((pixel >> 16) & 0xFF) +
601 * ((pixel >> 8) & 0xFF) +
117 * (pixel & 0xFF) +
(0x200)) >> 10; // 0x200 = 1<<9, half an lsb of the result to force rounding
matrix[offset + x] = (byte) luminance;
}
}
return matrix;
}
@Override
public boolean isCropSupported() {
return true;
}
@Override
public LuminanceSource crop(int left, int top, int width, int height) {
return new BufferedImageLuminanceSource(image, this.left + left, this.top + top, width, height);
}
// Can't run AffineTransforms on images of unknown format.
@Override
public boolean isRotateSupported() {
return image.getType() != BufferedImage.TYPE_CUSTOM;
}
@Override
public LuminanceSource rotateCounterClockwise() {
if (!isRotateSupported()) {
throw new IllegalStateException("Rotate not supported");
}
int sourceWidth = image.getWidth();
int sourceHeight = image.getHeight();
// Rotate 90 degrees counterclockwise.
AffineTransform transform = new AffineTransform(0.0, -1.0, 1.0, 0.0, 0.0, sourceWidth);
// Note width/height are flipped since we are rotating 90 degrees.
BufferedImage rotatedImage = new BufferedImage(sourceHeight, sourceWidth, image.getType());
// Draw the original image into rotated, via transformation
Graphics2D g = rotatedImage.createGraphics();
g.drawImage(image, transform, null);
g.dispose();
// Maintain the cropped region, but rotate it too.
int width = getWidth();
return new BufferedImageLuminanceSource(rotatedImage, top, sourceWidth - (left + width),
getHeight(), width);
}
}

View File

@ -0,0 +1,358 @@
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.client.j2se;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.DecodeHintType;
import com.google.zxing.LuminanceSource;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.NotFoundException;
import com.google.zxing.Result;
import com.google.zxing.client.result.ParsedResult;
import com.google.zxing.client.result.ResultParser;
import com.google.zxing.common.BitArray;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.ResultPoint;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.util.Hashtable;
import java.util.Vector;
import javax.imageio.ImageIO;
/**
* <p>This simple command line utility decodes files, directories of files, or URIs which are passed
* as arguments. By default it uses the normal decoding algorithms, but you can pass --try_harder to
* request that hint. The raw text of each barcode is printed, and when running against directories,
* summary statistics are also displayed.</p>
*
* @author Sean Owen
* @author dswitkin@google.com (Daniel Switkin)
*/
public final class CommandLineRunner {
private CommandLineRunner() {
}
public static void main(String[] args) throws Exception {
if (args == null || args.length == 0) {
printUsage();
return;
}
boolean tryHarder = false;
boolean pureBarcode = false;
boolean productsOnly = false;
boolean dumpResults = false;
boolean dumpBlackPoint = false;
int[] crop = null;
for (String arg : args) {
if ("--try_harder".equals(arg)) {
tryHarder = true;
} else if ("--pure_barcode".equals(arg)) {
pureBarcode = true;
} else if ("--products_only".equals(arg)) {
productsOnly = true;
} else if ("--dump_results".equals(arg)) {
dumpResults = true;
} else if ("--dump_black_point".equals(arg)) {
dumpBlackPoint = true;
} else if (arg.startsWith("--crop")) {
crop = new int[4];
String[] tokens = arg.substring(7).split(",");
for (int i = 0; i < crop.length; i++) {
crop[i] = Integer.parseInt(tokens[i]);
}
} else if (arg.startsWith("-")) {
System.err.println("Unknown command line option " + arg);
printUsage();
return;
}
}
Hashtable<DecodeHintType, Object> hints = buildHints(tryHarder, pureBarcode, productsOnly);
for (String arg : args) {
if (!arg.startsWith("--")) {
decodeOneArgument(arg, hints, dumpResults, dumpBlackPoint, crop);
}
}
}
// Manually turn on all formats, even those not yet considered production quality.
private static Hashtable<DecodeHintType, Object> buildHints(boolean tryHarder,
boolean pureBarcode,
boolean productsOnly) {
Hashtable<DecodeHintType, Object> hints = new Hashtable<DecodeHintType, Object>(3);
Vector<BarcodeFormat> vector = new Vector<BarcodeFormat>(8);
vector.addElement(BarcodeFormat.UPC_A);
vector.addElement(BarcodeFormat.UPC_E);
vector.addElement(BarcodeFormat.EAN_13);
vector.addElement(BarcodeFormat.EAN_8);
vector.addElement(BarcodeFormat.RSS14);
if (!productsOnly) {
vector.addElement(BarcodeFormat.CODE_39);
vector.addElement(BarcodeFormat.CODE_93);
vector.addElement(BarcodeFormat.CODE_128);
vector.addElement(BarcodeFormat.ITF);
vector.addElement(BarcodeFormat.QR_CODE);
vector.addElement(BarcodeFormat.DATA_MATRIX);
vector.addElement(BarcodeFormat.PDF417);
//vector.addElement(BarcodeFormat.CODABAR);
}
hints.put(DecodeHintType.POSSIBLE_FORMATS, vector);
if (tryHarder) {
hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
}
if (pureBarcode) {
hints.put(DecodeHintType.PURE_BARCODE, Boolean.TRUE);
}
return hints;
}
private static void printUsage() {
System.err.println("Decode barcode images using the ZXing library\n");
System.err.println("usage: CommandLineRunner { file | dir | url } [ options ]");
System.err.println(" --try_harder: Use the TRY_HARDER hint, default is normal (mobile) mode");
System.err.println(" --pure_barcode: Input image is a pure monochrome barcode image, not a photo");
System.err.println(" --products_only: Only decode the UPC and EAN families of barcodes");
System.err.println(" --dump_results: Write the decoded contents to input.txt");
System.err.println(" --dump_black_point: Compare black point algorithms as input.mono.png");
System.err.println(" --crop=left,top,width,height: Only examine cropped region of input image(s)");
}
private static void decodeOneArgument(String argument,
Hashtable<DecodeHintType, Object> hints,
boolean dumpResults,
boolean dumpBlackPoint,
int[] crop) throws IOException,
URISyntaxException {
File inputFile = new File(argument);
if (inputFile.exists()) {
if (inputFile.isDirectory()) {
int successful = 0;
int total = 0;
for (File input : inputFile.listFiles()) {
String filename = input.getName().toLowerCase();
// Skip hidden files and text files (the latter is found in the blackbox tests).
if (filename.startsWith(".") || filename.endsWith(".txt")) {
continue;
}
// Skip the results of dumping the black point.
if (filename.contains(".mono.png")) {
continue;
}
Result result = decode(input.toURI(), hints, dumpBlackPoint, crop);
if (result != null) {
successful++;
if (dumpResults) {
dumpResult(input, result);
}
}
total++;
}
System.out.println("\nDecoded " + successful + " files out of " + total +
" successfully (" + (successful * 100 / total) + "%)\n");
} else {
Result result = decode(inputFile.toURI(), hints, dumpBlackPoint, crop);
if (dumpResults) {
dumpResult(inputFile, result);
}
}
} else {
decode(new URI(argument), hints, dumpBlackPoint, crop);
}
}
private static void dumpResult(File input, Result result) throws IOException {
String name = input.getAbsolutePath();
int pos = name.lastIndexOf('.');
if (pos > 0) {
name = name.substring(0, pos);
}
File dump = new File(name + ".txt");
writeStringToFile(result.getText(), dump);
}
private static void writeStringToFile(String value, File file) throws IOException {
Writer out = new OutputStreamWriter(new FileOutputStream(file), Charset.forName("UTF8"));
try {
out.write(value);
} finally {
out.close();
}
}
private static Result decode(URI uri,
Hashtable<DecodeHintType, Object> hints,
boolean dumpBlackPoint,
int[] crop) throws IOException {
BufferedImage image;
try {
image = ImageIO.read(uri.toURL());
} catch (IllegalArgumentException iae) {
throw new FileNotFoundException("Resource not found: " + uri);
}
if (image == null) {
System.err.println(uri.toString() + ": Could not load image");
return null;
}
try {
LuminanceSource source;
if (crop == null) {
source = new BufferedImageLuminanceSource(image);
} else {
source = new BufferedImageLuminanceSource(image, crop[0], crop[1], crop[2], crop[3]);
}
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
if (dumpBlackPoint) {
dumpBlackPoint(uri, image, bitmap);
}
Result result = new MultiFormatReader().decode(bitmap, hints);
ParsedResult parsedResult = ResultParser.parseResult(result);
System.out.println(uri.toString() + " (format: " + result.getBarcodeFormat() +
", type: " + parsedResult.getType() + "):\nRaw result:\n" + result.getText() +
"\nParsed result:\n" + parsedResult.getDisplayResult());
System.out.println("Also, there were " + result.getResultPoints().length + " result points.");
for (int i = 0; i < result.getResultPoints().length; i++) {
ResultPoint rp = result.getResultPoints()[i];
System.out.println(" Point " + i + ": (" + rp.getX() + "," + rp.getY() + ")");
}
return result;
} catch (NotFoundException nfe) {
System.out.println(uri.toString() + ": No barcode found");
return null;
} finally {
// Uncomment these lines when turning on exception tracking in ReaderException.
//System.out.println("Threw " + ReaderException.getExceptionCountAndReset() + " exceptions");
//System.out.println("Throwers:\n" + ReaderException.getThrowersAndReset());
}
}
// Writes out a single PNG which is three times the width of the input image, containing from left
// to right: the original image, the row sampling monochrome version, and the 2D sampling
// monochrome version.
// TODO: Update to compare different Binarizer implementations.
private static void dumpBlackPoint(URI uri, BufferedImage image, BinaryBitmap bitmap) {
String inputName = uri.getPath();
if (inputName.contains(".mono.png")) {
return;
}
int width = bitmap.getWidth();
int height = bitmap.getHeight();
int stride = width * 3;
int[] pixels = new int[stride * height];
// The original image
int[] argb = new int[width];
for (int y = 0; y < height; y++) {
image.getRGB(0, y, width, 1, argb, 0, width);
System.arraycopy(argb, 0, pixels, y * stride, width);
}
// Row sampling
BitArray row = new BitArray(width);
for (int y = 0; y < height; y++) {
try {
row = bitmap.getBlackRow(y, row);
} catch (NotFoundException nfe) {
// If fetching the row failed, draw a red line and keep going.
int offset = y * stride + width;
for (int x = 0; x < width; x++) {
pixels[offset + x] = 0xffff0000;
}
continue;
}
int offset = y * stride + width;
for (int x = 0; x < width; x++) {
if (row.get(x)) {
pixels[offset + x] = 0xff000000;
} else {
pixels[offset + x] = 0xffffffff;
}
}
}
// 2D sampling
try {
for (int y = 0; y < height; y++) {
BitMatrix matrix = bitmap.getBlackMatrix();
int offset = y * stride + width * 2;
for (int x = 0; x < width; x++) {
if (matrix.get(x, y)) {
pixels[offset + x] = 0xff000000;
} else {
pixels[offset + x] = 0xffffffff;
}
}
}
} catch (NotFoundException nfe) {
}
// Write the result
BufferedImage result = new BufferedImage(stride, height, BufferedImage.TYPE_INT_ARGB);
result.setRGB(0, 0, stride, height, pixels, 0, stride);
// Use the current working directory for URLs
String resultName = inputName;
if ("http".equals(uri.getScheme())) {
int pos = resultName.lastIndexOf('/');
if (pos > 0) {
resultName = '.' + resultName.substring(pos);
}
}
int pos = resultName.lastIndexOf('.');
if (pos > 0) {
resultName = resultName.substring(0, pos);
}
resultName += ".mono.png";
OutputStream outStream = null;
try {
outStream = new FileOutputStream(resultName);
ImageIO.write(result, "png", outStream);
} catch (FileNotFoundException e) {
System.err.println("Could not create " + resultName);
} catch (IOException e) {
System.err.println("Could not write to " + resultName);
} finally {
try {
if (outStream != null) {
outStream.close();
}
} catch (IOException ioe) {
// continue
}
}
}
}

View File

@ -0,0 +1,185 @@
/*
* Copyright 2008 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.client.j2se;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.LuminanceSource;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.ReaderException;
import com.google.zxing.Result;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.HybridBinarizer;
import java.awt.Container;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.TextArea;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import javax.imageio.ImageIO;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
/**
* <p>
* Simple GUI frontend to the library. Right now, only decodes a local file.
* This definitely needs some improvement. Just throwing something down to
* start.
* </p>
*
* @author Sean Owen
*/
//设置编码格式 Encoder.java static final String DEFAULT_BYTE_MODE_ENCODING ="UTF-8"
//设置图片大小本文件中
public final class GUIRunner extends JFrame implements ActionListener {
private final JLabel imageLabel;
private final TextArea textArea;
private final JButton sel_button;
private final JButton generate_button;
private final JLabel label2;
private final TextArea textArea2;
private GUIRunner() {
imageLabel = new JLabel();
textArea = new TextArea();
textArea.setEditable(false);
// textArea.setLineWrap(true);
// textArea.setMaximumSize(new Dimension(400, 200));
label2=new JLabel("请输入要编码信息:");
textArea2 =new TextArea();
// Button
sel_button = new JButton("选择图片");
sel_button.addActionListener(this);
generate_button=new JButton("生成二维码");
generate_button.addActionListener(this);
Container panel = new JPanel(new GridLayout(2,1));
//panel.setLayout(new FlowLayout());//new GridLayout(2,1)
panel.add(imageLabel);//一行一列
JPanel panel2=new JPanel(new GridLayout(4,1));//2行一列
panel2.add(textArea);
panel2.add(label2);
panel2.add(textArea2);
JPanel panel3=new JPanel(new GridLayout(1,2));
panel3.add(sel_button);
panel3.add(generate_button);
panel2.add(panel3);
panel.add(panel2);
setTitle("二维码编码解码程序_Powered by Zxing (C)xuenhua");
setSize(420, 420);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setContentPane(panel);
setLocationRelativeTo(null);
}
public static void main(String[] args) throws MalformedURLException {
GUIRunner runner = new GUIRunner();
runner.setVisible(true);
//runner.chooseImage();
}
private void chooseImage() throws MalformedURLException {
JFileChooser fileChooser = new JFileChooser();
fileChooser.showOpenDialog(this);
File file = fileChooser.getSelectedFile();
Icon imageIcon = new ImageIcon(file.toURI().toURL());
if(imageIcon.getIconWidth()>200){//转换尺寸
Image image=null;
try {
image = ImageIO.read(file);
} catch (IOException ioe) {
System.out.println( ioe.toString());
}
if (image == null) {
System.out.println("Could not decode image");
}
Image scaledImage=image.getScaledInstance(200,200,Image.SCALE_DEFAULT);
imageIcon=new ImageIcon(scaledImage);
}
// setSize(imageIcon.getIconWidth(), imageIcon.getIconHeight() + 100);
imageLabel.setIcon(imageIcon);
String decodeText = getDecodeText(file);
textArea.setText(decodeText);
}
private static String getDecodeText(File file) {
BufferedImage image;
try {
image = ImageIO.read(file);
} catch (IOException ioe) {
return ioe.toString();
}
if (image == null) {
return "Could not decode image";
}
LuminanceSource source = new BufferedImageLuminanceSource(image);
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
Result result;
try {
result = new MultiFormatReader().decode(bitmap);
} catch (ReaderException re) {
return re.toString();
}
return result.getText();
}
@Override
public void actionPerformed(ActionEvent e) {
try {
if(e.getActionCommand()=="选择图片"){
chooseImage();
}
if(e.getActionCommand()=="生成二维码"){
BitMatrix bitMatrix;
String str=textArea2.getText();
String user_dir=System.getProperty("user.dir");
String path=user_dir+"\\qr.png";
try {
bitMatrix = new MultiFormatWriter().encode
(str, BarcodeFormat.QR_CODE, 200, 200);//图片大小
File file = new File(path);
if(!file.exists()) {
file.createNewFile();
}
MatrixToImageWriter.writeToFile(bitMatrix, "png", file);
textArea.setText("二维码生成成功\n"+path);
} catch (IOException eIO) {
eIO.printStackTrace();
} catch (WriterException e1) {
e1.printStackTrace();
}
}
} catch (MalformedURLException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}

View File

@ -0,0 +1,182 @@
/*
* Copyright 2008 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.client.j2se;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.LuminanceSource;
import com.google.zxing.NotFoundException;
import com.google.zxing.ReaderException;
import com.google.zxing.common.BitArray;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.HybridBinarizer;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import javax.imageio.ImageIO;
/**
* A utility application for evaluating the effectiveness of various thresholding algorithms.
* Given a set of images on the command line, it converts each to a black-and-white PNG.
* The result is placed in a file based on the input name, with either ".row.png" or ".2d.png"
* appended.
*
* TODO: Needs to be updated to accept different Binarizer implementations.
* TODO: Consider whether to keep this separate app, as CommandLineRunner has similar functionality.
*
* @author alasdair@google.com (Alasdair Mackintosh)
* @author dswitkin@google.com (Daniel Switkin)
*/
public final class ImageConverter {
private static final String FORMAT = "png";
private static final int WHITE = 0xFFFFFFFF;
private static final int BLACK = 0xFF000000;
private static final int RED = 0xFFFF0000;
private static boolean rowSampling = false;
private ImageConverter() {
}
public static void main(String[] args) throws Exception {
for (String arg : args) {
if ("-row".equals(arg)) {
rowSampling = true;
} else if ("-2d".equals(arg)) {
rowSampling = false;
} else if (arg.startsWith("-")) {
System.err.println("Ignoring unrecognized option: " + arg);
}
}
for (String arg : args) {
if (arg.startsWith("-")) {
continue;
}
File inputFile = new File(arg);
if (inputFile.exists()) {
if (inputFile.isDirectory()) {
for (File input : inputFile.listFiles()) {
String filename = input.getName().toLowerCase();
// Skip hidden files and text files (the latter is found in the blackbox tests).
if (filename.startsWith(".") || filename.endsWith(".txt")) {
continue;
}
// Skip the results of dumping the black point.
if (filename.contains(".mono.png") || filename.contains(".row.png") ||
filename.contains(".2d.png")) {
continue;
}
convertImage(input.toURI());
}
} else {
convertImage(inputFile.toURI());
}
} else {
convertImage(new URI(arg));
}
}
}
private static void convertImage(URI uri) throws IOException {
BufferedImage image = ImageIO.read(uri.toURL());
LuminanceSource source = new BufferedImageLuminanceSource(image);
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
int width = bitmap.getWidth();
int height = bitmap.getHeight();
BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
BitArray array = new BitArray(width);
if (rowSampling) {
for (int y = 0; y < height; y++) {
try {
array = bitmap.getBlackRow(y, array);
} catch (NotFoundException nfe) {
// Draw rows with insufficient dynamic range in red
for (int x = 0; x < width; x++) {
result.setRGB(x, y, RED);
}
continue;
}
for (int x = 0; x < width; x++) {
result.setRGB(x, y, array.get(x) ? BLACK : WHITE);
}
}
} else {
try {
BitMatrix matrix = bitmap.getBlackMatrix();
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
result.setRGB(x, y, matrix.get(x, y) ? BLACK : WHITE);
}
}
} catch (NotFoundException nfe) {
}
}
File output = getOutput(uri);
System.out.printf("Writing output to %s\n", output);
ImageIO.write(result, FORMAT, output);
}
private static File getFileOfUri(URI uri) {
String name = uri.getPath();
int slashPos = name.lastIndexOf((int) '/');
String parent;
String basename;
if (slashPos != -1 && slashPos != name.length() - 1) {
parent = name.substring(0, slashPos);
basename = name.substring(slashPos + 1);
} else {
parent = ".";
basename = name;
}
File parentFile = new File(parent);
if (!parentFile.exists()) {
return null;
}
File baseFile = new File(parent, basename);
if (!baseFile.exists()) {
return null;
}
return baseFile;
}
private static File getOutput(URI uri) {
File result = getFileOfUri(uri);
if (result == null) {
result = new File("ConvertedImage." + FORMAT);
} else {
String name = result.getPath();
int dotpos = name.lastIndexOf((int) '.');
if (dotpos != -1) {
name = name.substring(0, dotpos);
}
String suffix = rowSampling ? "row" : "2d";
result = new File(name + '.' + suffix + '.' + FORMAT);
}
return result;
}
}

View File

@ -0,0 +1,79 @@
/*
* Copyright 2009 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.client.j2se;
import com.google.zxing.common.BitMatrix;
import javax.imageio.ImageIO;
import java.io.File;
import java.io.OutputStream;
import java.io.IOException;
import java.awt.image.BufferedImage;
/**
* Writes a {@link BitMatrix} to {@link BufferedImage},
* file or stream. Provided here instead of core since it depends on
* Java SE libraries.
*
* @author Sean Owen
*/
public final class MatrixToImageWriter {
private static final int BLACK = 0xFF000000;
private static final int WHITE = 0xFFFFFFFF;
private MatrixToImageWriter() {}
/**
* Renders a {@link BitMatrix} as an image, where "false" bits are rendered
* as white, and "true" bits are rendered as black.
*/
public static BufferedImage toBufferedImage(BitMatrix matrix) {
int width = matrix.getWidth();
int height = matrix.getHeight();
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
image.setRGB(x, y, matrix.get(x, y) ? BLACK : WHITE);
}
}
return image;
}
/**
* Writes a {@link BitMatrix} to a file.
*
* @see #toBufferedImage(BitMatrix)
*/
public static void writeToFile(BitMatrix matrix, String format, File file)
throws IOException {
BufferedImage image = toBufferedImage(matrix);
ImageIO.write(image, format, file);
}
/**
* Writes a {@link BitMatrix} to a stream.
*
* @see #toBufferedImage(BitMatrix)
*/
public static void writeToStream(BitMatrix matrix, String format, OutputStream stream)
throws IOException {
BufferedImage image = toBufferedImage(matrix);
ImageIO.write(image, format, stream);
}
}

View File

@ -0,0 +1,39 @@
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.client.result;
/**
* <p>See
* <a href="http://www.nttdocomo.co.jp/english/service/imode/make/content/barcode/about/s2.html">
* DoCoMo's documentation</a> about the result types represented by subclasses of this class.</p>
*
* <p>Thanks to Jeff Griffin for proposing rewrite of these classes that relies less
* on exception-based mechanisms during parsing.</p>
*
* @author Sean Owen
*/
abstract class AbstractDoCoMoResultParser extends ResultParser {
static String[] matchDoCoMoPrefixedField(String prefix, String rawText, boolean trim) {
return matchPrefixedField(prefix, rawText, ';', trim);
}
static String matchSingleDoCoMoPrefixedField(String prefix, String rawText, boolean trim) {
return matchSinglePrefixedField(prefix, rawText, ';', trim);
}
}

View File

@ -0,0 +1,73 @@
/*
* Copyright 2008 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.client.result;
import com.google.zxing.Result;
import java.util.Vector;
/**
* Implements KDDI AU's address book format. See
* <a href="http://www.au.kddi.com/ezfactory/tec/two_dimensions/index.html">
* http://www.au.kddi.com/ezfactory/tec/two_dimensions/index.html</a>.
* (Thanks to Yuzo for translating!)
*
* @author Sean Owen
*/
final class AddressBookAUResultParser extends ResultParser {
public static AddressBookParsedResult parse(Result result) {
String rawText = result.getText();
// MEMORY is mandatory; seems like a decent indicator, as does end-of-record separator CR/LF
if (rawText == null || rawText.indexOf("MEMORY") < 0 || rawText.indexOf("\r\n") < 0) {
return null;
}
// NAME1 and NAME2 have specific uses, namely written name and pronunciation, respectively.
// Therefore we treat them specially instead of as an array of names.
String name = matchSinglePrefixedField("NAME1:", rawText, '\r', true);
String pronunciation = matchSinglePrefixedField("NAME2:", rawText, '\r', true);
String[] phoneNumbers = matchMultipleValuePrefix("TEL", 3, rawText, true);
String[] emails = matchMultipleValuePrefix("MAIL", 3, rawText, true);
String note = matchSinglePrefixedField("MEMORY:", rawText, '\r', false);
String address = matchSinglePrefixedField("ADD:", rawText, '\r', true);
String[] addresses = address == null ? null : new String[] {address};
return new AddressBookParsedResult(maybeWrap(name), pronunciation, phoneNumbers, emails, note,
addresses, null, null, null, null);
}
private static String[] matchMultipleValuePrefix(String prefix, int max, String rawText,
boolean trim) {
Vector values = null;
for (int i = 1; i <= max; i++) {
String value = matchSinglePrefixedField(prefix + i + ':', rawText, '\r', trim);
if (value == null) {
break;
}
if (values == null) {
values = new Vector(max); // lazy init
}
values.addElement(value);
}
if (values == null) {
return null;
}
return toStringArray(values);
}
}

View File

@ -0,0 +1,85 @@
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.client.result;
import com.google.zxing.Result;
/**
* Implements the "MECARD" address book entry format.
*
* Supported keys: N, SOUND, TEL, EMAIL, NOTE, ADR, BDAY, URL, plus ORG
* Unsupported keys: TEL-AV, NICKNAME
*
* Except for TEL, multiple values for keys are also not supported;
* the first one found takes precedence.
*
* Our understanding of the MECARD format is based on this document:
*
* http://www.mobicode.org.tw/files/OMIA%20Mobile%20Bar%20Code%20Standard%20v3.2.1.doc
*
* @author Sean Owen
*/
final class AddressBookDoCoMoResultParser extends AbstractDoCoMoResultParser {
public static AddressBookParsedResult parse(Result result) {
String rawText = result.getText();
if (rawText == null || !rawText.startsWith("MECARD:")) {
return null;
}
String[] rawName = matchDoCoMoPrefixedField("N:", rawText, true);
if (rawName == null) {
return null;
}
String name = parseName(rawName[0]);
String pronunciation = matchSingleDoCoMoPrefixedField("SOUND:", rawText, true);
String[] phoneNumbers = matchDoCoMoPrefixedField("TEL:", rawText, true);
String[] emails = matchDoCoMoPrefixedField("EMAIL:", rawText, true);
String note = matchSingleDoCoMoPrefixedField("NOTE:", rawText, false);
String[] addresses = matchDoCoMoPrefixedField("ADR:", rawText, true);
String birthday = matchSingleDoCoMoPrefixedField("BDAY:", rawText, true);
if (birthday != null && !isStringOfDigits(birthday, 8)) {
// No reason to throw out the whole card because the birthday is formatted wrong.
birthday = null;
}
String url = matchSingleDoCoMoPrefixedField("URL:", rawText, true);
// Although ORG may not be strictly legal in MECARD, it does exist in VCARD and we might as well
// honor it when found in the wild.
String org = matchSingleDoCoMoPrefixedField("ORG:", rawText, true);
return new AddressBookParsedResult(maybeWrap(name),
pronunciation,
phoneNumbers,
emails,
note,
addresses,
org,
birthday,
null,
url);
}
private static String parseName(String name) {
int comma = name.indexOf((int) ',');
if (comma >= 0) {
// Format may be last,first; switch it around
return name.substring(comma + 1) + ' ' + name.substring(0, comma);
}
return name;
}
}

View File

@ -0,0 +1,122 @@
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.client.result;
/**
* @author Sean Owen
*/
public final class AddressBookParsedResult extends ParsedResult {
private final String[] names;
private final String pronunciation;
private final String[] phoneNumbers;
private final String[] emails;
private final String note;
private final String[] addresses;
private final String org;
private final String birthday;
private final String title;
private final String url;
public AddressBookParsedResult(String[] names,
String pronunciation,
String[] phoneNumbers,
String[] emails,
String note,
String[] addresses,
String org,
String birthday,
String title,
String url) {
super(ParsedResultType.ADDRESSBOOK);
this.names = names;
this.pronunciation = pronunciation;
this.phoneNumbers = phoneNumbers;
this.emails = emails;
this.note = note;
this.addresses = addresses;
this.org = org;
this.birthday = birthday;
this.title = title;
this.url = url;
}
public String[] getNames() {
return names;
}
/**
* In Japanese, the name is written in kanji, which can have multiple readings. Therefore a hint
* is often provided, called furigana, which spells the name phonetically.
*
* @return The pronunciation of the getNames() field, often in hiragana or katakana.
*/
public String getPronunciation() {
return pronunciation;
}
public String[] getPhoneNumbers() {
return phoneNumbers;
}
public String[] getEmails() {
return emails;
}
public String getNote() {
return note;
}
public String[] getAddresses() {
return addresses;
}
public String getTitle() {
return title;
}
public String getOrg() {
return org;
}
public String getURL() {
return url;
}
/**
* @return birthday formatted as yyyyMMdd (e.g. 19780917)
*/
public String getBirthday() {
return birthday;
}
public String getDisplayResult() {
StringBuffer result = new StringBuffer(100);
maybeAppend(names, result);
maybeAppend(pronunciation, result);
maybeAppend(title, result);
maybeAppend(org, result);
maybeAppend(addresses, result);
maybeAppend(phoneNumbers, result);
maybeAppend(emails, result);
maybeAppend(url, result);
maybeAppend(birthday, result);
maybeAppend(note, result);
return result.toString();
}
}

View File

@ -0,0 +1,94 @@
/*
* Copyright 2008 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.client.result;
import com.google.zxing.Result;
import java.util.Vector;
/**
* Implements the "BIZCARD" address book entry format, though this has been
* largely reverse-engineered from examples observed in the wild -- still
* looking for a definitive reference.
*
* @author Sean Owen
*/
final class BizcardResultParser extends AbstractDoCoMoResultParser {
// Yes, we extend AbstractDoCoMoResultParser since the format is very much
// like the DoCoMo MECARD format, but this is not technically one of
// DoCoMo's proposed formats
public static AddressBookParsedResult parse(Result result) {
String rawText = result.getText();
if (rawText == null || !rawText.startsWith("BIZCARD:")) {
return null;
}
String firstName = matchSingleDoCoMoPrefixedField("N:", rawText, true);
String lastName = matchSingleDoCoMoPrefixedField("X:", rawText, true);
String fullName = buildName(firstName, lastName);
String title = matchSingleDoCoMoPrefixedField("T:", rawText, true);
String org = matchSingleDoCoMoPrefixedField("C:", rawText, true);
String[] addresses = matchDoCoMoPrefixedField("A:", rawText, true);
String phoneNumber1 = matchSingleDoCoMoPrefixedField("B:", rawText, true);
String phoneNumber2 = matchSingleDoCoMoPrefixedField("M:", rawText, true);
String phoneNumber3 = matchSingleDoCoMoPrefixedField("F:", rawText, true);
String email = matchSingleDoCoMoPrefixedField("E:", rawText, true);
return new AddressBookParsedResult(maybeWrap(fullName),
null,
buildPhoneNumbers(phoneNumber1, phoneNumber2, phoneNumber3),
maybeWrap(email),
null,
addresses,
org,
null,
title,
null);
}
private static String[] buildPhoneNumbers(String number1, String number2, String number3) {
Vector numbers = new Vector(3);
if (number1 != null) {
numbers.addElement(number1);
}
if (number2 != null) {
numbers.addElement(number2);
}
if (number3 != null) {
numbers.addElement(number3);
}
int size = numbers.size();
if (size == 0) {
return null;
}
String[] result = new String[size];
for (int i = 0; i < size; i++) {
result[i] = (String) numbers.elementAt(i);
}
return result;
}
private static String buildName(String firstName, String lastName) {
if (firstName == null) {
return lastName;
} else {
return lastName == null ? firstName : firstName + ' ' + lastName;
}
}
}

View File

@ -0,0 +1,46 @@
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.client.result;
import com.google.zxing.Result;
/**
* @author Sean Owen
*/
final class BookmarkDoCoMoResultParser extends AbstractDoCoMoResultParser {
private BookmarkDoCoMoResultParser() {
}
public static URIParsedResult parse(Result result) {
String rawText = result.getText();
if (rawText == null || !rawText.startsWith("MEBKM:")) {
return null;
}
String title = matchSingleDoCoMoPrefixedField("TITLE:", rawText, true);
String[] rawUri = matchDoCoMoPrefixedField("URL:", rawText, true);
if (rawUri == null) {
return null;
}
String uri = rawUri[0];
if (!URIResultParser.isBasicallyValidURI(uri)) {
return null;
}
return new URIParsedResult(uri, title);
}
}

View File

@ -0,0 +1,134 @@
/*
* Copyright 2008 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.client.result;
/**
* @author Sean Owen
*/
public final class CalendarParsedResult extends ParsedResult {
private final String summary;
private final String start;
private final String end;
private final String location;
private final String attendee;
private final String description;
public CalendarParsedResult(String summary,
String start,
String end,
String location,
String attendee,
String description) {
super(ParsedResultType.CALENDAR);
// Start is required, end is not
if (start == null) {
throw new IllegalArgumentException();
}
validateDate(start);
if (end == null) {
end = start;
} else {
validateDate(end);
}
this.summary = summary;
this.start = start;
this.end = end;
this.location = location;
this.attendee = attendee;
this.description = description;
}
public String getSummary() {
return summary;
}
/**
* <p>We would return the start and end date as a {@link java.util.Date} except that this code
* needs to work under JavaME / MIDP and there is no date parsing library available there, such
* as <code>java.text.SimpleDateFormat</code>.</p> See validateDate() for the return format.
*
* @return start time formatted as a RFC 2445 DATE or DATE-TIME.</p>
*/
public String getStart() {
return start;
}
/**
* @see #getStart(). May return null if the event has no duration.
*/
public String getEnd() {
return end;
}
public String getLocation() {
return location;
}
public String getAttendee() {
return attendee;
}
public String getDescription() {
return description;
}
public String getDisplayResult() {
StringBuffer result = new StringBuffer(100);
maybeAppend(summary, result);
maybeAppend(start, result);
maybeAppend(end, result);
maybeAppend(location, result);
maybeAppend(attendee, result);
maybeAppend(description, result);
return result.toString();
}
/**
* RFC 2445 allows the start and end fields to be of type DATE (e.g. 20081021) or DATE-TIME
* (e.g. 20081021T123000 for local time, or 20081021T123000Z for UTC).
*
* @param date The string to validate
*/
private static void validateDate(String date) {
if (date != null) {
int length = date.length();
if (length != 8 && length != 15 && length != 16) {
throw new IllegalArgumentException();
}
for (int i = 0; i < 8; i++) {
if (!Character.isDigit(date.charAt(i))) {
throw new IllegalArgumentException();
}
}
if (length > 8) {
if (date.charAt(8) != 'T') {
throw new IllegalArgumentException();
}
for (int i = 9; i < 15; i++) {
if (!Character.isDigit(date.charAt(i))) {
throw new IllegalArgumentException();
}
}
if (length == 16 && date.charAt(15) != 'Z') {
throw new IllegalArgumentException();
}
}
}
}
}

View File

@ -0,0 +1,61 @@
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.client.result;
/**
* @author Sean Owen
*/
public final class EmailAddressParsedResult extends ParsedResult {
private final String emailAddress;
private final String subject;
private final String body;
private final String mailtoURI;
EmailAddressParsedResult(String emailAddress, String subject, String body, String mailtoURI) {
super(ParsedResultType.EMAIL_ADDRESS);
this.emailAddress = emailAddress;
this.subject = subject;
this.body = body;
this.mailtoURI = mailtoURI;
}
public String getEmailAddress() {
return emailAddress;
}
public String getSubject() {
return subject;
}
public String getBody() {
return body;
}
public String getMailtoURI() {
return mailtoURI;
}
public String getDisplayResult() {
StringBuffer result = new StringBuffer(30);
maybeAppend(emailAddress, result);
maybeAppend(subject, result);
maybeAppend(body, result);
return result.toString();
}
}

View File

@ -0,0 +1,64 @@
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.client.result;
import com.google.zxing.Result;
import java.util.Hashtable;
/**
* Represents a result that encodes an e-mail address, either as a plain address
* like "joe@example.org" or a mailto: URL like "mailto:joe@example.org".
*
* @author Sean Owen
*/
final class EmailAddressResultParser extends ResultParser {
public static EmailAddressParsedResult parse(Result result) {
String rawText = result.getText();
if (rawText == null) {
return null;
}
String emailAddress;
if (rawText.startsWith("mailto:") || rawText.startsWith("MAILTO:")) {
// If it starts with mailto:, assume it is definitely trying to be an email address
emailAddress = rawText.substring(7);
int queryStart = emailAddress.indexOf('?');
if (queryStart >= 0) {
emailAddress = emailAddress.substring(0, queryStart);
}
Hashtable nameValues = parseNameValuePairs(rawText);
String subject = null;
String body = null;
if (nameValues != null) {
if (emailAddress.length() == 0) {
emailAddress = (String) nameValues.get("to");
}
subject = (String) nameValues.get("subject");
body = (String) nameValues.get("body");
}
return new EmailAddressParsedResult(emailAddress, subject, body, rawText);
} else {
if (!EmailDoCoMoResultParser.isBasicallyValidEmailAddress(rawText)) {
return null;
}
emailAddress = rawText;
return new EmailAddressParsedResult(emailAddress, null, null, "mailto:" + emailAddress);
}
}
}

View File

@ -0,0 +1,87 @@
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.client.result;
import com.google.zxing.Result;
/**
* Implements the "MATMSG" email message entry format.
*
* Supported keys: TO, SUB, BODY
*
* @author Sean Owen
*/
final class EmailDoCoMoResultParser extends AbstractDoCoMoResultParser {
private static final char[] ATEXT_SYMBOLS =
{'@','.','!','#','$','%','&','\'','*','+','-','/','=','?','^','_','`','{','|','}','~'};
public static EmailAddressParsedResult parse(Result result) {
String rawText = result.getText();
if (rawText == null || !rawText.startsWith("MATMSG:")) {
return null;
}
String[] rawTo = matchDoCoMoPrefixedField("TO:", rawText, true);
if (rawTo == null) {
return null;
}
String to = rawTo[0];
if (!isBasicallyValidEmailAddress(to)) {
return null;
}
String subject = matchSingleDoCoMoPrefixedField("SUB:", rawText, false);
String body = matchSingleDoCoMoPrefixedField("BODY:", rawText, false);
return new EmailAddressParsedResult(to, subject, body, "mailto:" + to);
}
/**
* This implements only the most basic checking for an email address's validity -- that it contains
* an '@' contains no characters disallowed by RFC 2822. This is an overly lenient definition of
* validity. We want to generally be lenient here since this class is only intended to encapsulate what's
* in a barcode, not "judge" it.
*/
static boolean isBasicallyValidEmailAddress(String email) {
if (email == null) {
return false;
}
boolean atFound = false;
for (int i = 0; i < email.length(); i++) {
char c = email.charAt(i);
if ((c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && (c < '0' || c > '9') &&
!isAtextSymbol(c)) {
return false;
}
if (c == '@') {
if (atFound) {
return false;
}
atFound = true;
}
}
return atFound;
}
private static boolean isAtextSymbol(char c) {
for (int i = 0; i < ATEXT_SYMBOLS.length; i++) {
if (c == ATEXT_SYMBOLS[i]) {
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,195 @@
/*
* Copyright (C) 2010 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* These authors would like to acknowledge the Spanish Ministry of Industry,
* Tourism and Trade, for the support in the project TSI020301-2008-2
* "PIRAmIDE: Personalizable Interactions with Resources on AmI-enabled
* Mobile Dynamic Environments", led by Treelogic
* ( http://www.treelogic.com/ ):
*
* http://www.piramidepse.com/
*/
package com.google.zxing.client.result;
import java.util.Hashtable;
/**
* @author Antonio Manuel Benjumea Conde, Servinform, S.A.
* @author Agustín Delgado, Servinform, S.A.
*/
public class ExpandedProductParsedResult extends ParsedResult {
public static final String KILOGRAM = "KG";
public static final String POUND = "LB";
private final String productID;
private final String sscc;
private final String lotNumber;
private final String productionDate;
private final String packagingDate;
private final String bestBeforeDate;
private final String expirationDate;
private final String weight;
private final String weightType;
private final String weightIncrement;
private final String price;
private final String priceIncrement;
private final String priceCurrency;
// For AIS that not exist in this object
private final Hashtable uncommonAIs;
ExpandedProductParsedResult() {
super(ParsedResultType.PRODUCT);
this.productID = "";
this.sscc = "";
this.lotNumber = "";
this.productionDate = "";
this.packagingDate = "";
this.bestBeforeDate = "";
this.expirationDate = "";
this.weight = "";
this.weightType = "";
this.weightIncrement = "";
this.price = "";
this.priceIncrement = "";
this.priceCurrency = "";
this.uncommonAIs = new Hashtable();
}
public ExpandedProductParsedResult(String productID, String sscc,
String lotNumber, String productionDate, String packagingDate,
String bestBeforeDate, String expirationDate, String weight,
String weightType, String weightIncrement, String price,
String priceIncrement, String priceCurrency, Hashtable uncommonAIs) {
super(ParsedResultType.PRODUCT);
this.productID = productID;
this.sscc = sscc;
this.lotNumber = lotNumber;
this.productionDate = productionDate;
this.packagingDate = packagingDate;
this.bestBeforeDate = bestBeforeDate;
this.expirationDate = expirationDate;
this.weight = weight;
this.weightType = weightType;
this.weightIncrement = weightIncrement;
this.price = price;
this.priceIncrement = priceIncrement;
this.priceCurrency = priceCurrency;
this.uncommonAIs = uncommonAIs;
}
public boolean equals(Object o){
if (!(o instanceof ExpandedProductParsedResult)) {
return false;
}
ExpandedProductParsedResult other = (ExpandedProductParsedResult)o;
return this.productID.equals( other.productID)
&& this.sscc.equals( other.sscc)
&& this.lotNumber.equals( other.lotNumber)
&& this.productionDate.equals( other.productionDate)
&& this.bestBeforeDate.equals( other.bestBeforeDate)
&& this.expirationDate.equals( other.expirationDate)
&& this.weight.equals( other.weight)
&& this.weightType.equals( other.weightType)
&& this.weightIncrement.equals( other.weightIncrement)
&& this.price.equals( other.price)
&& this.priceIncrement.equals( other.priceIncrement)
&& this.priceCurrency.equals( other.priceCurrency)
&& this.uncommonAIs.equals( other.uncommonAIs);
}
public int hashCode(){
int hash1 = this.productID.hashCode();
hash1 = 31 * hash1 + this.sscc.hashCode();
hash1 = 31 * hash1 + this.lotNumber.hashCode();
hash1 = 31 * hash1 + this.productionDate.hashCode();
hash1 = 31 * hash1 + this.bestBeforeDate.hashCode();
hash1 = 31 * hash1 + this.expirationDate.hashCode();
hash1 = 31 * hash1 + this.weight.hashCode();
int hash2 = this.weightType.hashCode();
hash2 = 31 * hash2 + this.weightIncrement.hashCode();
hash2 = 31 * hash2 + this.price.hashCode();
hash2 = 31 * hash2 + this.priceIncrement.hashCode();
hash2 = 31 * hash2 + this.priceCurrency.hashCode();
hash2 = 31 * hash2 + this.uncommonAIs.hashCode();
return hash1 ^ hash2;
}
public String getProductID() {
return productID;
}
public String getSscc() {
return sscc;
}
public String getLotNumber() {
return lotNumber;
}
public String getProductionDate() {
return productionDate;
}
public String getPackagingDate() {
return packagingDate;
}
public String getBestBeforeDate() {
return bestBeforeDate;
}
public String getExpirationDate() {
return expirationDate;
}
public String getWeight() {
return weight;
}
public String getWeightType() {
return weightType;
}
public String getWeightIncrement() {
return weightIncrement;
}
public String getPrice() {
return price;
}
public String getPriceIncrement() {
return priceIncrement;
}
public String getPriceCurrency() {
return priceCurrency;
}
public Hashtable getUncommonAIs() {
return uncommonAIs;
}
public String getDisplayResult() {
return productID;
}
}

View File

@ -0,0 +1,199 @@
/*
* Copyright (C) 2010 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* These authors would like to acknowledge the Spanish Ministry of Industry,
* Tourism and Trade, for the support in the project TSI020301-2008-2
* "PIRAmIDE: Personalizable Interactions with Resources on AmI-enabled
* Mobile Dynamic Environments", led by Treelogic
* ( http://www.treelogic.com/ ):
*
* http://www.piramidepse.com/
*/
package com.google.zxing.client.result;
import java.util.Hashtable;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.Result;
/**
* Parses strings of digits that represent a RSS Extended code.
*
* @author Antonio Manuel Benjumea Conde, Servinform, S.A.
* @author Agustín Delgado, Servinform, S.A.
*/
final class ExpandedProductResultParser extends ResultParser {
private ExpandedProductResultParser() {
}
// Treat all RSS EXPANDED, in the sense that they are all
// product barcodes with complementary data.
public static ExpandedProductParsedResult parse(Result result) {
BarcodeFormat format = result.getBarcodeFormat();
if (!(BarcodeFormat.RSS_EXPANDED.equals(format))) {
// ExtendedProductParsedResult NOT created. Not a RSS Expanded barcode
return null;
}
// Really neither of these should happen:
String rawText = result.getText();
if (rawText == null) {
// ExtendedProductParsedResult NOT created. Input text is NULL
return null;
}
String productID = "-";
String sscc = "-";
String lotNumber = "-";
String productionDate = "-";
String packagingDate = "-";
String bestBeforeDate = "-";
String expirationDate = "-";
String weight = "-";
String weightType = "-";
String weightIncrement = "-";
String price = "-";
String priceIncrement = "-";
String priceCurrency = "-";
Hashtable uncommonAIs = new Hashtable();
int i = 0;
while (i < rawText.length()) {
String ai = findAIvalue(i, rawText);
if ("ERROR".equals(ai)) {
// Error. Code doesn't match with RSS expanded pattern
// ExtendedProductParsedResult NOT created. Not match with RSS Expanded pattern
return null;
}
i += ai.length() + 2;
String value = findValue(i, rawText);
i += value.length();
if ("00".equals(ai)) {
sscc = value;
} else if ("01".equals(ai)) {
productID = value;
} else if ("10".equals(ai)) {
lotNumber = value;
} else if ("11".equals(ai)) {
productionDate = value;
} else if ("13".equals(ai)) {
packagingDate = value;
} else if ("15".equals(ai)) {
bestBeforeDate = value;
} else if ("17".equals(ai)) {
expirationDate = value;
} else if ("3100".equals(ai) || "3101".equals(ai)
|| "3102".equals(ai) || "3103".equals(ai)
|| "3104".equals(ai) || "3105".equals(ai)
|| "3106".equals(ai) || "3107".equals(ai)
|| "3108".equals(ai) || "3109".equals(ai)) {
weight = value;
weightType = ExpandedProductParsedResult.KILOGRAM;
weightIncrement = ai.substring(3);
} else if ("3200".equals(ai) || "3201".equals(ai)
|| "3202".equals(ai) || "3203".equals(ai)
|| "3204".equals(ai) || "3205".equals(ai)
|| "3206".equals(ai) || "3207".equals(ai)
|| "3208".equals(ai) || "3209".equals(ai)) {
weight = value;
weightType = ExpandedProductParsedResult.POUND;
weightIncrement = ai.substring(3);
} else if ("3920".equals(ai) || "3921".equals(ai)
|| "3922".equals(ai) || "3923".equals(ai)) {
price = value;
priceIncrement = ai.substring(3);
} else if ("3930".equals(ai) || "3931".equals(ai)
|| "3932".equals(ai) || "3933".equals(ai)) {
if (value.length() < 4) {
// The value must have more of 3 symbols (3 for currency and
// 1 at least for the price)
// ExtendedProductParsedResult NOT created. Not match with RSS Expanded pattern
return null;
}
price = value.substring(3);
priceCurrency = value.substring(0, 3);
priceIncrement = ai.substring(3);
} else {
// No match with common AIs
uncommonAIs.put(ai, value);
}
}
return new ExpandedProductParsedResult(productID, sscc, lotNumber,
productionDate, packagingDate, bestBeforeDate, expirationDate,
weight, weightType, weightIncrement, price, priceIncrement,
priceCurrency, uncommonAIs);
}
private static String findAIvalue(int i, String rawText) {
StringBuffer buf = new StringBuffer();
char c = rawText.charAt(i);
// First character must be a open parenthesis.If not, ERROR
if (c != '(') {
return "ERROR";
}
String rawTextAux = rawText.substring(i + 1);
for (int index = 0; index < rawTextAux.length(); index++) {
char currentChar = rawTextAux.charAt(index);
switch (currentChar){
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
buf.append(currentChar);
break;
case ')':
return buf.toString();
default:
return "ERROR";
}
}
return buf.toString();
}
private static String findValue(int i, String rawText) {
StringBuffer buf = new StringBuffer();
String rawTextAux = rawText.substring(i);
for (int index = 0; index < rawTextAux.length(); index++) {
char c = rawTextAux.charAt(index);
if (c == '(') {
// We look for a new AI. If it doesn't exist (ERROR), we coninue
// with the iteration
if ("ERROR".equals(findAIvalue(index, rawTextAux))) {
buf.append('(');
} else {
break;
}
} else {
buf.append(c);
}
}
return buf.toString();
}
}

View File

@ -0,0 +1,132 @@
/*
* Copyright 2008 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.client.result;
/**
* @author Sean Owen
*/
public final class GeoParsedResult extends ParsedResult {
private final double latitude;
private final double longitude;
private final double altitude;
private final String query;
GeoParsedResult(double latitude, double longitude, double altitude, String query) {
super(ParsedResultType.GEO);
this.latitude = latitude;
this.longitude = longitude;
this.altitude = altitude;
this.query = query;
}
public String getGeoURI() {
StringBuffer result = new StringBuffer();
result.append("geo:");
result.append(latitude);
result.append(',');
result.append(longitude);
if (altitude > 0) {
result.append(',');
result.append(altitude);
}
if (query != null) {
result.append('?');
result.append(query);
}
return result.toString();
}
/**
* @return latitude in degrees
*/
public double getLatitude() {
return latitude;
}
/**
* @return longitude in degrees
*/
public double getLongitude() {
return longitude;
}
/**
* @return altitude in meters. If not specified, in the geo URI, returns 0.0
*/
public double getAltitude() {
return altitude;
}
/**
* @return query string associated with geo URI or null if none exists
*/
public String getQuery() {
return query;
}
public String getDisplayResult() {
StringBuffer result = new StringBuffer(20);
result.append(latitude);
result.append(", ");
result.append(longitude);
if (altitude > 0.0) {
result.append(", ");
result.append(altitude);
result.append('m');
}
if (query != null) {
result.append(" (");
result.append(query);
result.append(')');
}
return result.toString();
}
/**
* @return a URI link to Google Maps which display the point on the Earth described
* by this instance, and sets the zoom level in a way that roughly reflects the
* altitude, if specified
*/
/*
public String getGoogleMapsURI() {
StringBuffer result = new StringBuffer(50);
result.append("http://maps.google.com/?ll=");
result.append(latitude);
result.append(',');
result.append(longitude);
if (altitude > 0.0f) {
// Map altitude to zoom level, cleverly. Roughly, zoom level 19 is like a
// view from 1000ft, 18 is like 2000ft, 17 like 4000ft, and so on.
double altitudeInFeet = altitude * 3.28;
int altitudeInKFeet = (int) (altitudeInFeet / 1000.0);
// No Math.log() available here, so compute log base 2 the old fashioned way
// Here logBaseTwo will take on a value between 0 and 18 actually
int logBaseTwo = 0;
while (altitudeInKFeet > 1 && logBaseTwo < 18) {
altitudeInKFeet >>= 1;
logBaseTwo++;
}
int zoom = 19 - logBaseTwo;
result.append("&z=");
result.append(zoom);
}
return result.toString();
}
*/
}

View File

@ -0,0 +1,77 @@
/*
* Copyright 2008 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.client.result;
import com.google.zxing.Result;
/**
* Parses a "geo:" URI result, which specifies a location on the surface of
* the Earth as well as an optional altitude above the surface. See
* <a href="http://tools.ietf.org/html/draft-mayrhofer-geo-uri-00">
* http://tools.ietf.org/html/draft-mayrhofer-geo-uri-00</a>.
*
* @author Sean Owen
*/
final class GeoResultParser extends ResultParser {
private GeoResultParser() {
}
public static GeoParsedResult parse(Result result) {
String rawText = result.getText();
if (rawText == null || (!rawText.startsWith("geo:") && !rawText.startsWith("GEO:"))) {
return null;
}
// Drop geo, query portion
int queryStart = rawText.indexOf('?', 4);
String query;
String geoURIWithoutQuery;
if (queryStart < 0) {
query = null;
geoURIWithoutQuery = rawText.substring(4);
} else {
query = rawText.substring(queryStart + 1);
geoURIWithoutQuery = rawText.substring(4, queryStart);
}
int latitudeEnd = geoURIWithoutQuery.indexOf(',');
if (latitudeEnd < 0) {
return null;
}
int longitudeEnd = geoURIWithoutQuery.indexOf(',', latitudeEnd + 1);
double latitude, longitude, altitude;
try {
latitude = Double.parseDouble(geoURIWithoutQuery.substring(0, latitudeEnd));
if (latitude > 90.0 || latitude < -90.0) {
return null;
}
if (longitudeEnd < 0) {
longitude = Double.parseDouble(geoURIWithoutQuery.substring(latitudeEnd + 1));
altitude = 0.0;
} else {
longitude = Double.parseDouble(geoURIWithoutQuery.substring(latitudeEnd + 1, longitudeEnd));
altitude = Double.parseDouble(geoURIWithoutQuery.substring(longitudeEnd + 1));
}
if (longitude > 180.0 || longitude < -180.0 || altitude < 0) {
return null;
}
} catch (NumberFormatException nfe) {
return null;
}
return new GeoParsedResult(latitude, longitude, altitude, query);
}
}

View File

@ -0,0 +1,39 @@
/*
* Copyright 2008 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.client.result;
/**
* @author jbreiden@google.com (Jeff Breidenbach)
*/
public final class ISBNParsedResult extends ParsedResult {
private final String isbn;
ISBNParsedResult(String isbn) {
super(ParsedResultType.ISBN);
this.isbn = isbn;
}
public String getISBN() {
return isbn;
}
public String getDisplayResult() {
return isbn;
}
}

View File

@ -0,0 +1,54 @@
/*
* Copyright 2008 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.client.result;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.Result;
/**
* Parses strings of digits that represent a ISBN.
*
* @author jbreiden@google.com (Jeff Breidenbach)
*/
public class ISBNResultParser extends ResultParser {
private ISBNResultParser() {
}
// ISBN-13 For Dummies
// http://www.bisg.org/isbn-13/for.dummies.html
public static ISBNParsedResult parse(Result result) {
BarcodeFormat format = result.getBarcodeFormat();
if (!BarcodeFormat.EAN_13.equals(format)) {
return null;
}
String rawText = result.getText();
if (rawText == null) {
return null;
}
int length = rawText.length();
if (length != 13) {
return null;
}
if (!rawText.startsWith("978") && !rawText.startsWith("979")) {
return null;
}
return new ISBNParsedResult(rawText);
}
}

View File

@ -0,0 +1,73 @@
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.client.result;
import com.google.zxing.Result;
/**
* <p>Abstract class representing the result of decoding a barcode, as more than
* a String -- as some type of structured data. This might be a subclass which represents
* a URL, or an e-mail address. {@link ResultParser#parseResult(Result)} will turn a raw
* decoded string into the most appropriate type of structured representation.</p>
*
* <p>Thanks to Jeff Griffin for proposing rewrite of these classes that relies less
* on exception-based mechanisms during parsing.</p>
*
* @author Sean Owen
*/
public abstract class ParsedResult {
private final ParsedResultType type;
protected ParsedResult(ParsedResultType type) {
this.type = type;
}
public ParsedResultType getType() {
return type;
}
public abstract String getDisplayResult();
public String toString() {
return getDisplayResult();
}
public static void maybeAppend(String value, StringBuffer result) {
if (value != null && value.length() > 0) {
// Don't add a newline before the first value
if (result.length() > 0) {
result.append('\n');
}
result.append(value);
}
}
public static void maybeAppend(String[] value, StringBuffer result) {
if (value != null) {
for (int i = 0; i < value.length; i++) {
if (value[i] != null && value[i].length() > 0) {
if (result.length() > 0) {
result.append('\n');
}
result.append(value[i]);
}
}
}
}
}

View File

@ -0,0 +1,53 @@
/*
* Copyright 2010 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.client.result;
/**
* Represents the type of data encoded by a barcode -- from plain text, to a
* URI, to an e-mail address, etc.
*
* @author Sean Owen
*/
public final class ParsedResultType {
public static final ParsedResultType ADDRESSBOOK = new ParsedResultType("ADDRESSBOOK");
public static final ParsedResultType EMAIL_ADDRESS = new ParsedResultType("EMAIL_ADDRESS");
public static final ParsedResultType PRODUCT = new ParsedResultType("PRODUCT");
public static final ParsedResultType URI = new ParsedResultType("URI");
public static final ParsedResultType TEXT = new ParsedResultType("TEXT");
public static final ParsedResultType ANDROID_INTENT = new ParsedResultType("ANDROID_INTENT");
public static final ParsedResultType GEO = new ParsedResultType("GEO");
public static final ParsedResultType TEL = new ParsedResultType("TEL");
public static final ParsedResultType SMS = new ParsedResultType("SMS");
public static final ParsedResultType CALENDAR = new ParsedResultType("CALENDAR");
public static final ParsedResultType WIFI = new ParsedResultType("WIFI");
// "optional" types
public static final ParsedResultType NDEF_SMART_POSTER = new ParsedResultType("NDEF_SMART_POSTER");
public static final ParsedResultType MOBILETAG_RICH_WEB = new ParsedResultType("MOBILETAG_RICH_WEB");
public static final ParsedResultType ISBN = new ParsedResultType("ISBN");
private final String name;
private ParsedResultType(String name) {
this.name = name;
}
public String toString() {
return name;
}
}

View File

@ -0,0 +1,49 @@
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.client.result;
/**
* @author dswitkin@google.com (Daniel Switkin)
*/
public final class ProductParsedResult extends ParsedResult {
private final String productID;
private final String normalizedProductID;
ProductParsedResult(String productID) {
this(productID, productID);
}
ProductParsedResult(String productID, String normalizedProductID) {
super(ParsedResultType.PRODUCT);
this.productID = productID;
this.normalizedProductID = normalizedProductID;
}
public String getProductID() {
return productID;
}
public String getNormalizedProductID() {
return normalizedProductID;
}
public String getDisplayResult() {
return productID;
}
}

View File

@ -0,0 +1,66 @@
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.client.result;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.Result;
import com.google.zxing.oned.UPCEReader;
/**
* Parses strings of digits that represent a UPC code.
*
* @author dswitkin@google.com (Daniel Switkin)
*/
final class ProductResultParser extends ResultParser {
private ProductResultParser() {
}
// Treat all UPC and EAN variants as UPCs, in the sense that they are all product barcodes.
public static ProductParsedResult parse(Result result) {
BarcodeFormat format = result.getBarcodeFormat();
if (!(BarcodeFormat.UPC_A.equals(format) || BarcodeFormat.UPC_E.equals(format) ||
BarcodeFormat.EAN_8.equals(format) || BarcodeFormat.EAN_13.equals(format))) {
return null;
}
// Really neither of these should happen:
String rawText = result.getText();
if (rawText == null) {
return null;
}
int length = rawText.length();
for (int x = 0; x < length; x++) {
char c = rawText.charAt(x);
if (c < '0' || c > '9') {
return null;
}
}
// Not actually checking the checksum again here
String normalizedProductID;
// Expand UPC-E for purposes of searching
if (BarcodeFormat.UPC_E.equals(format)) {
normalizedProductID = UPCEReader.convertUPCEtoUPCA(rawText);
} else {
normalizedProductID = rawText;
}
return new ProductParsedResult(rawText, normalizedProductID);
}
}

View File

@ -0,0 +1,319 @@
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.client.result;
import com.google.zxing.Result;
import java.util.Hashtable;
import java.util.Vector;
/**
* <p>Abstract class representing the result of decoding a barcode, as more than
* a String -- as some type of structured data. This might be a subclass which represents
* a URL, or an e-mail address. {@link #parseResult(com.google.zxing.Result)} will turn a raw
* decoded string into the most appropriate type of structured representation.</p>
*
* <p>Thanks to Jeff Griffin for proposing rewrite of these classes that relies less
* on exception-based mechanisms during parsing.</p>
*
* @author Sean Owen
*/
public abstract class ResultParser {
public static ParsedResult parseResult(Result theResult) {
// This is a bit messy, but given limited options in MIDP / CLDC, this may well be the simplest
// way to go about this. For example, we have no reflection available, really.
// Order is important here.
ParsedResult result;
if ((result = BookmarkDoCoMoResultParser.parse(theResult)) != null) {
return result;
} else if ((result = AddressBookDoCoMoResultParser.parse(theResult)) != null) {
return result;
} else if ((result = EmailDoCoMoResultParser.parse(theResult)) != null) {
return result;
} else if ((result = AddressBookAUResultParser.parse(theResult)) != null) {
return result;
} else if ((result = VCardResultParser.parse(theResult)) != null) {
return result;
} else if ((result = BizcardResultParser.parse(theResult)) != null) {
return result;
} else if ((result = VEventResultParser.parse(theResult)) != null) {
return result;
} else if ((result = EmailAddressResultParser.parse(theResult)) != null) {
return result;
} else if ((result = TelResultParser.parse(theResult)) != null) {
return result;
} else if ((result = SMSMMSResultParser.parse(theResult)) != null) {
return result;
} else if ((result = SMSTOMMSTOResultParser.parse(theResult)) != null) {
return result;
} else if ((result = GeoResultParser.parse(theResult)) != null) {
return result;
} else if ((result = WifiResultParser.parse(theResult)) != null) {
return result;
} else if ((result = URLTOResultParser.parse(theResult)) != null) {
return result;
} else if ((result = URIResultParser.parse(theResult)) != null) {
return result;
} else if ((result = ISBNResultParser.parse(theResult)) != null) {
// We depend on ISBN parsing coming before UPC, as it is a subset.
return result;
} else if ((result = ProductResultParser.parse(theResult)) != null) {
return result;
} else if ((result = ExpandedProductResultParser.parse(theResult)) != null) {
return result;
}
return new TextParsedResult(theResult.getText(), null);
}
protected static void maybeAppend(String value, StringBuffer result) {
if (value != null) {
result.append('\n');
result.append(value);
}
}
protected static void maybeAppend(String[] value, StringBuffer result) {
if (value != null) {
for (int i = 0; i < value.length; i++) {
result.append('\n');
result.append(value[i]);
}
}
}
protected static String[] maybeWrap(String value) {
return value == null ? null : new String[] { value };
}
protected static String unescapeBackslash(String escaped) {
if (escaped != null) {
int backslash = escaped.indexOf((int) '\\');
if (backslash >= 0) {
int max = escaped.length();
StringBuffer unescaped = new StringBuffer(max - 1);
unescaped.append(escaped.toCharArray(), 0, backslash);
boolean nextIsEscaped = false;
for (int i = backslash; i < max; i++) {
char c = escaped.charAt(i);
if (nextIsEscaped || c != '\\') {
unescaped.append(c);
nextIsEscaped = false;
} else {
nextIsEscaped = true;
}
}
return unescaped.toString();
}
}
return escaped;
}
private static String urlDecode(String escaped) {
// No we can't use java.net.URLDecoder here. JavaME doesn't have it.
if (escaped == null) {
return null;
}
char[] escapedArray = escaped.toCharArray();
int first = findFirstEscape(escapedArray);
if (first < 0) {
return escaped;
}
int max = escapedArray.length;
// final length is at most 2 less than original due to at least 1 unescaping
StringBuffer unescaped = new StringBuffer(max - 2);
// Can append everything up to first escape character
unescaped.append(escapedArray, 0, first);
for (int i = first; i < max; i++) {
char c = escapedArray[i];
if (c == '+') {
// + is translated directly into a space
unescaped.append(' ');
} else if (c == '%') {
// Are there even two more chars? if not we will just copy the escaped sequence and be done
if (i >= max - 2) {
unescaped.append('%'); // append that % and move on
} else {
int firstDigitValue = parseHexDigit(escapedArray[++i]);
int secondDigitValue = parseHexDigit(escapedArray[++i]);
if (firstDigitValue < 0 || secondDigitValue < 0) {
// bad digit, just move on
unescaped.append('%');
unescaped.append(escapedArray[i-1]);
unescaped.append(escapedArray[i]);
}
unescaped.append((char) ((firstDigitValue << 4) + secondDigitValue));
}
} else {
unescaped.append(c);
}
}
return unescaped.toString();
}
private static int findFirstEscape(char[] escapedArray) {
int max = escapedArray.length;
for (int i = 0; i < max; i++) {
char c = escapedArray[i];
if (c == '+' || c == '%') {
return i;
}
}
return -1;
}
private static int parseHexDigit(char c) {
if (c >= 'a') {
if (c <= 'f') {
return 10 + (c - 'a');
}
} else if (c >= 'A') {
if (c <= 'F') {
return 10 + (c - 'A');
}
} else if (c >= '0') {
if (c <= '9') {
return c - '0';
}
}
return -1;
}
protected static boolean isStringOfDigits(String value, int length) {
if (value == null) {
return false;
}
int stringLength = value.length();
if (length != stringLength) {
return false;
}
for (int i = 0; i < length; i++) {
char c = value.charAt(i);
if (c < '0' || c > '9') {
return false;
}
}
return true;
}
protected static boolean isSubstringOfDigits(String value, int offset, int length) {
if (value == null) {
return false;
}
int stringLength = value.length();
int max = offset + length;
if (stringLength < max) {
return false;
}
for (int i = offset; i < max; i++) {
char c = value.charAt(i);
if (c < '0' || c > '9') {
return false;
}
}
return true;
}
static Hashtable parseNameValuePairs(String uri) {
int paramStart = uri.indexOf('?');
if (paramStart < 0) {
return null;
}
Hashtable result = new Hashtable(3);
paramStart++;
int paramEnd;
while ((paramEnd = uri.indexOf('&', paramStart)) >= 0) {
appendKeyValue(uri, paramStart, paramEnd, result);
paramStart = paramEnd + 1;
}
appendKeyValue(uri, paramStart, uri.length(), result);
return result;
}
private static void appendKeyValue(String uri, int paramStart, int paramEnd, Hashtable result) {
int separator = uri.indexOf('=', paramStart);
if (separator >= 0) {
// key = value
String key = uri.substring(paramStart, separator);
String value = uri.substring(separator + 1, paramEnd);
value = urlDecode(value);
result.put(key, value);
}
// Can't put key, null into a hashtable
}
static String[] matchPrefixedField(String prefix, String rawText, char endChar, boolean trim) {
Vector matches = null;
int i = 0;
int max = rawText.length();
while (i < max) {
i = rawText.indexOf(prefix, i);
if (i < 0) {
break;
}
i += prefix.length(); // Skip past this prefix we found to start
int start = i; // Found the start of a match here
boolean done = false;
while (!done) {
i = rawText.indexOf((int) endChar, i);
if (i < 0) {
// No terminating end character? uh, done. Set i such that loop terminates and break
i = rawText.length();
done = true;
} else if (rawText.charAt(i - 1) == '\\') {
// semicolon was escaped so continue
i++;
} else {
// found a match
if (matches == null) {
matches = new Vector(3); // lazy init
}
String element = unescapeBackslash(rawText.substring(start, i));
if (trim) {
element = element.trim();
}
matches.addElement(element);
i++;
done = true;
}
}
}
if (matches == null || matches.isEmpty()) {
return null;
}
return toStringArray(matches);
}
static String matchSinglePrefixedField(String prefix, String rawText, char endChar, boolean trim) {
String[] matches = matchPrefixedField(prefix, rawText, endChar, trim);
return matches == null ? null : matches[0];
}
static String[] toStringArray(Vector strings) {
int size = strings.size();
String[] result = new String[size];
for (int j = 0; j < size; j++) {
result[j] = (String) strings.elementAt(j);
}
return result;
}
}

View File

@ -0,0 +1,107 @@
/*
* Copyright 2008 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.client.result;
import com.google.zxing.Result;
import java.util.Hashtable;
import java.util.Vector;
/**
* <p>Parses an "sms:" URI result, which specifies a number to SMS.
* See <a href="http://tools.ietf.org/html/rfc5724"> RFC 5724</a> on this.</p>
*
* <p>This class supports "via" syntax for numbers, which is not part of the spec.
* For example "+12125551212;via=+12124440101" may appear as a number.
* It also supports a "subject" query parameter, which is not mentioned in the spec.
* These are included since they were mentioned in earlier IETF drafts and might be
* used.</p>
*
* <p>This actually also parses URIs starting with "mms:" and treats them all the same way,
* and effectively converts them to an "sms:" URI for purposes of forwarding to the platform.</p>
*
* @author Sean Owen
*/
final class SMSMMSResultParser extends ResultParser {
private SMSMMSResultParser() {
}
public static SMSParsedResult parse(Result result) {
String rawText = result.getText();
if (rawText == null) {
return null;
}
if (!(rawText.startsWith("sms:") || rawText.startsWith("SMS:") ||
rawText.startsWith("mms:") || rawText.startsWith("MMS:"))) {
return null;
}
// Check up front if this is a URI syntax string with query arguments
Hashtable nameValuePairs = parseNameValuePairs(rawText);
String subject = null;
String body = null;
boolean querySyntax = false;
if (nameValuePairs != null && !nameValuePairs.isEmpty()) {
subject = (String) nameValuePairs.get("subject");
body = (String) nameValuePairs.get("body");
querySyntax = true;
}
// Drop sms, query portion
int queryStart = rawText.indexOf('?', 4);
String smsURIWithoutQuery;
// If it's not query syntax, the question mark is part of the subject or message
if (queryStart < 0 || !querySyntax) {
smsURIWithoutQuery = rawText.substring(4);
} else {
smsURIWithoutQuery = rawText.substring(4, queryStart);
}
int lastComma = -1;
int comma;
Vector numbers = new Vector(1);
Vector vias = new Vector(1);
while ((comma = smsURIWithoutQuery.indexOf(',', lastComma + 1)) > lastComma) {
String numberPart = smsURIWithoutQuery.substring(lastComma + 1, comma);
addNumberVia(numbers, vias, numberPart);
lastComma = comma;
}
addNumberVia(numbers, vias, smsURIWithoutQuery.substring(lastComma + 1));
return new SMSParsedResult(toStringArray(numbers), toStringArray(vias), subject, body);
}
private static void addNumberVia(Vector numbers, Vector vias, String numberPart) {
int numberEnd = numberPart.indexOf(';');
if (numberEnd < 0) {
numbers.addElement(numberPart);
vias.addElement(null);
} else {
numbers.addElement(numberPart.substring(0, numberEnd));
String maybeVia = numberPart.substring(numberEnd + 1);
String via;
if (maybeVia.startsWith("via=")) {
via = maybeVia.substring(4);
} else {
via = null;
}
vias.addElement(via);
}
}
}

View File

@ -0,0 +1,104 @@
/*
* Copyright 2008 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.client.result;
/**
* @author Sean Owen
*/
public final class SMSParsedResult extends ParsedResult {
private final String[] numbers;
private final String[] vias;
private final String subject;
private final String body;
public SMSParsedResult(String number, String via, String subject, String body) {
super(ParsedResultType.SMS);
this.numbers = new String[] {number};
this.vias = new String[] {via};
this.subject = subject;
this.body = body;
}
public SMSParsedResult(String[] numbers, String[] vias, String subject, String body) {
super(ParsedResultType.SMS);
this.numbers = numbers;
this.vias = vias;
this.subject = subject;
this.body = body;
}
public String getSMSURI() {
StringBuffer result = new StringBuffer();
result.append("sms:");
boolean first = true;
for (int i = 0; i < numbers.length; i++) {
if (first) {
first = false;
} else {
result.append(',');
}
result.append(numbers[i]);
if (vias[i] != null) {
result.append(";via=");
result.append(vias[i]);
}
}
boolean hasBody = body != null;
boolean hasSubject = subject != null;
if (hasBody || hasSubject) {
result.append('?');
if (hasBody) {
result.append("body=");
result.append(body);
}
if (hasSubject) {
if (hasBody) {
result.append('&');
}
result.append("subject=");
result.append(subject);
}
}
return result.toString();
}
public String[] getNumbers() {
return numbers;
}
public String[] getVias() {
return vias;
}
public String getSubject() {
return subject;
}
public String getBody() {
return body;
}
public String getDisplayResult() {
StringBuffer result = new StringBuffer(100);
maybeAppend(numbers, result);
maybeAppend(subject, result);
maybeAppend(body, result);
return result.toString();
}
}

View File

@ -0,0 +1,57 @@
/*
* Copyright 2008 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.client.result;
import com.google.zxing.Result;
/**
* <p>Parses an "smsto:" URI result, whose format is not standardized but appears to be like:
* <code>smsto:number(:body)</code>.</p>
*
* <p>This actually also parses URIs starting with "smsto:", "mmsto:", "SMSTO:", and
* "MMSTO:", and treats them all the same way, and effectively converts them to an "sms:" URI
* for purposes of forwarding to the platform.</p>
*
* @author Sean Owen
*/
final class SMSTOMMSTOResultParser extends ResultParser {
private SMSTOMMSTOResultParser() {
}
public static SMSParsedResult parse(Result result) {
String rawText = result.getText();
if (rawText == null) {
return null;
}
if (!(rawText.startsWith("smsto:") || rawText.startsWith("SMSTO:") ||
rawText.startsWith("mmsto:") || rawText.startsWith("MMSTO:"))) {
return null;
}
// Thanks to dominik.wild for suggesting this enhancement to support
// smsto:number:body URIs
String number = rawText.substring(6);
String body = null;
int bodyStart = number.indexOf(':');
if (bodyStart >= 0) {
body = number.substring(bodyStart + 1);
number = number.substring(0, bodyStart);
}
return new SMSParsedResult(number, null, null, body);
}
}

View File

@ -0,0 +1,54 @@
/*
* Copyright 2008 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.client.result;
/**
* @author Sean Owen
*/
public final class TelParsedResult extends ParsedResult {
private final String number;
private final String telURI;
private final String title;
public TelParsedResult(String number, String telURI, String title) {
super(ParsedResultType.TEL);
this.number = number;
this.telURI = telURI;
this.title = title;
}
public String getNumber() {
return number;
}
public String getTelURI() {
return telURI;
}
public String getTitle() {
return title;
}
public String getDisplayResult() {
StringBuffer result = new StringBuffer(20);
maybeAppend(number, result);
maybeAppend(title, result);
return result.toString();
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2008 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.client.result;
import com.google.zxing.Result;
/**
* Parses a "tel:" URI result, which specifies a phone number.
*
* @author Sean Owen
*/
final class TelResultParser extends ResultParser {
private TelResultParser() {
}
public static TelParsedResult parse(Result result) {
String rawText = result.getText();
if (rawText == null || (!rawText.startsWith("tel:") && !rawText.startsWith("TEL:"))) {
return null;
}
// Normalize "TEL:" to "tel:"
String telURI = rawText.startsWith("TEL:") ? "tel:" + rawText.substring(4) : rawText;
// Drop tel, query portion
int queryStart = rawText.indexOf('?', 4);
String number = queryStart < 0 ? rawText.substring(4) : rawText.substring(4, queryStart);
return new TelParsedResult(number, telURI, null);
}
}

View File

@ -0,0 +1,48 @@
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.client.result;
/**
* A simple result type encapsulating a string that has no further
* interpretation.
*
* @author Sean Owen
*/
public final class TextParsedResult extends ParsedResult {
private final String text;
private final String language;
public TextParsedResult(String text, String language) {
super(ParsedResultType.TEXT);
this.text = text;
this.language = language;
}
public String getText() {
return text;
}
public String getLanguage() {
return language;
}
public String getDisplayResult() {
return text;
}
}

View File

@ -0,0 +1,113 @@
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.client.result;
/**
* @author Sean Owen
*/
public final class URIParsedResult extends ParsedResult {
private final String uri;
private final String title;
public URIParsedResult(String uri, String title) {
super(ParsedResultType.URI);
this.uri = massageURI(uri);
this.title = title;
}
public String getURI() {
return uri;
}
public String getTitle() {
return title;
}
/**
* @return true if the URI contains suspicious patterns that may suggest it intends to
* mislead the user about its true nature. At the moment this looks for the presence
* of user/password syntax in the host/authority portion of a URI which may be used
* in attempts to make the URI's host appear to be other than it is. Example:
* http://yourbank.com@phisher.com This URI connects to phisher.com but may appear
* to connect to yourbank.com at first glance.
*/
public boolean isPossiblyMaliciousURI() {
return containsUser();
}
private boolean containsUser() {
// This method is likely not 100% RFC compliant yet
int hostStart = uri.indexOf(':'); // we should always have scheme at this point
hostStart++;
// Skip slashes preceding host
int uriLength = uri.length();
while (hostStart < uriLength && uri.charAt(hostStart) == '/') {
hostStart++;
}
int hostEnd = uri.indexOf('/', hostStart);
if (hostEnd < 0) {
hostEnd = uriLength;
}
int at = uri.indexOf('@', hostStart);
return at >= hostStart && at < hostEnd;
}
public String getDisplayResult() {
StringBuffer result = new StringBuffer(30);
maybeAppend(title, result);
maybeAppend(uri, result);
return result.toString();
}
/**
* Transforms a string that represents a URI into something more proper, by adding or canonicalizing
* the protocol.
*/
private static String massageURI(String uri) {
int protocolEnd = uri.indexOf(':');
if (protocolEnd < 0) {
// No protocol, assume http
uri = "http://" + uri;
} else if (isColonFollowedByPortNumber(uri, protocolEnd)) {
// Found a colon, but it looks like it is after the host, so the protocol is still missing
uri = "http://" + uri;
} else {
// Lowercase protocol to avoid problems
uri = uri.substring(0, protocolEnd).toLowerCase() + uri.substring(protocolEnd);
}
return uri;
}
private static boolean isColonFollowedByPortNumber(String uri, int protocolEnd) {
int nextSlash = uri.indexOf('/', protocolEnd + 1);
if (nextSlash < 0) {
nextSlash = uri.length();
}
if (nextSlash <= protocolEnd + 1) {
return false;
}
for (int x = protocolEnd + 1; x < nextSlash; x++) {
if (uri.charAt(x) < '0' || uri.charAt(x) > '9') {
return false;
}
}
return true;
}
}

View File

@ -0,0 +1,87 @@
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.client.result;
import com.google.zxing.Result;
/**
* Tries to parse results that are a URI of some kind.
*
* @author Sean Owen
*/
final class URIResultParser extends ResultParser {
private URIResultParser() {
}
public static URIParsedResult parse(Result result) {
String rawText = result.getText();
// We specifically handle the odd "URL" scheme here for simplicity
if (rawText != null && rawText.startsWith("URL:")) {
rawText = rawText.substring(4);
}
if (!isBasicallyValidURI(rawText)) {
return null;
}
return new URIParsedResult(rawText, null);
}
/**
* Determines whether a string is not obviously not a URI. This implements crude checks; this class does not
* intend to strictly check URIs as its only function is to represent what is in a barcode, but, it does
* need to know when a string is obviously not a URI.
*/
static boolean isBasicallyValidURI(String uri) {
if (uri == null || uri.indexOf(' ') >= 0 || uri.indexOf('\n') >= 0) {
return false;
}
// Look for period in a domain but followed by at least a two-char TLD
// Forget strings that don't have a valid-looking protocol
int period = uri.indexOf('.');
if (period >= uri.length() - 2) {
return false;
}
int colon = uri.indexOf(':');
if (period < 0 && colon < 0) {
return false;
}
if (colon >= 0) {
if (period < 0 || period > colon) {
// colon ends the protocol
for (int i = 0; i < colon; i++) {
char c = uri.charAt(i);
if ((c < 'a' || c > 'z') && (c < 'A' || c > 'Z')) {
return false;
}
}
} else {
// colon starts the port; crudely look for at least two numbers
if (colon >= uri.length() - 2) {
return false;
}
for (int i = colon + 1; i < colon + 3; i++) {
char c = uri.charAt(i);
if (c < '0' || c > '9') {
return false;
}
}
}
}
return true;
}
}

View File

@ -0,0 +1,47 @@
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.client.result;
import com.google.zxing.Result;
/**
* Parses the "URLTO" result format, which is of the form "URLTO:[title]:[url]".
* This seems to be used sometimes, but I am not able to find documentation
* on its origin or official format?
*
* @author Sean Owen
*/
final class URLTOResultParser {
private URLTOResultParser() {
}
public static URIParsedResult parse(Result result) {
String rawText = result.getText();
if (rawText == null || (!rawText.startsWith("urlto:") && !rawText.startsWith("URLTO:"))) {
return null;
}
int titleEnd = rawText.indexOf(':', 6);
if (titleEnd < 0) {
return null;
}
String title = titleEnd <= 6 ? null : rawText.substring(6, titleEnd);
String uri = rawText.substring(titleEnd + 1);
return new URIParsedResult(uri, title);
}
}

View File

@ -0,0 +1,346 @@
/*
* Copyright 2008 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.client.result;
import com.google.zxing.Result;
import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
import java.util.Vector;
/**
* Parses contact information formatted according to the VCard (2.1) format. This is not a complete
* implementation but should parse information as commonly encoded in 2D barcodes.
*
* @author Sean Owen
*/
final class VCardResultParser extends ResultParser {
private VCardResultParser() {
}
public static AddressBookParsedResult parse(Result result) {
// Although we should insist on the raw text ending with "END:VCARD", there's no reason
// to throw out everything else we parsed just because this was omitted. In fact, Eclair
// is doing just that, and we can't parse its contacts without this leniency.
String rawText = result.getText();
if (rawText == null || !rawText.startsWith("BEGIN:VCARD")) {
return null;
}
String[] names = matchVCardPrefixedField("FN", rawText, true);
if (names == null) {
// If no display names found, look for regular name fields and format them
names = matchVCardPrefixedField("N", rawText, true);
formatNames(names);
}
String[] phoneNumbers = matchVCardPrefixedField("TEL", rawText, true);
String[] emails = matchVCardPrefixedField("EMAIL", rawText, true);
String note = matchSingleVCardPrefixedField("NOTE", rawText, false);
String[] addresses = matchVCardPrefixedField("ADR", rawText, true);
if (addresses != null) {
for (int i = 0; i < addresses.length; i++) {
addresses[i] = formatAddress(addresses[i]);
}
}
String org = matchSingleVCardPrefixedField("ORG", rawText, true);
String birthday = matchSingleVCardPrefixedField("BDAY", rawText, true);
if (!isLikeVCardDate(birthday)) {
birthday = null;
}
String title = matchSingleVCardPrefixedField("TITLE", rawText, true);
String url = matchSingleVCardPrefixedField("URL", rawText, true);
return new AddressBookParsedResult(names, null, phoneNumbers, emails, note, addresses, org,
birthday, title, url);
}
private static String[] matchVCardPrefixedField(String prefix, String rawText, boolean trim) {
Vector matches = null;
int i = 0;
int max = rawText.length();
while (i < max) {
i = rawText.indexOf(prefix, i);
if (i < 0) {
break;
}
if (i > 0 && rawText.charAt(i - 1) != '\n') {
// then this didn't start a new token, we matched in the middle of something
i++;
continue;
}
i += prefix.length(); // Skip past this prefix we found to start
if (rawText.charAt(i) != ':' && rawText.charAt(i) != ';') {
continue;
}
int metadataStart = i;
while (rawText.charAt(i) != ':') { // Skip until a colon
i++;
}
boolean quotedPrintable = false;
String quotedPrintableCharset = null;
if (i > metadataStart) {
// There was something after the tag, before colon
int j = metadataStart+1;
while (j <= i) {
if (rawText.charAt(j) == ';' || rawText.charAt(j) == ':') {
String metadata = rawText.substring(metadataStart+1, j);
int equals = metadata.indexOf('=');
if (equals >= 0) {
String key = metadata.substring(0, equals);
String value = metadata.substring(equals+1);
if (key.equalsIgnoreCase("ENCODING")) {
if (value.equalsIgnoreCase("QUOTED-PRINTABLE")) {
quotedPrintable = true;
}
} else if (key.equalsIgnoreCase("CHARSET")) {
quotedPrintableCharset = value;
}
}
metadataStart = j;
}
j++;
}
}
i++; // skip colon
int matchStart = i; // Found the start of a match here
while ((i = rawText.indexOf((int) '\n', i)) >= 0) { // Really, end in \r\n
if (i < rawText.length() - 1 && // But if followed by tab or space,
(rawText.charAt(i+1) == ' ' || // this is only a continuation
rawText.charAt(i+1) == '\t')) {
i += 2; // Skip \n and continutation whitespace
} else if (quotedPrintable && // If preceded by = in quoted printable
(rawText.charAt(i-1) == '=' || // this is a continuation
rawText.charAt(i-2) == '=')) {
i++; // Skip \n
} else {
break;
}
}
if (i < 0) {
// No terminating end character? uh, done. Set i such that loop terminates and break
i = max;
} else if (i > matchStart) {
// found a match
if (matches == null) {
matches = new Vector(1); // lazy init
}
if (rawText.charAt(i-1) == '\r') {
i--; // Back up over \r, which really should be there
}
String element = rawText.substring(matchStart, i);
if (trim) {
element = element.trim();
}
if (quotedPrintable) {
element = decodeQuotedPrintable(element, quotedPrintableCharset);
} else {
element = stripContinuationCRLF(element);
}
matches.addElement(element);
i++;
} else {
i++;
}
}
if (matches == null || matches.isEmpty()) {
return null;
}
return toStringArray(matches);
}
private static String stripContinuationCRLF(String value) {
int length = value.length();
StringBuffer result = new StringBuffer(length);
boolean lastWasLF = false;
for (int i = 0; i < length; i++) {
if (lastWasLF) {
lastWasLF = false;
continue;
}
char c = value.charAt(i);
lastWasLF = false;
switch (c) {
case '\n':
lastWasLF = true;
break;
case '\r':
break;
default:
result.append(c);
}
}
return result.toString();
}
private static String decodeQuotedPrintable(String value, String charset) {
int length = value.length();
StringBuffer result = new StringBuffer(length);
ByteArrayOutputStream fragmentBuffer = new ByteArrayOutputStream();
for (int i = 0; i < length; i++) {
char c = value.charAt(i);
switch (c) {
case '\r':
case '\n':
break;
case '=':
if (i < length - 2) {
char nextChar = value.charAt(i+1);
if (nextChar == '\r' || nextChar == '\n') {
// Ignore, it's just a continuation symbol
} else {
char nextNextChar = value.charAt(i+2);
try {
int encodedByte = 16 * toHexValue(nextChar) + toHexValue(nextNextChar);
fragmentBuffer.write(encodedByte);
} catch (IllegalArgumentException iae) {
// continue, assume it was incorrectly encoded
}
i += 2;
}
}
break;
default:
maybeAppendFragment(fragmentBuffer, charset, result);
result.append(c);
}
}
maybeAppendFragment(fragmentBuffer, charset, result);
return result.toString();
}
private static int toHexValue(char c) {
if (c >= '0' && c <= '9') {
return c - '0';
} else if (c >= 'A' && c <= 'F') {
return c - 'A' + 10;
} else if (c >= 'a' && c <= 'f') {
return c - 'a' + 10;
}
throw new IllegalArgumentException();
}
private static void maybeAppendFragment(ByteArrayOutputStream fragmentBuffer,
String charset,
StringBuffer result) {
if (fragmentBuffer.size() > 0) {
byte[] fragmentBytes = fragmentBuffer.toByteArray();
String fragment;
if (charset == null) {
fragment = new String(fragmentBytes);
} else {
try {
fragment = new String(fragmentBytes, charset);
} catch (UnsupportedEncodingException e) {
// Yikes, well try anyway:
fragment = new String(fragmentBytes);
}
}
fragmentBuffer.reset();
result.append(fragment);
}
}
static String matchSingleVCardPrefixedField(String prefix, String rawText, boolean trim) {
String[] values = matchVCardPrefixedField(prefix, rawText, trim);
return values == null ? null : values[0];
}
private static boolean isLikeVCardDate(String value) {
if (value == null) {
return true;
}
// Not really sure this is true but matches practice
// Mach YYYYMMDD
if (isStringOfDigits(value, 8)) {
return true;
}
// or YYYY-MM-DD
return
value.length() == 10 &&
value.charAt(4) == '-' &&
value.charAt(7) == '-' &&
isSubstringOfDigits(value, 0, 4) &&
isSubstringOfDigits(value, 5, 2) &&
isSubstringOfDigits(value, 8, 2);
}
private static String formatAddress(String address) {
if (address == null) {
return null;
}
int length = address.length();
StringBuffer newAddress = new StringBuffer(length);
for (int j = 0; j < length; j++) {
char c = address.charAt(j);
if (c == ';') {
newAddress.append(' ');
} else {
newAddress.append(c);
}
}
return newAddress.toString().trim();
}
/**
* Formats name fields of the form "Public;John;Q.;Reverend;III" into a form like
* "Reverend John Q. Public III".
*
* @param names name values to format, in place
*/
private static void formatNames(String[] names) {
if (names != null) {
for (int i = 0; i < names.length; i++) {
String name = names[i];
String[] components = new String[5];
int start = 0;
int end;
int componentIndex = 0;
while ((end = name.indexOf(';', start)) > 0) {
components[componentIndex] = name.substring(start, end);
componentIndex++;
start = end + 1;
}
components[componentIndex] = name.substring(start);
StringBuffer newName = new StringBuffer(100);
maybeAppendComponent(components, 3, newName);
maybeAppendComponent(components, 1, newName);
maybeAppendComponent(components, 2, newName);
maybeAppendComponent(components, 0, newName);
maybeAppendComponent(components, 4, newName);
names[i] = newName.toString().trim();
}
}
}
private static void maybeAppendComponent(String[] components, int i, StringBuffer newName) {
if (components[i] != null) {
newName.append(' ');
newName.append(components[i]);
}
}
}

View File

@ -0,0 +1,54 @@
/*
* Copyright 2008 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.client.result;
import com.google.zxing.Result;
/**
* Partially implements the iCalendar format's "VEVENT" format for specifying a
* calendar event. See RFC 2445. This supports SUMMARY, LOCATION, DTSTART and DTEND fields.
*
* @author Sean Owen
*/
final class VEventResultParser extends ResultParser {
private VEventResultParser() {
}
public static CalendarParsedResult parse(Result result) {
String rawText = result.getText();
if (rawText == null) {
return null;
}
int vEventStart = rawText.indexOf("BEGIN:VEVENT");
if (vEventStart < 0) {
return null;
}
String summary = VCardResultParser.matchSingleVCardPrefixedField("SUMMARY", rawText, true);
String start = VCardResultParser.matchSingleVCardPrefixedField("DTSTART", rawText, true);
String end = VCardResultParser.matchSingleVCardPrefixedField("DTEND", rawText, true);
String location = VCardResultParser.matchSingleVCardPrefixedField("LOCATION", rawText, true);
String description = VCardResultParser.matchSingleVCardPrefixedField("DESCRIPTION", rawText, true);
try {
return new CalendarParsedResult(summary, start, end, location, null, description);
} catch (IllegalArgumentException iae) {
return null;
}
}
}

View File

@ -0,0 +1,53 @@
/*
* Copyright 2010 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.client.result;
/**
* @author Vikram Aggarwal
*/
public final class WifiParsedResult extends ParsedResult {
private final String ssid;
private final String networkEncryption;
private final String password;
public WifiParsedResult(String networkEncryption, String ssid, String password) {
super(ParsedResultType.WIFI);
this.ssid = ssid;
this.networkEncryption = networkEncryption;
this.password = password;
}
public String getSsid() {
return ssid;
}
public String getNetworkEncryption() {
return networkEncryption;
}
public String getPassword() {
return password;
}
public String getDisplayResult() {
StringBuffer result = new StringBuffer(80);
maybeAppend(ssid, result);
maybeAppend(networkEncryption, result);
maybeAppend(password, result);
return result.toString();
}
}

View File

@ -0,0 +1,50 @@
/*
* Copyright 2010 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.client.result;
import com.google.zxing.Result;
/**
* Parses a WIFI configuration string. Strings will be of the form:
* WIFI:T:WPA;S:mynetwork;P:mypass;;
*
* The fields can come in any order, and there should be tests to see
* if we can parse them all correctly.
*
* @author Vikram Aggarwal
*/
final class WifiResultParser extends ResultParser {
private WifiResultParser() {
}
public static WifiParsedResult parse(Result result) {
String rawText = result.getText();
if (rawText == null || !rawText.startsWith("WIFI:")) {
return null;
}
// Don't remove leading or trailing whitespace
boolean trim = false;
String ssid = matchSinglePrefixedField("S:", rawText, ';', trim);
String pass = matchSinglePrefixedField("P:", rawText, ';', trim);
String type = matchSinglePrefixedField("T:", rawText, ';', trim);
return new WifiParsedResult(type, ssid, pass);
}
}

View File

@ -0,0 +1,45 @@
/*
* Copyright 2008 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.client.result.optional;
import com.google.zxing.client.result.ResultParser;
import java.io.UnsupportedEncodingException;
/**
* <p>Superclass for classes encapsulating results in the NDEF format.
* See <a href="http://www.nfc-forum.org/specs/">http://www.nfc-forum.org/specs/</a>.</p>
*
* <p>This code supports a limited subset of NDEF messages, ones that are plausibly
* useful in 2D barcode formats. This generally includes 1-record messages, no chunking,
* "short record" syntax, no ID field.</p>
*
* @author Sean Owen
*/
abstract class AbstractNDEFResultParser extends ResultParser {
static String bytesToString(byte[] bytes, int offset, int length, String encoding) {
try {
return new String(bytes, offset, length, encoding);
} catch (UnsupportedEncodingException uee) {
// This should only be used when 'encoding' is an encoding that must necessarily
// be supported by the JVM, like UTF-8
throw new RuntimeException("Platform does not support required encoding: " + uee);
}
}
}

View File

@ -0,0 +1,87 @@
/*
* Copyright 2008 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.client.result.optional;
/**
* <p>Represents a record in an NDEF message. This class only supports certain types
* of records -- namely, non-chunked records, where ID length is omitted, and only
* "short records".</p>
*
* @author Sean Owen
*/
final class NDEFRecord {
private static final int SUPPORTED_HEADER_MASK = 0x3F; // 0 0 1 1 1 111 (the bottom 6 bits matter)
private static final int SUPPORTED_HEADER = 0x11; // 0 0 0 1 0 001
public static final String TEXT_WELL_KNOWN_TYPE = "T";
public static final String URI_WELL_KNOWN_TYPE = "U";
public static final String SMART_POSTER_WELL_KNOWN_TYPE = "Sp";
public static final String ACTION_WELL_KNOWN_TYPE = "act";
private final int header;
private final String type;
private final byte[] payload;
private final int totalRecordLength;
private NDEFRecord(int header, String type, byte[] payload, int totalRecordLength) {
this.header = header;
this.type = type;
this.payload = payload;
this.totalRecordLength = totalRecordLength;
}
static NDEFRecord readRecord(byte[] bytes, int offset) {
int header = bytes[offset] & 0xFF;
// Does header match what we support in the bits we care about?
// XOR figures out where we differ, and if any of those are in the mask, fail
if (((header ^ SUPPORTED_HEADER) & SUPPORTED_HEADER_MASK) != 0) {
return null;
}
int typeLength = bytes[offset + 1] & 0xFF;
int payloadLength = bytes[offset + 2] & 0xFF;
String type = AbstractNDEFResultParser.bytesToString(bytes, offset + 3, typeLength, "US-ASCII");
byte[] payload = new byte[payloadLength];
System.arraycopy(bytes, offset + 3 + typeLength, payload, 0, payloadLength);
return new NDEFRecord(header, type, payload, 3 + typeLength + payloadLength);
}
boolean isMessageBegin() {
return (header & 0x80) != 0;
}
boolean isMessageEnd() {
return (header & 0x40) != 0;
}
String getType() {
return type;
}
byte[] getPayload() {
return payload;
}
int getTotalRecordLength() {
return totalRecordLength;
}
}

View File

@ -0,0 +1,63 @@
/*
* Copyright 2008 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.client.result.optional;
import com.google.zxing.client.result.ParsedResult;
import com.google.zxing.client.result.ParsedResultType;
/**
* @author Sean Owen
*/
public final class NDEFSmartPosterParsedResult extends ParsedResult {
public static final int ACTION_UNSPECIFIED = -1;
public static final int ACTION_DO = 0;
public static final int ACTION_SAVE = 1;
public static final int ACTION_OPEN = 2;
private final String title;
private final String uri;
private final int action;
NDEFSmartPosterParsedResult(int action, String uri, String title) {
super(ParsedResultType.NDEF_SMART_POSTER);
this.action = action;
this.uri = uri;
this.title = title;
}
public String getTitle() {
return title;
}
public String getURI() {
return uri;
}
public int getAction() {
return action;
}
public String getDisplayResult() {
if (title == null) {
return uri;
} else {
return title + '\n' + uri;
}
}
}

View File

@ -0,0 +1,81 @@
/*
* Copyright 2008 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.client.result.optional;
import com.google.zxing.Result;
/**
* <p>Recognizes an NDEF message that encodes information according to the
* "Smart Poster Record Type Definition" specification.</p>
*
* <p>This actually only supports some parts of the Smart Poster format: title,
* URI, and action records. Icon records are not supported because the size
* of these records are infeasibly large for barcodes. Size and type records
* are not supported. Multiple titles are not supported.</p>
*
* @author Sean Owen
*/
final class NDEFSmartPosterResultParser extends AbstractNDEFResultParser {
public static NDEFSmartPosterParsedResult parse(Result result) {
byte[] bytes = result.getRawBytes();
if (bytes == null) {
return null;
}
NDEFRecord headerRecord = NDEFRecord.readRecord(bytes, 0);
// Yes, header record starts and ends a message
if (headerRecord == null || !headerRecord.isMessageBegin() || !headerRecord.isMessageEnd()) {
return null;
}
if (!headerRecord.getType().equals(NDEFRecord.SMART_POSTER_WELL_KNOWN_TYPE)) {
return null;
}
int offset = 0;
int recordNumber = 0;
NDEFRecord ndefRecord = null;
byte[] payload = headerRecord.getPayload();
int action = NDEFSmartPosterParsedResult.ACTION_UNSPECIFIED;
String title = null;
String uri = null;
while (offset < payload.length && (ndefRecord = NDEFRecord.readRecord(payload, offset)) != null) {
if (recordNumber == 0 && !ndefRecord.isMessageBegin()) {
return null;
}
String type = ndefRecord.getType();
if (NDEFRecord.TEXT_WELL_KNOWN_TYPE.equals(type)) {
String[] languageText = NDEFTextResultParser.decodeTextPayload(ndefRecord.getPayload());
title = languageText[1];
} else if (NDEFRecord.URI_WELL_KNOWN_TYPE.equals(type)) {
uri = NDEFURIResultParser.decodeURIPayload(ndefRecord.getPayload());
} else if (NDEFRecord.ACTION_WELL_KNOWN_TYPE.equals(type)) {
action = ndefRecord.getPayload()[0];
}
recordNumber++;
offset += ndefRecord.getTotalRecordLength();
}
if (recordNumber == 0 || (ndefRecord != null && !ndefRecord.isMessageEnd())) {
return null;
}
return new NDEFSmartPosterParsedResult(action, uri, title);
}
}

View File

@ -0,0 +1,57 @@
/*
* Copyright 2008 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.client.result.optional;
import com.google.zxing.Result;
import com.google.zxing.client.result.TextParsedResult;
/**
* Recognizes an NDEF message that encodes text according to the
* "Text Record Type Definition" specification.
*
* @author Sean Owen
*/
final class NDEFTextResultParser extends AbstractNDEFResultParser {
public static TextParsedResult parse(Result result) {
byte[] bytes = result.getRawBytes();
if (bytes == null) {
return null;
}
NDEFRecord ndefRecord = NDEFRecord.readRecord(bytes, 0);
if (ndefRecord == null || !ndefRecord.isMessageBegin() || !ndefRecord.isMessageEnd()) {
return null;
}
if (!ndefRecord.getType().equals(NDEFRecord.TEXT_WELL_KNOWN_TYPE)) {
return null;
}
String[] languageText = decodeTextPayload(ndefRecord.getPayload());
return new TextParsedResult(languageText[0], languageText[1]);
}
static String[] decodeTextPayload(byte[] payload) {
byte statusByte = payload[0];
boolean isUTF16 = (statusByte & 0x80) != 0;
int languageLength = statusByte & 0x1F;
// language is always ASCII-encoded:
String language = bytesToString(payload, 1, languageLength, "US-ASCII");
String encoding = isUTF16 ? "UTF-16" : "UTF8";
String text = bytesToString(payload, 1 + languageLength, payload.length - languageLength - 1, encoding);
return new String[] { language, text };
}
}

View File

@ -0,0 +1,95 @@
/*
* Copyright 2008 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.client.result.optional;
import com.google.zxing.Result;
import com.google.zxing.client.result.URIParsedResult;
/**
* Recognizes an NDEF message that encodes a URI according to the
* "URI Record Type Definition" specification.
*
* @author Sean Owen
*/
final class NDEFURIResultParser extends AbstractNDEFResultParser {
private static final String[] URI_PREFIXES = {
null,
"http://www.",
"https://www.",
"http://",
"https://",
"tel:",
"mailto:",
"ftp://anonymous:anonymous@",
"ftp://ftp.",
"ftps://",
"sftp://",
"smb://",
"nfs://",
"ftp://",
"dav://",
"news:",
"telnet://",
"imap:",
"rtsp://",
"urn:",
"pop:",
"sip:",
"sips:",
"tftp:",
"btspp://",
"btl2cap://",
"btgoep://",
"tcpobex://",
"irdaobex://",
"file://",
"urn:epc:id:",
"urn:epc:tag:",
"urn:epc:pat:",
"urn:epc:raw:",
"urn:epc:",
"urn:nfc:",
};
public static URIParsedResult parse(Result result) {
byte[] bytes = result.getRawBytes();
if (bytes == null) {
return null;
}
NDEFRecord ndefRecord = NDEFRecord.readRecord(bytes, 0);
if (ndefRecord == null || !ndefRecord.isMessageBegin() || !ndefRecord.isMessageEnd()) {
return null;
}
if (!ndefRecord.getType().equals(NDEFRecord.URI_WELL_KNOWN_TYPE)) {
return null;
}
String fullURI = decodeURIPayload(ndefRecord.getPayload());
return new URIParsedResult(fullURI, null);
}
static String decodeURIPayload(byte[] payload) {
int identifierCode = payload[0] & 0xFF;
String prefix = null;
if (identifierCode < URI_PREFIXES.length) {
prefix = URI_PREFIXES[identifierCode];
}
String restOfURI = bytesToString(payload, 1, payload.length - 1, "UTF8");
return prefix == null ? restOfURI : prefix + restOfURI;
}
}

View File

@ -0,0 +1,247 @@
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.common;
/**
* <p>A simple, fast array of bits, represented compactly by an array of ints internally.</p>
*
* @author Sean Owen
*/
public final class BitArray {
// TODO: I have changed these members to be public so ProGuard can inline get() and set(). Ideally
// they'd be private and we'd use the -allowaccessmodification flag, but Dalvik rejects the
// resulting binary at runtime on Android. If we find a solution to this, these should be changed
// back to private.
public int[] bits;
public int size;
public BitArray() {
this.size = 0;
this.bits = new int[1];
}
public BitArray(int size) {
this.size = size;
this.bits = makeArray(size);
}
public int getSize() {
return size;
}
public int getSizeInBytes() {
return (size + 7) >> 3;
}
private void ensureCapacity(int size) {
if (size > bits.length << 5) {
int[] newBits = makeArray(size);
System.arraycopy(bits, 0, newBits, 0, bits.length);
this.bits = newBits;
}
}
/**
* @param i bit to get
* @return true iff bit i is set
*/
public boolean get(int i) {
return (bits[i >> 5] & (1 << (i & 0x1F))) != 0;
}
/**
* Sets bit i.
*
* @param i bit to set
*/
public void set(int i) {
bits[i >> 5] |= 1 << (i & 0x1F);
}
/**
* Flips bit i.
*
* @param i bit to set
*/
public void flip(int i) {
bits[i >> 5] ^= 1 << (i & 0x1F);
}
/**
* Sets a block of 32 bits, starting at bit i.
*
* @param i first bit to set
* @param newBits the new value of the next 32 bits. Note again that the least-significant bit
* corresponds to bit i, the next-least-significant to i+1, and so on.
*/
public void setBulk(int i, int newBits) {
bits[i >> 5] = newBits;
}
/**
* Clears all bits (sets to false).
*/
public void clear() {
int max = bits.length;
for (int i = 0; i < max; i++) {
bits[i] = 0;
}
}
/**
* Efficient method to check if a range of bits is set, or not set.
*
* @param start start of range, inclusive.
* @param end end of range, exclusive
* @param value if true, checks that bits in range are set, otherwise checks that they are not set
* @return true iff all bits are set or not set in range, according to value argument
* @throws IllegalArgumentException if end is less than or equal to start
*/
public boolean isRange(int start, int end, boolean value) {
if (end < start) {
throw new IllegalArgumentException();
}
if (end == start) {
return true; // empty range matches
}
end--; // will be easier to treat this as the last actually set bit -- inclusive
int firstInt = start >> 5;
int lastInt = end >> 5;
for (int i = firstInt; i <= lastInt; i++) {
int firstBit = i > firstInt ? 0 : start & 0x1F;
int lastBit = i < lastInt ? 31 : end & 0x1F;
int mask;
if (firstBit == 0 && lastBit == 31) {
mask = -1;
} else {
mask = 0;
for (int j = firstBit; j <= lastBit; j++) {
mask |= 1 << j;
}
}
// Return false if we're looking for 1s and the masked bits[i] isn't all 1s (that is,
// equals the mask, or we're looking for 0s and the masked portion is not all 0s
if ((bits[i] & mask) != (value ? mask : 0)) {
return false;
}
}
return true;
}
public void appendBit(boolean bit) {
ensureCapacity(size + 1);
if (bit) {
bits[size >> 5] |= (1 << (size & 0x1F));
}
size++;
}
/**
* Appends the least-significant bits, from value, in order from most-significant to
* least-significant. For example, appending 6 bits from 0x000001E will append the bits
* 0, 1, 1, 1, 1, 0 in that order.
*/
public void appendBits(int value, int numBits) {
if (numBits < 0 || numBits > 32) {
throw new IllegalArgumentException("Num bits must be between 0 and 32");
}
ensureCapacity(size + numBits);
for (int numBitsLeft = numBits; numBitsLeft > 0; numBitsLeft--) {
appendBit(((value >> (numBitsLeft - 1)) & 0x01) == 1);
}
}
public void appendBitArray(BitArray other) {
int otherSize = other.getSize();
ensureCapacity(size + otherSize);
for (int i = 0; i < otherSize; i++) {
appendBit(other.get(i));
}
}
public void xor(BitArray other) {
if (bits.length != other.bits.length) {
throw new IllegalArgumentException("Sizes don't match");
}
for (int i = 0; i < bits.length; i++) {
// The last byte could be incomplete (i.e. not have 8 bits in
// it) but there is no problem since 0 XOR 0 == 0.
bits[i] ^= other.bits[i];
}
}
/**
*
* @param bitOffset first bit to start writing
* @param array array to write into. Bytes are written most-significant byte first. This is the opposite
* of the internal representation, which is exposed by {@link #getBitArray()}
* @param offset position in array to start writing
* @param numBytes how many bytes to write
*/
public void toBytes(int bitOffset, byte[] array, int offset, int numBytes) {
for (int i = 0; i < numBytes; i++) {
int theByte = 0;
for (int j = 0; j < 8; j++) {
if (get(bitOffset)) {
theByte |= 1 << (7 - j);
}
bitOffset++;
}
array[offset + i] = (byte) theByte;
}
}
/**
* @return underlying array of ints. The first element holds the first 32 bits, and the least
* significant bit is bit 0.
*/
public int[] getBitArray() {
return bits;
}
/**
* Reverses all bits in the array.
*/
public void reverse() {
int[] newBits = new int[bits.length];
int size = this.size;
for (int i = 0; i < size; i++) {
if (get(size - i - 1)) {
newBits[i >> 5] |= 1 << (i & 0x1F);
}
}
bits = newBits;
}
private static int[] makeArray(int size) {
return new int[(size + 31) >> 5];
}
public String toString() {
StringBuffer result = new StringBuffer(size);
for (int i = 0; i < size; i++) {
if ((i & 0x07) == 0) {
result.append(' ');
}
result.append(get(i) ? 'X' : '.');
}
return result.toString();
}
}

View File

@ -0,0 +1,226 @@
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.common;
/**
* <p>Represents a 2D matrix of bits. In function arguments below, and throughout the common
* module, x is the column position, and y is the row position. The ordering is always x, y.
* The origin is at the top-left.</p>
*
* <p>Internally the bits are represented in a 1-D array of 32-bit ints. However, each row begins
* with a new int. This is done intentionally so that we can copy out a row into a BitArray very
* efficiently.</p>
*
* <p>The ordering of bits is row-major. Within each int, the least significant bits are used first,
* meaning they represent lower x values. This is compatible with BitArray's implementation.</p>
*
* @author Sean Owen
* @author dswitkin@google.com (Daniel Switkin)
*/
public final class BitMatrix {
// TODO: Just like BitArray, these need to be public so ProGuard can inline them.
public final int width;
public final int height;
public final int rowSize;
public final int[] bits;
// A helper to construct a square matrix.
public BitMatrix(int dimension) {
this(dimension, dimension);
}
public BitMatrix(int width, int height) {
if (width < 1 || height < 1) {
throw new IllegalArgumentException("Both dimensions must be greater than 0");
}
this.width = width;
this.height = height;
this.rowSize = (width + 31) >> 5;
bits = new int[rowSize * height];
}
/**
* <p>Gets the requested bit, where true means black.</p>
*
* @param x The horizontal component (i.e. which column)
* @param y The vertical component (i.e. which row)
* @return value of given bit in matrix
*/
public boolean get(int x, int y) {
int offset = y * rowSize + (x >> 5);
return ((bits[offset] >>> (x & 0x1f)) & 1) != 0;
}
/**
* <p>Sets the given bit to true.</p>
*
* @param x The horizontal component (i.e. which column)
* @param y The vertical component (i.e. which row)
*/
public void set(int x, int y) {
int offset = y * rowSize + (x >> 5);
bits[offset] |= 1 << (x & 0x1f);
}
/**
* <p>Flips the given bit.</p>
*
* @param x The horizontal component (i.e. which column)
* @param y The vertical component (i.e. which row)
*/
public void flip(int x, int y) {
int offset = y * rowSize + (x >> 5);
bits[offset] ^= 1 << (x & 0x1f);
}
/**
* Clears all bits (sets to false).
*/
public void clear() {
int max = bits.length;
for (int i = 0; i < max; i++) {
bits[i] = 0;
}
}
/**
* <p>Sets a square region of the bit matrix to true.</p>
*
* @param left The horizontal position to begin at (inclusive)
* @param top The vertical position to begin at (inclusive)
* @param width The width of the region
* @param height The height of the region
*/
public void setRegion(int left, int top, int width, int height) {
if (top < 0 || left < 0) {
throw new IllegalArgumentException("Left and top must be nonnegative");
}
if (height < 1 || width < 1) {
throw new IllegalArgumentException("Height and width must be at least 1");
}
int right = left + width;
int bottom = top + height;
if (bottom > this.height || right > this.width) {
throw new IllegalArgumentException("The region must fit inside the matrix");
}
for (int y = top; y < bottom; y++) {
int offset = y * rowSize;
for (int x = left; x < right; x++) {
bits[offset + (x >> 5)] |= 1 << (x & 0x1f);
}
}
}
/**
* A fast method to retrieve one row of data from the matrix as a BitArray.
*
* @param y The row to retrieve
* @param row An optional caller-allocated BitArray, will be allocated if null or too small
* @return The resulting BitArray - this reference should always be used even when passing
* your own row
*/
public BitArray getRow(int y, BitArray row) {
if (row == null || row.getSize() < width) {
row = new BitArray(width);
}
int offset = y * rowSize;
for (int x = 0; x < rowSize; x++) {
row.setBulk(x << 5, bits[offset + x]);
}
return row;
}
/**
* This is useful in detecting a corner of a 'pure' barcode.
*
* @return {x,y} coordinate of top-left-most 1 bit, or null if it is all white
*/
public int[] getTopLeftOnBit() {
int bitsOffset = 0;
while (bitsOffset < bits.length && bits[bitsOffset] == 0) {
bitsOffset++;
}
if (bitsOffset == bits.length) {
return null;
}
int y = bitsOffset / rowSize;
int x = (bitsOffset % rowSize) << 5;
int theBits = bits[bitsOffset];
int bit = 0;
while ((theBits << (31-bit)) == 0) {
bit++;
}
x += bit;
return new int[] {x, y};
}
/**
* @return The width of the matrix
*/
public int getWidth() {
return width;
}
/**
* @return The height of the matrix
*/
public int getHeight() {
return height;
}
public boolean equals(Object o) {
if (!(o instanceof BitMatrix)) {
return false;
}
BitMatrix other = (BitMatrix) o;
if (width != other.width || height != other.height ||
rowSize != other.rowSize || bits.length != other.bits.length) {
return false;
}
for (int i = 0; i < bits.length; i++) {
if (bits[i] != other.bits[i]) {
return false;
}
}
return true;
}
public int hashCode() {
int hash = width;
hash = 31 * hash + width;
hash = 31 * hash + height;
hash = 31 * hash + rowSize;
for (int i = 0; i < bits.length; i++) {
hash = 31 * hash + bits[i];
}
return hash;
}
public String toString() {
StringBuffer result = new StringBuffer(height * (width + 1));
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
result.append(get(x, y) ? "X " : " ");
}
result.append('\n');
}
return result.toString();
}
}

View File

@ -0,0 +1,97 @@
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.common;
/**
* <p>This provides an easy abstraction to read bits at a time from a sequence of bytes, where the
* number of bits read is not often a multiple of 8.</p>
*
* <p>This class is thread-safe but not reentrant. Unless the caller modifies the bytes array
* it passed in, in which case all bets are off.</p>
*
* @author Sean Owen
*/
public final class BitSource {
private final byte[] bytes;
private int byteOffset;
private int bitOffset;
/**
* @param bytes bytes from which this will read bits. Bits will be read from the first byte first.
* Bits are read within a byte from most-significant to least-significant bit.
*/
public BitSource(byte[] bytes) {
this.bytes = bytes;
}
/**
* @param numBits number of bits to read
* @return int representing the bits read. The bits will appear as the least-significant
* bits of the int
* @throws IllegalArgumentException if numBits isn't in [1,32]
*/
public int readBits(int numBits) {
if (numBits < 1 || numBits > 32) {
throw new IllegalArgumentException();
}
int result = 0;
// First, read remainder from current byte
if (bitOffset > 0) {
int bitsLeft = 8 - bitOffset;
int toRead = numBits < bitsLeft ? numBits : bitsLeft;
int bitsToNotRead = bitsLeft - toRead;
int mask = (0xFF >> (8 - toRead)) << bitsToNotRead;
result = (bytes[byteOffset] & mask) >> bitsToNotRead;
numBits -= toRead;
bitOffset += toRead;
if (bitOffset == 8) {
bitOffset = 0;
byteOffset++;
}
}
// Next read whole bytes
if (numBits > 0) {
while (numBits >= 8) {
result = (result << 8) | (bytes[byteOffset] & 0xFF);
byteOffset++;
numBits -= 8;
}
// Finally read a partial byte
if (numBits > 0) {
int bitsToNotRead = 8 - numBits;
int mask = (0xFF >> bitsToNotRead) << bitsToNotRead;
result = (result << numBits) | ((bytes[byteOffset] & mask) >> bitsToNotRead);
bitOffset += numBits;
}
}
return result;
}
/**
* @return number of bits that can be read successfully
*/
public int available() {
return 8 * (bytes.length - byteOffset) - bitOffset;
}
}

View File

@ -0,0 +1,110 @@
/*
* Copyright 2008 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.common;
import java.util.Hashtable;
/**
* Encapsulates a Character Set ECI, according to "Extended Channel Interpretations" 5.3.1.1
* of ISO 18004.
*
* @author Sean Owen
*/
public final class CharacterSetECI extends ECI {
private static Hashtable VALUE_TO_ECI;
private static Hashtable NAME_TO_ECI;
private static void initialize() {
VALUE_TO_ECI = new Hashtable(29);
NAME_TO_ECI = new Hashtable(29);
// TODO figure out if these values are even right!
addCharacterSet(0, "Cp437");
addCharacterSet(1, new String[] {"ISO8859_1", "ISO-8859-1"});
addCharacterSet(2, "Cp437");
addCharacterSet(3, new String[] {"ISO8859_1", "ISO-8859-1"});
addCharacterSet(4, "ISO8859_2");
addCharacterSet(5, "ISO8859_3");
addCharacterSet(6, "ISO8859_4");
addCharacterSet(7, "ISO8859_5");
addCharacterSet(8, "ISO8859_6");
addCharacterSet(9, "ISO8859_7");
addCharacterSet(10, "ISO8859_8");
addCharacterSet(11, "ISO8859_9");
addCharacterSet(12, "ISO8859_10");
addCharacterSet(13, "ISO8859_11");
addCharacterSet(15, "ISO8859_13");
addCharacterSet(16, "ISO8859_14");
addCharacterSet(17, "ISO8859_15");
addCharacterSet(18, "ISO8859_16");
addCharacterSet(20, new String[] {"SJIS", "Shift_JIS"});
}
private final String encodingName;
private CharacterSetECI(int value, String encodingName) {
super(value);
this.encodingName = encodingName;
}
public String getEncodingName() {
return encodingName;
}
private static void addCharacterSet(int value, String encodingName) {
CharacterSetECI eci = new CharacterSetECI(value, encodingName);
VALUE_TO_ECI.put(new Integer(value), eci); // can't use valueOf
NAME_TO_ECI.put(encodingName, eci);
}
private static void addCharacterSet(int value, String[] encodingNames) {
CharacterSetECI eci = new CharacterSetECI(value, encodingNames[0]);
VALUE_TO_ECI.put(new Integer(value), eci); // can't use valueOf
for (int i = 0; i < encodingNames.length; i++) {
NAME_TO_ECI.put(encodingNames[i], eci);
}
}
/**
* @param value character set ECI value
* @return {@link CharacterSetECI} representing ECI of given value, or null if it is legal but
* unsupported
* @throws IllegalArgumentException if ECI value is invalid
*/
public static CharacterSetECI getCharacterSetECIByValue(int value) {
if (VALUE_TO_ECI == null) {
initialize();
}
if (value < 0 || value >= 900) {
throw new IllegalArgumentException("Bad ECI value: " + value);
}
return (CharacterSetECI) VALUE_TO_ECI.get(new Integer(value));
}
/**
* @param name character set ECI encoding name
* @return {@link CharacterSetECI} representing ECI for character encoding, or null if it is legal
* but unsupported
*/
public static CharacterSetECI getCharacterSetECIByName(String name) {
if (NAME_TO_ECI == null) {
initialize();
}
return (CharacterSetECI) NAME_TO_ECI.get(name);
}
}

View File

@ -0,0 +1,53 @@
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.common;
import java.util.Vector;
/**
* <p>This is basically a substitute for <code>java.util.Collections</code>, which is not
* present in MIDP 2.0 / CLDC 1.1.</p>
*
* @author Sean Owen
*/
public final class Collections {
private Collections() {
}
/**
* Sorts its argument (destructively) using insert sort; in the context of this package
* insertion sort is simple and efficient given its relatively small inputs.
*
* @param vector vector to sort
* @param comparator comparator to define sort ordering
*/
public static void insertionSort(Vector vector, Comparator comparator) {
int max = vector.size();
for (int i = 1; i < max; i++) {
Object value = vector.elementAt(i);
int j = i - 1;
Object valueB;
while (j >= 0 && comparator.compare((valueB = vector.elementAt(j)), value) > 0) {
vector.setElementAt(valueB, j + 1);
j--;
}
vector.setElementAt(value, j + 1);
}
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.common;
/**
* This is merely a clone of <code>Comparator</code> since it is not available in
* CLDC 1.1 / MIDP 2.0.
*/
public interface Comparator {
int compare(Object o1, Object o2);
}

View File

@ -0,0 +1,63 @@
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.common;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import java.util.Vector;
/**
* <p>Encapsulates the result of decoding a matrix of bits. This typically
* applies to 2D barcode formats. For now it contains the raw bytes obtained,
* as well as a String interpretation of those bytes, if applicable.</p>
*
* @author Sean Owen
*/
public final class DecoderResult {
private final byte[] rawBytes;
private final String text;
private final Vector byteSegments;
private final ErrorCorrectionLevel ecLevel;
public DecoderResult(byte[] rawBytes, String text, Vector byteSegments, ErrorCorrectionLevel ecLevel) {
if (rawBytes == null && text == null) {
throw new IllegalArgumentException();
}
this.rawBytes = rawBytes;
this.text = text;
this.byteSegments = byteSegments;
this.ecLevel = ecLevel;
}
public byte[] getRawBytes() {
return rawBytes;
}
public String getText() {
return text;
}
public Vector getByteSegments() {
return byteSegments;
}
public ErrorCorrectionLevel getECLevel() {
return ecLevel;
}
}

View File

@ -0,0 +1,81 @@
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.common;
import com.google.zxing.NotFoundException;
/**
* @author Sean Owen
*/
public final class DefaultGridSampler extends GridSampler {
public BitMatrix sampleGrid(BitMatrix image,
int dimension,
float p1ToX, float p1ToY,
float p2ToX, float p2ToY,
float p3ToX, float p3ToY,
float p4ToX, float p4ToY,
float p1FromX, float p1FromY,
float p2FromX, float p2FromY,
float p3FromX, float p3FromY,
float p4FromX, float p4FromY) throws NotFoundException {
PerspectiveTransform transform = PerspectiveTransform.quadrilateralToQuadrilateral(
p1ToX, p1ToY, p2ToX, p2ToY, p3ToX, p3ToY, p4ToX, p4ToY,
p1FromX, p1FromY, p2FromX, p2FromY, p3FromX, p3FromY, p4FromX, p4FromY);
return sampleGrid(image, dimension, transform);
}
public BitMatrix sampleGrid(BitMatrix image,
int dimension,
PerspectiveTransform transform) throws NotFoundException {
BitMatrix bits = new BitMatrix(dimension);
float[] points = new float[dimension << 1];
for (int y = 0; y < dimension; y++) {
int max = points.length;
float iValue = (float) y + 0.5f;
for (int x = 0; x < max; x += 2) {
points[x] = (float) (x >> 1) + 0.5f;
points[x + 1] = iValue;
}
transform.transformPoints(points);
// Quick check to see if points transformed to something inside the image;
// sufficient to check the endpoints
checkAndNudgePoints(image, points);
try {
for (int x = 0; x < max; x += 2) {
if (image.get((int) points[x], (int) points[x + 1])) {
// Black(-ish) pixel
bits.set(x >> 1, y);
}
}
} catch (ArrayIndexOutOfBoundsException aioobe) {
// This feels wrong, but, sometimes if the finder patterns are misidentified, the resulting
// transform gets "twisted" such that it maps a straight line of points to a set of points
// whose endpoints are in bounds, but others are not. There is probably some mathematical
// way to detect this about the transformation that I don't know yet.
// This results in an ugly runtime exception despite our clever checks above -- can't have
// that. We could check each point's coordinates but that feels duplicative. We settle for
// catching and wrapping ArrayIndexOutOfBoundsException.
throw NotFoundException.getNotFoundInstance();
}
}
return bits;
}
}

View File

@ -0,0 +1,46 @@
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.common;
import com.google.zxing.ResultPoint;
/**
* <p>Encapsulates the result of detecting a barcode in an image. This includes the raw
* matrix of black/white pixels corresponding to the barcode, and possibly points of interest
* in the image, like the location of finder patterns or corners of the barcode in the image.</p>
*
* @author Sean Owen
*/
public final class DetectorResult {
private final BitMatrix bits;
private final ResultPoint[] points;
public DetectorResult(BitMatrix bits, ResultPoint[] points) {
this.bits = bits;
this.points = points;
}
public BitMatrix getBits() {
return bits;
}
public ResultPoint[] getPoints() {
return points;
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright 2008 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.common;
/**
* Superclass of classes encapsulating types ECIs, according to "Extended Channel Interpretations"
* 5.3 of ISO 18004.
*
* @author Sean Owen
*/
public abstract class ECI {
private final int value;
ECI(int value) {
this.value = value;
}
public int getValue() {
return value;
}
/**
* @param value ECI value
* @return {@link ECI} representing ECI of given value, or null if it is legal but unsupported
* @throws IllegalArgumentException if ECI value is invalid
*/
public static ECI getECIByValue(int value) {
if (value < 0 || value > 999999) {
throw new IllegalArgumentException("Bad ECI value: " + value);
}
if (value < 900) { // Character set ECIs use 000000 - 000899
return CharacterSetECI.getCharacterSetECIByValue(value);
}
return null;
}
}

View File

@ -0,0 +1,196 @@
/*
* Copyright 2009 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.common;
import com.google.zxing.Binarizer;
import com.google.zxing.LuminanceSource;
import com.google.zxing.NotFoundException;
/**
* This Binarizer implementation uses the old ZXing global histogram approach. It is suitable
* for low-end mobile devices which don't have enough CPU or memory to use a local thresholding
* algorithm. However, because it picks a global black point, it cannot handle difficult shadows
* and gradients.
*
* Faster mobile devices and all desktop applications should probably use HybridBinarizer instead.
*
* @author dswitkin@google.com (Daniel Switkin)
* @author Sean Owen
*/
public class GlobalHistogramBinarizer extends Binarizer {
private static final int LUMINANCE_BITS = 5;
private static final int LUMINANCE_SHIFT = 8 - LUMINANCE_BITS;
private static final int LUMINANCE_BUCKETS = 1 << LUMINANCE_BITS;
private byte[] luminances = null;
private int[] buckets = null;
public GlobalHistogramBinarizer(LuminanceSource source) {
super(source);
}
// Applies simple sharpening to the row data to improve performance of the 1D Readers.
public BitArray getBlackRow(int y, BitArray row) throws NotFoundException {
LuminanceSource source = getLuminanceSource();
int width = source.getWidth();
if (row == null || row.getSize() < width) {
row = new BitArray(width);
} else {
row.clear();
}
initArrays(width);
byte[] localLuminances = source.getRow(y, luminances);
int[] localBuckets = buckets;
for (int x = 0; x < width; x++) {
int pixel = localLuminances[x] & 0xff;
localBuckets[pixel >> LUMINANCE_SHIFT]++;
}
int blackPoint = estimateBlackPoint(localBuckets);
int left = localLuminances[0] & 0xff;
int center = localLuminances[1] & 0xff;
for (int x = 1; x < width - 1; x++) {
int right = localLuminances[x + 1] & 0xff;
// A simple -1 4 -1 box filter with a weight of 2.
int luminance = ((center << 2) - left - right) >> 1;
if (luminance < blackPoint) {
row.set(x);
}
left = center;
center = right;
}
return row;
}
// Does not sharpen the data, as this call is intended to only be used by 2D Readers.
public BitMatrix getBlackMatrix() throws NotFoundException {
LuminanceSource source = getLuminanceSource();
int width = source.getWidth();
int height = source.getHeight();
BitMatrix matrix = new BitMatrix(width, height);
// Quickly calculates the histogram by sampling four rows from the image. This proved to be
// more robust on the blackbox tests than sampling a diagonal as we used to do.
initArrays(width);
int[] localBuckets = buckets;
for (int y = 1; y < 5; y++) {
int row = height * y / 5;
byte[] localLuminances = source.getRow(row, luminances);
int right = (width << 2) / 5;
for (int x = width / 5; x < right; x++) {
int pixel = localLuminances[x] & 0xff;
localBuckets[pixel >> LUMINANCE_SHIFT]++;
}
}
int blackPoint = estimateBlackPoint(localBuckets);
// We delay reading the entire image luminance until the black point estimation succeeds.
// Although we end up reading four rows twice, it is consistent with our motto of
// "fail quickly" which is necessary for continuous scanning.
byte[] localLuminances = source.getMatrix();
for (int y = 0; y < height; y++) {
int offset = y * width;
for (int x = 0; x< width; x++) {
int pixel = localLuminances[offset + x] & 0xff;
if (pixel < blackPoint) {
matrix.set(x, y);
}
}
}
return matrix;
}
public Binarizer createBinarizer(LuminanceSource source) {
return new GlobalHistogramBinarizer(source);
}
private void initArrays(int luminanceSize) {
if (luminances == null || luminances.length < luminanceSize) {
luminances = new byte[luminanceSize];
}
if (buckets == null) {
buckets = new int[LUMINANCE_BUCKETS];
} else {
for (int x = 0; x < LUMINANCE_BUCKETS; x++) {
buckets[x] = 0;
}
}
}
private static int estimateBlackPoint(int[] buckets) throws NotFoundException {
// Find the tallest peak in the histogram.
int numBuckets = buckets.length;
int maxBucketCount = 0;
int firstPeak = 0;
int firstPeakSize = 0;
for (int x = 0; x < numBuckets; x++) {
if (buckets[x] > firstPeakSize) {
firstPeak = x;
firstPeakSize = buckets[x];
}
if (buckets[x] > maxBucketCount) {
maxBucketCount = buckets[x];
}
}
// Find the second-tallest peak which is somewhat far from the tallest peak.
int secondPeak = 0;
int secondPeakScore = 0;
for (int x = 0; x < numBuckets; x++) {
int distanceToBiggest = x - firstPeak;
// Encourage more distant second peaks by multiplying by square of distance.
int score = buckets[x] * distanceToBiggest * distanceToBiggest;
if (score > secondPeakScore) {
secondPeak = x;
secondPeakScore = score;
}
}
// Make sure firstPeak corresponds to the black peak.
if (firstPeak > secondPeak) {
int temp = firstPeak;
firstPeak = secondPeak;
secondPeak = temp;
}
// If there is too little contrast in the image to pick a meaningful black point, throw rather
// than waste time trying to decode the image, and risk false positives.
// TODO: It might be worth comparing the brightest and darkest pixels seen, rather than the
// two peaks, to determine the contrast.
if (secondPeak - firstPeak <= numBuckets >> 4) {
throw NotFoundException.getNotFoundInstance();
}
// Find a valley between them that is low and closer to the white peak.
int bestValley = secondPeak - 1;
int bestValleyScore = -1;
for (int x = secondPeak - 1; x > firstPeak; x--) {
int fromFirst = x - firstPeak;
int score = fromFirst * fromFirst * (secondPeak - x) * (maxBucketCount - buckets[x]);
if (score > bestValleyScore) {
bestValley = x;
bestValleyScore = score;
}
}
return bestValley << LUMINANCE_SHIFT;
}
}

View File

@ -0,0 +1,171 @@
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.common;
import com.google.zxing.NotFoundException;
/**
* Implementations of this class can, given locations of finder patterns for a QR code in an
* image, sample the right points in the image to reconstruct the QR code, accounting for
* perspective distortion. It is abstracted since it is relatively expensive and should be allowed
* to take advantage of platform-specific optimized implementations, like Sun's Java Advanced
* Imaging library, but which may not be available in other environments such as J2ME, and vice
* versa.
*
* The implementation used can be controlled by calling {@link #setGridSampler(GridSampler)}
* with an instance of a class which implements this interface.
*
* @author Sean Owen
*/
public abstract class GridSampler {
private static GridSampler gridSampler = new DefaultGridSampler();
/**
* Sets the implementation of {@link GridSampler} used by the library. One global
* instance is stored, which may sound problematic. But, the implementation provided
* ought to be appropriate for the entire platform, and all uses of this library
* in the whole lifetime of the JVM. For instance, an Android activity can swap in
* an implementation that takes advantage of native platform libraries.
*
* @param newGridSampler The platform-specific object to install.
*/
public static void setGridSampler(GridSampler newGridSampler) {
if (newGridSampler == null) {
throw new IllegalArgumentException();
}
gridSampler = newGridSampler;
}
/**
* @return the current implementation of {@link GridSampler}
*/
public static GridSampler getInstance() {
return gridSampler;
}
/**
* <p>Samples an image for a square matrix of bits of the given dimension. This is used to extract
* the black/white modules of a 2D barcode like a QR Code found in an image. Because this barcode
* may be rotated or perspective-distorted, the caller supplies four points in the source image
* that define known points in the barcode, so that the image may be sampled appropriately.</p>
*
* <p>The last eight "from" parameters are four X/Y coordinate pairs of locations of points in
* the image that define some significant points in the image to be sample. For example,
* these may be the location of finder pattern in a QR Code.</p>
*
* <p>The first eight "to" parameters are four X/Y coordinate pairs measured in the destination
* {@link BitMatrix}, from the top left, where the known points in the image given by the "from"
* parameters map to.</p>
*
* <p>These 16 parameters define the transformation needed to sample the image.</p>
*
* @param image image to sample
* @param dimension width/height of {@link BitMatrix} to sample from image
* @return {@link BitMatrix} representing a grid of points sampled from the image within a region
* defined by the "from" parameters
* @throws NotFoundException if image can't be sampled, for example, if the transformation defined
* by the given points is invalid or results in sampling outside the image boundaries
*/
public abstract BitMatrix sampleGrid(BitMatrix image,
int dimension,
float p1ToX, float p1ToY,
float p2ToX, float p2ToY,
float p3ToX, float p3ToY,
float p4ToX, float p4ToY,
float p1FromX, float p1FromY,
float p2FromX, float p2FromY,
float p3FromX, float p3FromY,
float p4FromX, float p4FromY) throws NotFoundException;
public BitMatrix sampleGrid(BitMatrix image,
int dimension,
PerspectiveTransform transform) throws NotFoundException {
throw new IllegalStateException(); // Can't use UnsupportedOperationException
}
/**
* <p>Checks a set of points that have been transformed to sample points on an image against
* the image's dimensions to see if the point are even within the image.</p>
*
* <p>This method will actually "nudge" the endpoints back onto the image if they are found to be
* barely (less than 1 pixel) off the image. This accounts for imperfect detection of finder
* patterns in an image where the QR Code runs all the way to the image border.</p>
*
* <p>For efficiency, the method will check points from either end of the line until one is found
* to be within the image. Because the set of points are assumed to be linear, this is valid.</p>
*
* @param image image into which the points should map
* @param points actual points in x1,y1,...,xn,yn form
* @throws NotFoundException if an endpoint is lies outside the image boundaries
*/
protected static void checkAndNudgePoints(BitMatrix image, float[] points)
throws NotFoundException {
int width = image.getWidth();
int height = image.getHeight();
// Check and nudge points from start until we see some that are OK:
boolean nudged = true;
for (int offset = 0; offset < points.length && nudged; offset += 2) {
int x = (int) points[offset];
int y = (int) points[offset + 1];
if (x < -1 || x > width || y < -1 || y > height) {
throw NotFoundException.getNotFoundInstance();
}
nudged = false;
if (x == -1) {
points[offset] = 0.0f;
nudged = true;
} else if (x == width) {
points[offset] = width - 1;
nudged = true;
}
if (y == -1) {
points[offset + 1] = 0.0f;
nudged = true;
} else if (y == height) {
points[offset + 1] = height - 1;
nudged = true;
}
}
// Check and nudge points from end:
nudged = true;
for (int offset = points.length - 2; offset >= 0 && nudged; offset -= 2) {
int x = (int) points[offset];
int y = (int) points[offset + 1];
if (x < -1 || x > width || y < -1 || y > height) {
throw NotFoundException.getNotFoundInstance();
}
nudged = false;
if (x == -1) {
points[offset] = 0.0f;
nudged = true;
} else if (x == width) {
points[offset] = width - 1;
nudged = true;
}
if (y == -1) {
points[offset + 1] = 0.0f;
nudged = true;
} else if (y == height) {
points[offset + 1] = height - 1;
nudged = true;
}
}
}
}

View File

@ -0,0 +1,185 @@
/*
* Copyright 2009 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.common;
import com.google.zxing.Binarizer;
import com.google.zxing.LuminanceSource;
import com.google.zxing.NotFoundException;
/**
* This class implements a local thresholding algorithm, which while slower than the
* GlobalHistogramBinarizer, is fairly efficient for what it does. It is designed for
* high frequency images of barcodes with black data on white backgrounds. For this application,
* it does a much better job than a global blackpoint with severe shadows and gradients.
* However it tends to produce artifacts on lower frequency images and is therefore not
* a good general purpose binarizer for uses outside ZXing.
*
* This class extends GlobalHistogramBinarizer, using the older histogram approach for 1D readers,
* and the newer local approach for 2D readers. 1D decoding using a per-row histogram is already
* inherently local, and only fails for horizontal gradients. We can revisit that problem later,
* but for now it was not a win to use local blocks for 1D.
*
* This Binarizer is the default for the unit tests and the recommended class for library users.
*
* @author dswitkin@google.com (Daniel Switkin)
*/
public final class HybridBinarizer extends GlobalHistogramBinarizer {
// This class uses 5x5 blocks to compute local luminance, where each block is 8x8 pixels.
// So this is the smallest dimension in each axis we can accept.
private static final int MINIMUM_DIMENSION = 40;
private BitMatrix matrix = null;
public HybridBinarizer(LuminanceSource source) {
super(source);
}
public BitMatrix getBlackMatrix() throws NotFoundException {
binarizeEntireImage();
return matrix;
}
public Binarizer createBinarizer(LuminanceSource source) {
return new HybridBinarizer(source);
}
// Calculates the final BitMatrix once for all requests. This could be called once from the
// constructor instead, but there are some advantages to doing it lazily, such as making
// profiling easier, and not doing heavy lifting when callers don't expect it.
private void binarizeEntireImage() throws NotFoundException {
if (matrix == null) {
LuminanceSource source = getLuminanceSource();
if (source.getWidth() >= MINIMUM_DIMENSION && source.getHeight() >= MINIMUM_DIMENSION) {
byte[] luminances = source.getMatrix();
int width = source.getWidth();
int height = source.getHeight();
int subWidth = width >> 3;
if ((width & 0x07) != 0) {
subWidth++;
}
int subHeight = height >> 3;
if ((height & 0x07) != 0) {
subHeight++;
}
int[][] blackPoints = calculateBlackPoints(luminances, subWidth, subHeight, width, height);
matrix = new BitMatrix(width, height);
calculateThresholdForBlock(luminances, subWidth, subHeight, width, height, blackPoints, matrix);
} else {
// If the image is too small, fall back to the global histogram approach.
matrix = super.getBlackMatrix();
}
}
}
// For each 8x8 block in the image, calculate the average black point using a 5x5 grid
// of the blocks around it. Also handles the corner cases (fractional blocks are computed based
// on the last 8 pixels in the row/column which are also used in the previous block).
private static void calculateThresholdForBlock(byte[] luminances, int subWidth, int subHeight,
int width, int height, int[][] blackPoints, BitMatrix matrix) {
for (int y = 0; y < subHeight; y++) {
int yoffset = y << 3;
if ((yoffset + 8) >= height) {
yoffset = height - 8;
}
for (int x = 0; x < subWidth; x++) {
int xoffset = x << 3;
if ((xoffset + 8) >= width) {
xoffset = width - 8;
}
int left = (x > 1) ? x : 2;
left = (left < subWidth - 2) ? left : subWidth - 3;
int top = (y > 1) ? y : 2;
top = (top < subHeight - 2) ? top : subHeight - 3;
int sum = 0;
for (int z = -2; z <= 2; z++) {
int[] blackRow = blackPoints[top + z];
sum += blackRow[left - 2];
sum += blackRow[left - 1];
sum += blackRow[left];
sum += blackRow[left + 1];
sum += blackRow[left + 2];
}
int average = sum / 25;
threshold8x8Block(luminances, xoffset, yoffset, average, width, matrix);
}
}
}
// Applies a single threshold to an 8x8 block of pixels.
private static void threshold8x8Block(byte[] luminances, int xoffset, int yoffset, int threshold,
int stride, BitMatrix matrix) {
for (int y = 0; y < 8; y++) {
int offset = (yoffset + y) * stride + xoffset;
for (int x = 0; x < 8; x++) {
int pixel = luminances[offset + x] & 0xff;
if (pixel < threshold) {
matrix.set(xoffset + x, yoffset + y);
}
}
}
}
// Calculates a single black point for each 8x8 block of pixels and saves it away.
private static int[][] calculateBlackPoints(byte[] luminances, int subWidth, int subHeight,
int width, int height) {
int[][] blackPoints = new int[subHeight][subWidth];
for (int y = 0; y < subHeight; y++) {
int yoffset = y << 3;
if ((yoffset + 8) >= height) {
yoffset = height - 8;
}
for (int x = 0; x < subWidth; x++) {
int xoffset = x << 3;
if ((xoffset + 8) >= width) {
xoffset = width - 8;
}
int sum = 0;
int min = 255;
int max = 0;
for (int yy = 0; yy < 8; yy++) {
int offset = (yoffset + yy) * width + xoffset;
for (int xx = 0; xx < 8; xx++) {
int pixel = luminances[offset + xx] & 0xff;
sum += pixel;
if (pixel < min) {
min = pixel;
}
if (pixel > max) {
max = pixel;
}
}
}
// If the contrast is inadequate, use half the minimum, so that this block will be
// treated as part of the white background, but won't drag down neighboring blocks
// too much.
int average;
if (max - min > 24) {
average = sum >> 6;
} else {
// When min == max == 0, let average be 1 so all is black
average = max == 0 ? 1 : min >> 1;
}
blackPoints[y][x] = average;
}
}
return blackPoints;
}
}

View File

@ -0,0 +1,148 @@
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.common;
/**
* <p>This class implements a perspective transform in two dimensions. Given four source and four
* destination points, it will compute the transformation implied between them. The code is based
* directly upon section 3.4.2 of George Wolberg's "Digital Image Warping"; see pages 54-56.</p>
*
* @author Sean Owen
*/
public final class PerspectiveTransform {
private final float a11, a12, a13, a21, a22, a23, a31, a32, a33;
private PerspectiveTransform(float a11, float a21, float a31,
float a12, float a22, float a32,
float a13, float a23, float a33) {
this.a11 = a11;
this.a12 = a12;
this.a13 = a13;
this.a21 = a21;
this.a22 = a22;
this.a23 = a23;
this.a31 = a31;
this.a32 = a32;
this.a33 = a33;
}
public static PerspectiveTransform quadrilateralToQuadrilateral(float x0, float y0,
float x1, float y1,
float x2, float y2,
float x3, float y3,
float x0p, float y0p,
float x1p, float y1p,
float x2p, float y2p,
float x3p, float y3p) {
PerspectiveTransform qToS = quadrilateralToSquare(x0, y0, x1, y1, x2, y2, x3, y3);
PerspectiveTransform sToQ = squareToQuadrilateral(x0p, y0p, x1p, y1p, x2p, y2p, x3p, y3p);
return sToQ.times(qToS);
}
public void transformPoints(float[] points) {
int max = points.length;
float a11 = this.a11;
float a12 = this.a12;
float a13 = this.a13;
float a21 = this.a21;
float a22 = this.a22;
float a23 = this.a23;
float a31 = this.a31;
float a32 = this.a32;
float a33 = this.a33;
for (int i = 0; i < max; i += 2) {
float x = points[i];
float y = points[i + 1];
float denominator = a13 * x + a23 * y + a33;
points[i] = (a11 * x + a21 * y + a31) / denominator;
points[i + 1] = (a12 * x + a22 * y + a32) / denominator;
}
}
/** Convenience method, not optimized for performance. */
public void transformPoints(float[] xValues, float[] yValues) {
int n = xValues.length;
for (int i = 0; i < n; i ++) {
float x = xValues[i];
float y = yValues[i];
float denominator = a13 * x + a23 * y + a33;
xValues[i] = (a11 * x + a21 * y + a31) / denominator;
yValues[i] = (a12 * x + a22 * y + a32) / denominator;
}
}
public static PerspectiveTransform squareToQuadrilateral(float x0, float y0,
float x1, float y1,
float x2, float y2,
float x3, float y3) {
float dy2 = y3 - y2;
float dy3 = y0 - y1 + y2 - y3;
if (dy2 == 0.0f && dy3 == 0.0f) {
return new PerspectiveTransform(x1 - x0, x2 - x1, x0,
y1 - y0, y2 - y1, y0,
0.0f, 0.0f, 1.0f);
} else {
float dx1 = x1 - x2;
float dx2 = x3 - x2;
float dx3 = x0 - x1 + x2 - x3;
float dy1 = y1 - y2;
float denominator = dx1 * dy2 - dx2 * dy1;
float a13 = (dx3 * dy2 - dx2 * dy3) / denominator;
float a23 = (dx1 * dy3 - dx3 * dy1) / denominator;
return new PerspectiveTransform(x1 - x0 + a13 * x1, x3 - x0 + a23 * x3, x0,
y1 - y0 + a13 * y1, y3 - y0 + a23 * y3, y0,
a13, a23, 1.0f);
}
}
public static PerspectiveTransform quadrilateralToSquare(float x0, float y0,
float x1, float y1,
float x2, float y2,
float x3, float y3) {
// Here, the adjoint serves as the inverse:
return squareToQuadrilateral(x0, y0, x1, y1, x2, y2, x3, y3).buildAdjoint();
}
PerspectiveTransform buildAdjoint() {
// Adjoint is the transpose of the cofactor matrix:
return new PerspectiveTransform(a22 * a33 - a23 * a32,
a23 * a31 - a21 * a33,
a21 * a32 - a22 * a31,
a13 * a32 - a12 * a33,
a11 * a33 - a13 * a31,
a12 * a31 - a11 * a32,
a12 * a23 - a13 * a22,
a13 * a21 - a11 * a23,
a11 * a22 - a12 * a21);
}
PerspectiveTransform times(PerspectiveTransform other) {
return new PerspectiveTransform(a11 * other.a11 + a21 * other.a12 + a31 * other.a13,
a11 * other.a21 + a21 * other.a22 + a31 * other.a23,
a11 * other.a31 + a21 * other.a32 + a31 * other.a33,
a12 * other.a11 + a22 * other.a12 + a32 * other.a13,
a12 * other.a21 + a22 * other.a22 + a32 * other.a23,
a12 * other.a31 + a22 * other.a32 + a32 * other.a33,
a13 * other.a11 + a23 * other.a12 + a33 * other.a13,
a13 * other.a21 + a23 * other.a22 + a33 * other.a23,
a13 * other.a31 + a23 * other.a32 + a33 * other.a33);
}
}

View File

@ -0,0 +1,191 @@
/*
* Copyright (C) 2010 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.common;
import java.util.Hashtable;
import com.google.zxing.DecodeHintType;
/**
* Common string-related functions.
*
* @author Sean Owen
*/
public final class StringUtils {
private static final String PLATFORM_DEFAULT_ENCODING =
System.getProperty("file.encoding");
public static final String SHIFT_JIS = "SJIS";
private static final String EUC_JP = "EUC_JP";
private static final String UTF8 = "UTF8";
private static final String ISO88591 = "ISO8859_1";
private static final boolean ASSUME_SHIFT_JIS =
SHIFT_JIS.equalsIgnoreCase(PLATFORM_DEFAULT_ENCODING) ||
EUC_JP.equalsIgnoreCase(PLATFORM_DEFAULT_ENCODING);
private StringUtils() {}
/**
* @param bytes bytes encoding a string, whose encoding should be guessed
* @param hints decode hints if applicable
* @return name of guessed encoding; at the moment will only guess one of:
* {@link #SHIFT_JIS}, {@link #UTF8}, {@link #ISO88591}, or the platform
* default encoding if none of these can possibly be correct
*/
public static String guessEncoding(byte[] bytes, Hashtable hints) {
if (hints != null) {
String characterSet = (String) hints.get(DecodeHintType.CHARACTER_SET);
if (characterSet != null) {
return characterSet;
}
}
// Does it start with the UTF-8 byte order mark? then guess it's UTF-8
if (bytes.length > 3 &&
bytes[0] == (byte) 0xEF &&
bytes[1] == (byte) 0xBB &&
bytes[2] == (byte) 0xBF) {
return UTF8;
}
// For now, merely tries to distinguish ISO-8859-1, UTF-8 and Shift_JIS,
// which should be by far the most common encodings. ISO-8859-1
// should not have bytes in the 0x80 - 0x9F range, while Shift_JIS
// uses this as a first byte of a two-byte character. If we see this
// followed by a valid second byte in Shift_JIS, assume it is Shift_JIS.
// If we see something else in that second byte, we'll make the risky guess
// that it's UTF-8.
int length = bytes.length;
boolean canBeISO88591 = true;
boolean canBeShiftJIS = true;
boolean canBeUTF8 = true;
int utf8BytesLeft = 0;
int maybeDoubleByteCount = 0;
int maybeSingleByteKatakanaCount = 0;
boolean sawLatin1Supplement = false;
boolean sawUTF8Start = false;
boolean lastWasPossibleDoubleByteStart = false;
for (int i = 0;
i < length && (canBeISO88591 || canBeShiftJIS || canBeUTF8);
i++) {
int value = bytes[i] & 0xFF;
// UTF-8 stuff
if (value >= 0x80 && value <= 0xBF) {
if (utf8BytesLeft > 0) {
utf8BytesLeft--;
}
} else {
if (utf8BytesLeft > 0) {
canBeUTF8 = false;
}
if (value >= 0xC0 && value <= 0xFD) {
sawUTF8Start = true;
int valueCopy = value;
while ((valueCopy & 0x40) != 0) {
utf8BytesLeft++;
valueCopy <<= 1;
}
}
}
// ISO-8859-1 stuff
if ((value == 0xC2 || value == 0xC3) && i < length - 1) {
// This is really a poor hack. The slightly more exotic characters people might want to put in
// a QR Code, by which I mean the Latin-1 supplement characters (e.g. u-umlaut) have encodings
// that start with 0xC2 followed by [0xA0,0xBF], or start with 0xC3 followed by [0x80,0xBF].
int nextValue = bytes[i + 1] & 0xFF;
if (nextValue <= 0xBF &&
((value == 0xC2 && nextValue >= 0xA0) || (value == 0xC3 && nextValue >= 0x80))) {
sawLatin1Supplement = true;
}
}
if (value >= 0x7F && value <= 0x9F) {
canBeISO88591 = false;
}
// Shift_JIS stuff
if (value >= 0xA1 && value <= 0xDF) {
// count the number of characters that might be a Shift_JIS single-byte Katakana character
if (!lastWasPossibleDoubleByteStart) {
maybeSingleByteKatakanaCount++;
}
}
if (!lastWasPossibleDoubleByteStart &&
((value >= 0xF0 && value <= 0xFF) || value == 0x80 || value == 0xA0)) {
canBeShiftJIS = false;
}
if (((value >= 0x81 && value <= 0x9F) || (value >= 0xE0 && value <= 0xEF))) {
// These start double-byte characters in Shift_JIS. Let's see if it's followed by a valid
// second byte.
if (lastWasPossibleDoubleByteStart) {
// If we just checked this and the last byte for being a valid double-byte
// char, don't check starting on this byte. If this and the last byte
// formed a valid pair, then this shouldn't be checked to see if it starts
// a double byte pair of course.
lastWasPossibleDoubleByteStart = false;
} else {
// ... otherwise do check to see if this plus the next byte form a valid
// double byte pair encoding a character.
lastWasPossibleDoubleByteStart = true;
if (i >= bytes.length - 1) {
canBeShiftJIS = false;
} else {
int nextValue = bytes[i + 1] & 0xFF;
if (nextValue < 0x40 || nextValue > 0xFC) {
canBeShiftJIS = false;
} else {
maybeDoubleByteCount++;
}
// There is some conflicting information out there about which bytes can follow which in
// double-byte Shift_JIS characters. The rule above seems to be the one that matches practice.
}
}
} else {
lastWasPossibleDoubleByteStart = false;
}
}
if (utf8BytesLeft > 0) {
canBeUTF8 = false;
}
// Easy -- if assuming Shift_JIS and no evidence it can't be, done
if (canBeShiftJIS && ASSUME_SHIFT_JIS) {
return SHIFT_JIS;
}
if (canBeUTF8 && sawUTF8Start) {
return UTF8;
}
// Distinguishing Shift_JIS and ISO-8859-1 can be a little tough. The crude heuristic is:
// - If we saw
// - at least 3 bytes that starts a double-byte value (bytes that are rare in ISO-8859-1), or
// - over 5% of bytes could be single-byte Katakana (also rare in ISO-8859-1),
// - and, saw no sequences that are invalid in Shift_JIS, then we conclude Shift_JIS
if (canBeShiftJIS && (maybeDoubleByteCount >= 3 || 20 * maybeSingleByteKatakanaCount > length)) {
return SHIFT_JIS;
}
// Otherwise, we default to ISO-8859-1 unless we know it can't be
if (!sawLatin1Supplement && canBeISO88591) {
return ISO88591;
}
// Otherwise, we take a wild guess with platform encoding
return PLATFORM_DEFAULT_ENCODING;
}
}

View File

@ -0,0 +1,209 @@
/*
* Copyright 2009 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.common.detector;
import com.google.zxing.NotFoundException;
import com.google.zxing.ResultPoint;
import com.google.zxing.common.BitMatrix;
/**
* <p>A somewhat generic detector that looks for a barcode-like rectangular region within an image.
* It looks within a mostly white region of an image for a region of black and white, but mostly
* black. It returns the four corners of the region, as best it can determine.</p>
*
* @author Sean Owen
*/
public final class MonochromeRectangleDetector {
private static final int MAX_MODULES = 32;
private final BitMatrix image;
public MonochromeRectangleDetector(BitMatrix image) {
this.image = image;
}
/**
* <p>Detects a rectangular region of black and white -- mostly black -- with a region of mostly
* white, in an image.</p>
*
* @return {@link ResultPoint}[] describing the corners of the rectangular region. The first and
* last points are opposed on the diagonal, as are the second and third. The first point will be
* the topmost point and the last, the bottommost. The second point will be leftmost and the
* third, the rightmost
* @throws NotFoundException if no Data Matrix Code can be found
*/
public ResultPoint[] detect() throws NotFoundException {
int height = image.getHeight();
int width = image.getWidth();
int halfHeight = height >> 1;
int halfWidth = width >> 1;
int deltaY = Math.max(1, height / (MAX_MODULES << 3));
int deltaX = Math.max(1, width / (MAX_MODULES << 3));
int top = 0;
int bottom = height;
int left = 0;
int right = width;
ResultPoint pointA = findCornerFromCenter(halfWidth, 0, left, right,
halfHeight, -deltaY, top, bottom, halfWidth >> 1);
top = (int) pointA.getY() - 1;
ResultPoint pointB = findCornerFromCenter(halfWidth, -deltaX, left, right,
halfHeight, 0, top, bottom, halfHeight >> 1);
left = (int) pointB.getX() - 1;
ResultPoint pointC = findCornerFromCenter(halfWidth, deltaX, left, right,
halfHeight, 0, top, bottom, halfHeight >> 1);
right = (int) pointC.getX() + 1;
ResultPoint pointD = findCornerFromCenter(halfWidth, 0, left, right,
halfHeight, deltaY, top, bottom, halfWidth >> 1);
bottom = (int) pointD.getY() + 1;
// Go try to find point A again with better information -- might have been off at first.
pointA = findCornerFromCenter(halfWidth, 0, left, right,
halfHeight, -deltaY, top, bottom, halfWidth >> 2);
return new ResultPoint[] { pointA, pointB, pointC, pointD };
}
/**
* Attempts to locate a corner of the barcode by scanning up, down, left or right from a center
* point which should be within the barcode.
*
* @param centerX center's x component (horizontal)
* @param deltaX same as deltaY but change in x per step instead
* @param left minimum value of x
* @param right maximum value of x
* @param centerY center's y component (vertical)
* @param deltaY change in y per step. If scanning up this is negative; down, positive;
* left or right, 0
* @param top minimum value of y to search through (meaningless when di == 0)
* @param bottom maximum value of y
* @param maxWhiteRun maximum run of white pixels that can still be considered to be within
* the barcode
* @return a {@link com.google.zxing.ResultPoint} encapsulating the corner that was found
* @throws NotFoundException if such a point cannot be found
*/
private ResultPoint findCornerFromCenter(int centerX, int deltaX, int left, int right,
int centerY, int deltaY, int top, int bottom, int maxWhiteRun) throws NotFoundException {
int[] lastRange = null;
for (int y = centerY, x = centerX;
y < bottom && y >= top && x < right && x >= left;
y += deltaY, x += deltaX) {
int[] range;
if (deltaX == 0) {
// horizontal slices, up and down
range = blackWhiteRange(y, maxWhiteRun, left, right, true);
} else {
// vertical slices, left and right
range = blackWhiteRange(x, maxWhiteRun, top, bottom, false);
}
if (range == null) {
if (lastRange == null) {
throw NotFoundException.getNotFoundInstance();
}
// lastRange was found
if (deltaX == 0) {
int lastY = y - deltaY;
if (lastRange[0] < centerX) {
if (lastRange[1] > centerX) {
// straddle, choose one or the other based on direction
return new ResultPoint(deltaY > 0 ? lastRange[0] : lastRange[1], lastY);
}
return new ResultPoint(lastRange[0], lastY);
} else {
return new ResultPoint(lastRange[1], lastY);
}
} else {
int lastX = x - deltaX;
if (lastRange[0] < centerY) {
if (lastRange[1] > centerY) {
return new ResultPoint(lastX, deltaX < 0 ? lastRange[0] : lastRange[1]);
}
return new ResultPoint(lastX, lastRange[0]);
} else {
return new ResultPoint(lastX, lastRange[1]);
}
}
}
lastRange = range;
}
throw NotFoundException.getNotFoundInstance();
}
/**
* Computes the start and end of a region of pixels, either horizontally or vertically, that could
* be part of a Data Matrix barcode.
*
* @param fixedDimension if scanning horizontally, this is the row (the fixed vertical location)
* where we are scanning. If scanning vertically it's the column, the fixed horizontal location
* @param maxWhiteRun largest run of white pixels that can still be considered part of the
* barcode region
* @param minDim minimum pixel location, horizontally or vertically, to consider
* @param maxDim maximum pixel location, horizontally or vertically, to consider
* @param horizontal if true, we're scanning left-right, instead of up-down
* @return int[] with start and end of found range, or null if no such range is found
* (e.g. only white was found)
*/
private int[] blackWhiteRange(int fixedDimension, int maxWhiteRun, int minDim, int maxDim,
boolean horizontal) {
int center = (minDim + maxDim) >> 1;
// Scan left/up first
int start = center;
while (start >= minDim) {
if (horizontal ? image.get(start, fixedDimension) : image.get(fixedDimension, start)) {
start--;
} else {
int whiteRunStart = start;
do {
start--;
} while (start >= minDim && !(horizontal ? image.get(start, fixedDimension) :
image.get(fixedDimension, start)));
int whiteRunSize = whiteRunStart - start;
if (start < minDim || whiteRunSize > maxWhiteRun) {
start = whiteRunStart;
break;
}
}
}
start++;
// Then try right/down
int end = center;
while (end < maxDim) {
if (horizontal ? image.get(end, fixedDimension) : image.get(fixedDimension, end)) {
end++;
} else {
int whiteRunStart = end;
do {
end++;
} while (end < maxDim && !(horizontal ? image.get(end, fixedDimension) :
image.get(fixedDimension, end)));
int whiteRunSize = end - whiteRunStart;
if (end >= maxDim || whiteRunSize > maxWhiteRun) {
end = whiteRunStart;
break;
}
}
}
end--;
return end > start ? new int[]{start, end} : null;
}
}

View File

@ -0,0 +1,316 @@
/*
* Copyright 2010 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.common.detector;
import com.google.zxing.NotFoundException;
import com.google.zxing.ResultPoint;
import com.google.zxing.common.BitMatrix;
/**
* <p>
* Detects a candidate barcode-like rectangular region within an image. It
* starts around the center of the image, increases the size of the candidate
* region until it finds a white rectangular region. By keeping track of the
* last black points it encountered, it determines the corners of the barcode.
* </p>
*
* @author David Olivier
*/
public final class WhiteRectangleDetector {
private static final int INIT_SIZE = 40;
private static final int CORR = 1;
private final BitMatrix image;
private final int height;
private final int width;
public WhiteRectangleDetector(BitMatrix image) {
this.image = image;
height = image.getHeight();
width = image.getWidth();
}
/**
* <p>
* Detects a candidate barcode-like rectangular region within an image. It
* starts around the center of the image, increases the size of the candidate
* region until it finds a white rectangular region.
* </p>
*
* @return {@link ResultPoint}[] describing the corners of the rectangular
* region. The first and last points are opposed on the diagonal, as
* are the second and third. The first point will be the topmost
* point and the last, the bottommost. The second point will be
* leftmost and the third, the rightmost
* @throws NotFoundException if no Data Matrix Code can be found
*/
public ResultPoint[] detect() throws NotFoundException {
int left = (width - INIT_SIZE) >> 1;
int right = (width + INIT_SIZE) >> 1;
int up = (height - INIT_SIZE) >> 1;
int down = (height + INIT_SIZE) >> 1;
boolean sizeExceeded = false;
boolean aBlackPointFoundOnBorder = true;
boolean atLeastOneBlackPointFoundOnBorder = false;
while (aBlackPointFoundOnBorder) {
aBlackPointFoundOnBorder = false;
// .....
// . |
// .....
boolean rightBorderNotWhite = true;
while (rightBorderNotWhite && right < width) {
rightBorderNotWhite = containsBlackPoint(up, down, right, false);
if (rightBorderNotWhite) {
right++;
aBlackPointFoundOnBorder = true;
}
}
if (right >= width) {
sizeExceeded = true;
break;
}
// .....
// . .
// .___.
boolean bottomBorderNotWhite = true;
while (bottomBorderNotWhite && down < height) {
bottomBorderNotWhite = containsBlackPoint(left, right, down, true);
if (bottomBorderNotWhite) {
down++;
aBlackPointFoundOnBorder = true;
}
}
if (down >= height) {
sizeExceeded = true;
break;
}
// .....
// | .
// .....
boolean leftBorderNotWhite = true;
while (leftBorderNotWhite && left >= 0) {
leftBorderNotWhite = containsBlackPoint(up, down, left, false);
if (leftBorderNotWhite) {
left--;
aBlackPointFoundOnBorder = true;
}
}
if (left < 0) {
sizeExceeded = true;
break;
}
// .___.
// . .
// .....
boolean topBorderNotWhite = true;
while (topBorderNotWhite && up >= 0) {
topBorderNotWhite = containsBlackPoint(left, right, up, true);
if (topBorderNotWhite) {
up--;
aBlackPointFoundOnBorder = true;
}
}
if (up < 0) {
sizeExceeded = true;
break;
}
if (aBlackPointFoundOnBorder) {
atLeastOneBlackPointFoundOnBorder = true;
}
}
if (!sizeExceeded && atLeastOneBlackPointFoundOnBorder) {
int maxSize = right - left;
ResultPoint z = null;
for (int i = 1; i < maxSize; i++) {
z = getBlackPointOnSegment(left, down - i, left + i, down);
if (z != null) {
break;
}
}
if (z == null) {
throw NotFoundException.getNotFoundInstance();
}
ResultPoint t = null;
//go down right
for (int i = 1; i < maxSize; i++) {
t = getBlackPointOnSegment(left, up + i, left + i, up);
if (t != null) {
break;
}
}
if (t == null) {
throw NotFoundException.getNotFoundInstance();
}
ResultPoint x = null;
//go down left
for (int i = 1; i < maxSize; i++) {
x = getBlackPointOnSegment(right, up + i, right - i, up);
if (x != null) {
break;
}
}
if (x == null) {
throw NotFoundException.getNotFoundInstance();
}
ResultPoint y = null;
//go up left
for (int i = 1; i < maxSize; i++) {
y = getBlackPointOnSegment(right, down - i, right - i, down);
if (y != null) {
break;
}
}
if (y == null) {
throw NotFoundException.getNotFoundInstance();
}
return centerEdges(y, z, x, t);
} else {
throw NotFoundException.getNotFoundInstance();
}
}
/**
* Ends up being a bit faster than Math.round(). This merely rounds its
* argument to the nearest int, where x.5 rounds up.
*/
private static int round(float d) {
return (int) (d + 0.5f);
}
private ResultPoint getBlackPointOnSegment(float aX, float aY, float bX, float bY) {
int dist = distanceL2(aX, aY, bX, bY);
float xStep = (bX - aX) / dist;
float yStep = (bY - aY) / dist;
for (int i = 0; i < dist; i++) {
int x = round(aX + i * xStep);
int y = round(aY + i * yStep);
if (image.get(x, y)) {
return new ResultPoint(x, y);
}
}
return null;
}
private static int distanceL2(float aX, float aY, float bX, float bY) {
float xDiff = aX - bX;
float yDiff = aY - bY;
return round((float) Math.sqrt(xDiff * xDiff + yDiff * yDiff));
}
/**
* recenters the points of a constant distance towards the center
*
* @param y bottom most point
* @param z left most point
* @param x right most point
* @param t top most point
* @return {@link ResultPoint}[] describing the corners of the rectangular
* region. The first and last points are opposed on the diagonal, as
* are the second and third. The first point will be the topmost
* point and the last, the bottommost. The second point will be
* leftmost and the third, the rightmost
*/
private ResultPoint[] centerEdges(ResultPoint y, ResultPoint z,
ResultPoint x, ResultPoint t) {
//
// t t
// z x
// x OR z
// y y
//
float yi = y.getX();
float yj = y.getY();
float zi = z.getX();
float zj = z.getY();
float xi = x.getX();
float xj = x.getY();
float ti = t.getX();
float tj = t.getY();
if (yi < width / 2) {
return new ResultPoint[]{
new ResultPoint(ti - CORR, tj + CORR),
new ResultPoint(zi + CORR, zj + CORR),
new ResultPoint(xi - CORR, xj - CORR),
new ResultPoint(yi + CORR, yj - CORR)};
} else {
return new ResultPoint[]{
new ResultPoint(ti + CORR, tj + CORR),
new ResultPoint(zi + CORR, zj - CORR),
new ResultPoint(xi - CORR, xj + CORR),
new ResultPoint(yi - CORR, yj - CORR)};
}
}
/**
* Determines whether a segment contains a black point
*
* @param a min value of the scanned coordinate
* @param b max value of the scanned coordinate
* @param fixed value of fixed coordinate
* @param horizontal set to true if scan must be horizontal, false if vertical
* @return true if a black point has been found, else false.
*/
private boolean containsBlackPoint(int a, int b, int fixed, boolean horizontal) {
if (horizontal) {
for (int x = a; x <= b; x++) {
if (image.get(x, fixed)) {
return true;
}
}
} else {
for (int y = a; y <= b; y++) {
if (image.get(fixed, y)) {
return true;
}
}
}
return false;
}
}

View File

@ -0,0 +1,139 @@
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.common.reedsolomon;
/**
* <p>This class contains utility methods for performing mathematical operations over
* the Galois Field GF(256). Operations use a given primitive polynomial in calculations.</p>
*
* <p>Throughout this package, elements of GF(256) are represented as an <code>int</code>
* for convenience and speed (but at the cost of memory).
* Only the bottom 8 bits are really used.</p>
*
* @author Sean Owen
*/
public final class GF256 {
public static final GF256 QR_CODE_FIELD = new GF256(0x011D); // x^8 + x^4 + x^3 + x^2 + 1
public static final GF256 DATA_MATRIX_FIELD = new GF256(0x012D); // x^8 + x^5 + x^3 + x^2 + 1
private final int[] expTable;
private final int[] logTable;
private final GF256Poly zero;
private final GF256Poly one;
/**
* Create a representation of GF(256) using the given primitive polynomial.
*
* @param primitive irreducible polynomial whose coefficients are represented by
* the bits of an int, where the least-significant bit represents the constant
* coefficient
*/
private GF256(int primitive) {
expTable = new int[256];
logTable = new int[256];
int x = 1;
for (int i = 0; i < 256; i++) {
expTable[i] = x;
x <<= 1; // x = x * 2; we're assuming the generator alpha is 2
if (x >= 0x100) {
x ^= primitive;
}
}
for (int i = 0; i < 255; i++) {
logTable[expTable[i]] = i;
}
// logTable[0] == 0 but this should never be used
zero = new GF256Poly(this, new int[]{0});
one = new GF256Poly(this, new int[]{1});
}
GF256Poly getZero() {
return zero;
}
GF256Poly getOne() {
return one;
}
/**
* @return the monomial representing coefficient * x^degree
*/
GF256Poly buildMonomial(int degree, int coefficient) {
if (degree < 0) {
throw new IllegalArgumentException();
}
if (coefficient == 0) {
return zero;
}
int[] coefficients = new int[degree + 1];
coefficients[0] = coefficient;
return new GF256Poly(this, coefficients);
}
/**
* Implements both addition and subtraction -- they are the same in GF(256).
*
* @return sum/difference of a and b
*/
static int addOrSubtract(int a, int b) {
return a ^ b;
}
/**
* @return 2 to the power of a in GF(256)
*/
int exp(int a) {
return expTable[a];
}
/**
* @return base 2 log of a in GF(256)
*/
int log(int a) {
if (a == 0) {
throw new IllegalArgumentException();
}
return logTable[a];
}
/**
* @return multiplicative inverse of a
*/
int inverse(int a) {
if (a == 0) {
throw new ArithmeticException();
}
return expTable[255 - logTable[a]];
}
/**
* @param a
* @param b
* @return product of a and b in GF(256)
*/
int multiply(int a, int b) {
if (a == 0 || b == 0) {
return 0;
}
int logSum = logTable[a] + logTable[b];
// index is a sped-up alternative to logSum % 255 since sum
// is in [0,510]. Thanks to jmsachs for the idea
return expTable[(logSum & 0xFF) + (logSum >>> 8)];
}
}

View File

@ -0,0 +1,263 @@
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.common.reedsolomon;
/**
* <p>Represents a polynomial whose coefficients are elements of GF(256).
* Instances of this class are immutable.</p>
*
* <p>Much credit is due to William Rucklidge since portions of this code are an indirect
* port of his C++ Reed-Solomon implementation.</p>
*
* @author Sean Owen
*/
final class GF256Poly {
private final GF256 field;
private final int[] coefficients;
/**
* @param field the {@link GF256} instance representing the field to use
* to perform computations
* @param coefficients coefficients as ints representing elements of GF(256), arranged
* from most significant (highest-power term) coefficient to least significant
* @throws IllegalArgumentException if argument is null or empty,
* or if leading coefficient is 0 and this is not a
* constant polynomial (that is, it is not the monomial "0")
*/
GF256Poly(GF256 field, int[] coefficients) {
if (coefficients == null || coefficients.length == 0) {
throw new IllegalArgumentException();
}
this.field = field;
int coefficientsLength = coefficients.length;
if (coefficientsLength > 1 && coefficients[0] == 0) {
// Leading term must be non-zero for anything except the constant polynomial "0"
int firstNonZero = 1;
while (firstNonZero < coefficientsLength && coefficients[firstNonZero] == 0) {
firstNonZero++;
}
if (firstNonZero == coefficientsLength) {
this.coefficients = field.getZero().coefficients;
} else {
this.coefficients = new int[coefficientsLength - firstNonZero];
System.arraycopy(coefficients,
firstNonZero,
this.coefficients,
0,
this.coefficients.length);
}
} else {
this.coefficients = coefficients;
}
}
int[] getCoefficients() {
return coefficients;
}
/**
* @return degree of this polynomial
*/
int getDegree() {
return coefficients.length - 1;
}
/**
* @return true iff this polynomial is the monomial "0"
*/
boolean isZero() {
return coefficients[0] == 0;
}
/**
* @return coefficient of x^degree term in this polynomial
*/
int getCoefficient(int degree) {
return coefficients[coefficients.length - 1 - degree];
}
/**
* @return evaluation of this polynomial at a given point
*/
int evaluateAt(int a) {
if (a == 0) {
// Just return the x^0 coefficient
return getCoefficient(0);
}
int size = coefficients.length;
if (a == 1) {
// Just the sum of the coefficients
int result = 0;
for (int i = 0; i < size; i++) {
result = GF256.addOrSubtract(result, coefficients[i]);
}
return result;
}
int result = coefficients[0];
for (int i = 1; i < size; i++) {
result = GF256.addOrSubtract(field.multiply(a, result), coefficients[i]);
}
return result;
}
GF256Poly addOrSubtract(GF256Poly other) {
if (!field.equals(other.field)) {
throw new IllegalArgumentException("GF256Polys do not have same GF256 field");
}
if (isZero()) {
return other;
}
if (other.isZero()) {
return this;
}
int[] smallerCoefficients = this.coefficients;
int[] largerCoefficients = other.coefficients;
if (smallerCoefficients.length > largerCoefficients.length) {
int[] temp = smallerCoefficients;
smallerCoefficients = largerCoefficients;
largerCoefficients = temp;
}
int[] sumDiff = new int[largerCoefficients.length];
int lengthDiff = largerCoefficients.length - smallerCoefficients.length;
// Copy high-order terms only found in higher-degree polynomial's coefficients
System.arraycopy(largerCoefficients, 0, sumDiff, 0, lengthDiff);
for (int i = lengthDiff; i < largerCoefficients.length; i++) {
sumDiff[i] = GF256.addOrSubtract(smallerCoefficients[i - lengthDiff], largerCoefficients[i]);
}
return new GF256Poly(field, sumDiff);
}
GF256Poly multiply(GF256Poly other) {
if (!field.equals(other.field)) {
throw new IllegalArgumentException("GF256Polys do not have same GF256 field");
}
if (isZero() || other.isZero()) {
return field.getZero();
}
int[] aCoefficients = this.coefficients;
int aLength = aCoefficients.length;
int[] bCoefficients = other.coefficients;
int bLength = bCoefficients.length;
int[] product = new int[aLength + bLength - 1];
for (int i = 0; i < aLength; i++) {
int aCoeff = aCoefficients[i];
for (int j = 0; j < bLength; j++) {
product[i + j] = GF256.addOrSubtract(product[i + j],
field.multiply(aCoeff, bCoefficients[j]));
}
}
return new GF256Poly(field, product);
}
GF256Poly multiply(int scalar) {
if (scalar == 0) {
return field.getZero();
}
if (scalar == 1) {
return this;
}
int size = coefficients.length;
int[] product = new int[size];
for (int i = 0; i < size; i++) {
product[i] = field.multiply(coefficients[i], scalar);
}
return new GF256Poly(field, product);
}
GF256Poly multiplyByMonomial(int degree, int coefficient) {
if (degree < 0) {
throw new IllegalArgumentException();
}
if (coefficient == 0) {
return field.getZero();
}
int size = coefficients.length;
int[] product = new int[size + degree];
for (int i = 0; i < size; i++) {
product[i] = field.multiply(coefficients[i], coefficient);
}
return new GF256Poly(field, product);
}
GF256Poly[] divide(GF256Poly other) {
if (!field.equals(other.field)) {
throw new IllegalArgumentException("GF256Polys do not have same GF256 field");
}
if (other.isZero()) {
throw new IllegalArgumentException("Divide by 0");
}
GF256Poly quotient = field.getZero();
GF256Poly remainder = this;
int denominatorLeadingTerm = other.getCoefficient(other.getDegree());
int inverseDenominatorLeadingTerm = field.inverse(denominatorLeadingTerm);
while (remainder.getDegree() >= other.getDegree() && !remainder.isZero()) {
int degreeDifference = remainder.getDegree() - other.getDegree();
int scale = field.multiply(remainder.getCoefficient(remainder.getDegree()), inverseDenominatorLeadingTerm);
GF256Poly term = other.multiplyByMonomial(degreeDifference, scale);
GF256Poly iterationQuotient = field.buildMonomial(degreeDifference, scale);
quotient = quotient.addOrSubtract(iterationQuotient);
remainder = remainder.addOrSubtract(term);
}
return new GF256Poly[] { quotient, remainder };
}
public String toString() {
StringBuffer result = new StringBuffer(8 * getDegree());
for (int degree = getDegree(); degree >= 0; degree--) {
int coefficient = getCoefficient(degree);
if (coefficient != 0) {
if (coefficient < 0) {
result.append(" - ");
coefficient = -coefficient;
} else {
if (result.length() > 0) {
result.append(" + ");
}
}
if (degree == 0 || coefficient != 1) {
int alphaPower = field.log(coefficient);
if (alphaPower == 0) {
result.append('1');
} else if (alphaPower == 1) {
result.append('a');
} else {
result.append("a^");
result.append(alphaPower);
}
}
if (degree != 0) {
if (degree == 1) {
result.append('x');
} else {
result.append("x^");
result.append(degree);
}
}
}
}
return result.toString();
}
}

View File

@ -0,0 +1,194 @@
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.common.reedsolomon;
/**
* <p>Implements Reed-Solomon decoding, as the name implies.</p>
*
* <p>The algorithm will not be explained here, but the following references were helpful
* in creating this implementation:</p>
*
* <ul>
* <li>Bruce Maggs.
* <a href="http://www.cs.cmu.edu/afs/cs.cmu.edu/project/pscico-guyb/realworld/www/rs_decode.ps">
* "Decoding Reed-Solomon Codes"</a> (see discussion of Forney's Formula)</li>
* <li>J.I. Hall. <a href="www.mth.msu.edu/~jhall/classes/codenotes/GRS.pdf">
* "Chapter 5. Generalized Reed-Solomon Codes"</a>
* (see discussion of Euclidean algorithm)</li>
* </ul>
*
* <p>Much credit is due to William Rucklidge since portions of this code are an indirect
* port of his C++ Reed-Solomon implementation.</p>
*
* @author Sean Owen
* @author William Rucklidge
* @author sanfordsquires
*/
public final class ReedSolomonDecoder {
private final GF256 field;
public ReedSolomonDecoder(GF256 field) {
this.field = field;
}
/**
* <p>Decodes given set of received codewords, which include both data and error-correction
* codewords. Really, this means it uses Reed-Solomon to detect and correct errors, in-place,
* in the input.</p>
*
* @param received data and error-correction codewords
* @param twoS number of error-correction codewords available
* @throws ReedSolomonException if decoding fails for any reason
*/
public void decode(int[] received, int twoS) throws ReedSolomonException {
GF256Poly poly = new GF256Poly(field, received);
int[] syndromeCoefficients = new int[twoS];
boolean dataMatrix = field.equals(GF256.DATA_MATRIX_FIELD);
boolean noError = true;
for (int i = 0; i < twoS; i++) {
// Thanks to sanfordsquires for this fix:
int eval = poly.evaluateAt(field.exp(dataMatrix ? i + 1 : i));
syndromeCoefficients[syndromeCoefficients.length - 1 - i] = eval;
if (eval != 0) {
noError = false;
}
}
if (noError) {
return;
}
GF256Poly syndrome = new GF256Poly(field, syndromeCoefficients);
GF256Poly[] sigmaOmega =
runEuclideanAlgorithm(field.buildMonomial(twoS, 1), syndrome, twoS);
GF256Poly sigma = sigmaOmega[0];
GF256Poly omega = sigmaOmega[1];
int[] errorLocations = findErrorLocations(sigma);
int[] errorMagnitudes = findErrorMagnitudes(omega, errorLocations, dataMatrix);
for (int i = 0; i < errorLocations.length; i++) {
int position = received.length - 1 - field.log(errorLocations[i]);
if (position < 0) {
throw new ReedSolomonException("Bad error location");
}
received[position] = GF256.addOrSubtract(received[position], errorMagnitudes[i]);
}
}
private GF256Poly[] runEuclideanAlgorithm(GF256Poly a, GF256Poly b, int R)
throws ReedSolomonException {
// Assume a's degree is >= b's
if (a.getDegree() < b.getDegree()) {
GF256Poly temp = a;
a = b;
b = temp;
}
GF256Poly rLast = a;
GF256Poly r = b;
GF256Poly sLast = field.getOne();
GF256Poly s = field.getZero();
GF256Poly tLast = field.getZero();
GF256Poly t = field.getOne();
// Run Euclidean algorithm until r's degree is less than R/2
while (r.getDegree() >= R / 2) {
GF256Poly rLastLast = rLast;
GF256Poly sLastLast = sLast;
GF256Poly tLastLast = tLast;
rLast = r;
sLast = s;
tLast = t;
// Divide rLastLast by rLast, with quotient in q and remainder in r
if (rLast.isZero()) {
// Oops, Euclidean algorithm already terminated?
throw new ReedSolomonException("r_{i-1} was zero");
}
r = rLastLast;
GF256Poly q = field.getZero();
int denominatorLeadingTerm = rLast.getCoefficient(rLast.getDegree());
int dltInverse = field.inverse(denominatorLeadingTerm);
while (r.getDegree() >= rLast.getDegree() && !r.isZero()) {
int degreeDiff = r.getDegree() - rLast.getDegree();
int scale = field.multiply(r.getCoefficient(r.getDegree()), dltInverse);
q = q.addOrSubtract(field.buildMonomial(degreeDiff, scale));
r = r.addOrSubtract(rLast.multiplyByMonomial(degreeDiff, scale));
}
s = q.multiply(sLast).addOrSubtract(sLastLast);
t = q.multiply(tLast).addOrSubtract(tLastLast);
}
int sigmaTildeAtZero = t.getCoefficient(0);
if (sigmaTildeAtZero == 0) {
throw new ReedSolomonException("sigmaTilde(0) was zero");
}
int inverse = field.inverse(sigmaTildeAtZero);
GF256Poly sigma = t.multiply(inverse);
GF256Poly omega = r.multiply(inverse);
return new GF256Poly[]{sigma, omega};
}
private int[] findErrorLocations(GF256Poly errorLocator) throws ReedSolomonException {
// This is a direct application of Chien's search
int numErrors = errorLocator.getDegree();
if (numErrors == 1) { // shortcut
return new int[] { errorLocator.getCoefficient(1) };
}
int[] result = new int[numErrors];
int e = 0;
for (int i = 1; i < 256 && e < numErrors; i++) {
if (errorLocator.evaluateAt(i) == 0) {
result[e] = field.inverse(i);
e++;
}
}
if (e != numErrors) {
throw new ReedSolomonException("Error locator degree does not match number of roots");
}
return result;
}
private int[] findErrorMagnitudes(GF256Poly errorEvaluator, int[] errorLocations, boolean dataMatrix) {
// This is directly applying Forney's Formula
int s = errorLocations.length;
int[] result = new int[s];
for (int i = 0; i < s; i++) {
int xiInverse = field.inverse(errorLocations[i]);
int denominator = 1;
for (int j = 0; j < s; j++) {
if (i != j) {
//denominator = field.multiply(denominator,
// GF256.addOrSubtract(1, field.multiply(errorLocations[j], xiInverse)));
// Above should work but fails on some Apple and Linux JDKs due to a Hotspot bug.
// Below is a funny-looking workaround from Steven Parkes
int term = field.multiply(errorLocations[j], xiInverse);
int termPlus1 = ((term & 0x1) == 0) ? (term | 1) : (term & ~1);
denominator = field.multiply(denominator, termPlus1);
}
}
result[i] = field.multiply(errorEvaluator.evaluateAt(xiInverse),
field.inverse(denominator));
// Thanks to sanfordsquires for this fix:
if (dataMatrix) {
result[i] = field.multiply(result[i], xiInverse);
}
}
return result;
}
}

View File

@ -0,0 +1,75 @@
/*
* Copyright 2008 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.common.reedsolomon;
import java.util.Vector;
/**
* <p>Implements Reed-Solomon enbcoding, as the name implies.</p>
*
* @author Sean Owen
* @author William Rucklidge
*/
public final class ReedSolomonEncoder {
private final GF256 field;
private final Vector cachedGenerators;
public ReedSolomonEncoder(GF256 field) {
if (!GF256.QR_CODE_FIELD.equals(field)) {
throw new IllegalArgumentException("Only QR Code is supported at this time");
}
this.field = field;
this.cachedGenerators = new Vector();
cachedGenerators.addElement(new GF256Poly(field, new int[] { 1 }));
}
private GF256Poly buildGenerator(int degree) {
if (degree >= cachedGenerators.size()) {
GF256Poly lastGenerator = (GF256Poly) cachedGenerators.elementAt(cachedGenerators.size() - 1);
for (int d = cachedGenerators.size(); d <= degree; d++) {
GF256Poly nextGenerator = lastGenerator.multiply(new GF256Poly(field, new int[] { 1, field.exp(d - 1) }));
cachedGenerators.addElement(nextGenerator);
lastGenerator = nextGenerator;
}
}
return (GF256Poly) cachedGenerators.elementAt(degree);
}
public void encode(int[] toEncode, int ecBytes) {
if (ecBytes == 0) {
throw new IllegalArgumentException("No error correction bytes");
}
int dataBytes = toEncode.length - ecBytes;
if (dataBytes <= 0) {
throw new IllegalArgumentException("No data bytes provided");
}
GF256Poly generator = buildGenerator(ecBytes);
int[] infoCoefficients = new int[dataBytes];
System.arraycopy(toEncode, 0, infoCoefficients, 0, dataBytes);
GF256Poly info = new GF256Poly(field, infoCoefficients);
info = info.multiplyByMonomial(ecBytes, 1);
GF256Poly remainder = info.divide(generator)[1];
int[] coefficients = remainder.getCoefficients();
int numZeroCoefficients = ecBytes - coefficients.length;
for (int i = 0; i < numZeroCoefficients; i++) {
toEncode[dataBytes + i] = 0;
}
System.arraycopy(coefficients, 0, toEncode, dataBytes + numZeroCoefficients, coefficients.length);
}
}

View File

@ -0,0 +1,31 @@
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.common.reedsolomon;
/**
* <p>Thrown when an exception occurs during Reed-Solomon decoding, such as when
* there are too many errors to correct.</p>
*
* @author Sean Owen
*/
public final class ReedSolomonException extends Exception {
public ReedSolomonException(String message) {
super(message);
}
}

View File

@ -0,0 +1,161 @@
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.datamatrix;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.ChecksumException;
import com.google.zxing.DecodeHintType;
import com.google.zxing.FormatException;
import com.google.zxing.NotFoundException;
import com.google.zxing.Reader;
import com.google.zxing.Result;
import com.google.zxing.ResultMetadataType;
import com.google.zxing.ResultPoint;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.DecoderResult;
import com.google.zxing.common.DetectorResult;
import com.google.zxing.datamatrix.decoder.Decoder;
import com.google.zxing.datamatrix.detector.Detector;
import java.util.Hashtable;
/**
* This implementation can detect and decode Data Matrix codes in an image.
*
* @author bbrown@google.com (Brian Brown)
*/
public final class DataMatrixReader implements Reader {
private static final ResultPoint[] NO_POINTS = new ResultPoint[0];
private final Decoder decoder = new Decoder();
/**
* Locates and decodes a Data Matrix code in an image.
*
* @return a String representing the content encoded by the Data Matrix code
* @throws NotFoundException if a Data Matrix code cannot be found
* @throws FormatException if a Data Matrix code cannot be decoded
* @throws ChecksumException if error correction fails
*/
public Result decode(BinaryBitmap image) throws NotFoundException, ChecksumException, FormatException {
return decode(image, null);
}
public Result decode(BinaryBitmap image, Hashtable hints)
throws NotFoundException, ChecksumException, FormatException {
DecoderResult decoderResult;
ResultPoint[] points;
if (hints != null && hints.containsKey(DecodeHintType.PURE_BARCODE)) {
BitMatrix bits = extractPureBits(image.getBlackMatrix());
decoderResult = decoder.decode(bits);
points = NO_POINTS;
} else {
DetectorResult detectorResult = new Detector(image.getBlackMatrix()).detect();
decoderResult = decoder.decode(detectorResult.getBits());
points = detectorResult.getPoints();
}
Result result = new Result(decoderResult.getText(), decoderResult.getRawBytes(), points,
BarcodeFormat.DATA_MATRIX);
if (decoderResult.getByteSegments() != null) {
result.putMetadata(ResultMetadataType.BYTE_SEGMENTS, decoderResult.getByteSegments());
}
if (decoderResult.getECLevel() != null) {
result.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, decoderResult.getECLevel().toString());
}
return result;
}
public void reset() {
// do nothing
}
/**
* This method detects a Data Matrix code in a "pure" image -- that is, pure monochrome image
* which contains only an unrotated, unskewed, image of a Data Matrix code, with some white border
* around it. This is a specialized method that works exceptionally fast in this special
* case.
*
* @see com.google.zxing.qrcode.QRCodeReader#extractPureBits(BitMatrix)
*/
private static BitMatrix extractPureBits(BitMatrix image) throws NotFoundException {
int height = image.getHeight();
int width = image.getWidth();
int minDimension = Math.min(height, width);
// And then keep tracking across the top-left black module to determine module size
//int moduleEnd = borderWidth;
int[] leftTopBlack = image.getTopLeftOnBit();
if (leftTopBlack == null) {
throw NotFoundException.getNotFoundInstance();
}
int x = leftTopBlack[0];
int y = leftTopBlack[1];
while (x < minDimension && y < minDimension && image.get(x, y)) {
x++;
}
if (x == minDimension) {
throw NotFoundException.getNotFoundInstance();
}
int moduleSize = x - leftTopBlack[0];
// And now find where the rightmost black module on the first row ends
int rowEndOfSymbol = width - 1;
while (rowEndOfSymbol >= 0 && !image.get(rowEndOfSymbol, y)) {
rowEndOfSymbol--;
}
if (rowEndOfSymbol < 0) {
throw NotFoundException.getNotFoundInstance();
}
rowEndOfSymbol++;
// Make sure width of barcode is a multiple of module size
if ((rowEndOfSymbol - x) % moduleSize != 0) {
throw NotFoundException.getNotFoundInstance();
}
int dimension = 2 + ((rowEndOfSymbol - x) / moduleSize);
y += moduleSize;
// Push in the "border" by half the module width so that we start
// sampling in the middle of the module. Just in case the image is a
// little off, this will help recover.
x -= moduleSize >> 1;
y -= moduleSize >> 1;
if ((x + (dimension - 1) * moduleSize) >= width ||
(y + (dimension - 1) * moduleSize) >= height) {
throw NotFoundException.getNotFoundInstance();
}
// Now just read off the bits
BitMatrix bits = new BitMatrix(dimension);
for (int i = 0; i < dimension; i++) {
int iOffset = y + i * moduleSize;
for (int j = 0; j < dimension; j++) {
if (image.get(x + j * moduleSize, iOffset)) {
bits.set(j, i);
}
}
}
return bits;
}
}

View File

@ -0,0 +1,446 @@
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.datamatrix.decoder;
import com.google.zxing.FormatException;
import com.google.zxing.common.BitMatrix;
/**
* @author bbrown@google.com (Brian Brown)
*/
final class BitMatrixParser {
private final BitMatrix mappingBitMatrix;
private final BitMatrix readMappingMatrix;
private final Version version;
/**
* @param bitMatrix {@link BitMatrix} to parse
* @throws FormatException if dimension is < 10 or > 144 or not 0 mod 2
*/
BitMatrixParser(BitMatrix bitMatrix) throws FormatException {
int dimension = bitMatrix.getHeight();
if (dimension < 10 || dimension > 144 || (dimension & 0x01) != 0) {
throw FormatException.getFormatInstance();
}
version = readVersion(bitMatrix);
this.mappingBitMatrix = extractDataRegion(bitMatrix);
// TODO(bbrown): Make this work for rectangular symbols
this.readMappingMatrix = new BitMatrix(this.mappingBitMatrix.getHeight());
}
/**
* <p>Creates the version object based on the dimension of the original bit matrix from
* the datamatrix code.</p>
*
* <p>See ISO 16022:2006 Table 7 - ECC 200 symbol attributes</p>
*
* @param bitMatrix Original {@link BitMatrix} including alignment patterns
* @return {@link Version} encapsulating the Data Matrix Code's "version"
* @throws FormatException if the dimensions of the mapping matrix are not valid
* Data Matrix dimensions.
*/
Version readVersion(BitMatrix bitMatrix) throws FormatException {
if (version != null) {
return version;
}
// TODO(bbrown): make this work for rectangular dimensions as well.
int numRows = bitMatrix.getHeight();
int numColumns = numRows;
return Version.getVersionForDimensions(numRows, numColumns);
}
/**
* <p>Reads the bits in the {@link BitMatrix} representing the mapping matrix (No alignment patterns)
* in the correct order in order to reconstitute the codewords bytes contained within the
* Data Matrix Code.</p>
*
* @return bytes encoded within the Data Matrix Code
* @throws FormatException if the exact number of bytes expected is not read
*/
byte[] readCodewords() throws FormatException {
byte[] result = new byte[version.getTotalCodewords()];
int resultOffset = 0;
int row = 4;
int column = 0;
// TODO(bbrown): Data Matrix can be rectangular, assuming square for now
int numRows = mappingBitMatrix.getHeight();
int numColumns = numRows;
boolean corner1Read = false;
boolean corner2Read = false;
boolean corner3Read = false;
boolean corner4Read = false;
// Read all of the codewords
do {
// Check the four corner cases
if ((row == numRows) && (column == 0) && !corner1Read) {
result[resultOffset++] = (byte) readCorner1(numRows, numColumns);
row -= 2;
column +=2;
corner1Read = true;
} else if ((row == numRows-2) && (column == 0) && ((numColumns & 0x03) != 0) && !corner2Read) {
result[resultOffset++] = (byte) readCorner2(numRows, numColumns);
row -= 2;
column +=2;
corner2Read = true;
} else if ((row == numRows+4) && (column == 2) && ((numColumns & 0x07) == 0) && !corner3Read) {
result[resultOffset++] = (byte) readCorner3(numRows, numColumns);
row -= 2;
column +=2;
corner3Read = true;
} else if ((row == numRows-2) && (column == 0) && ((numColumns & 0x07) == 4) && !corner4Read) {
result[resultOffset++] = (byte) readCorner4(numRows, numColumns);
row -= 2;
column +=2;
corner4Read = true;
} else {
// Sweep upward diagonally to the right
do {
if ((row < numRows) && (column >= 0) && !readMappingMatrix.get(column, row)) {
result[resultOffset++] = (byte) readUtah(row, column, numRows, numColumns);
}
row -= 2;
column +=2;
} while ((row >= 0) && (column < numColumns));
row += 1;
column +=3;
// Sweep downward diagonally to the left
do {
if ((row >= 0) && (column < numColumns) && !readMappingMatrix.get(column, row)) {
result[resultOffset++] = (byte) readUtah(row, column, numRows, numColumns);
}
row += 2;
column -=2;
} while ((row < numRows) && (column >= 0));
row += 3;
column +=1;
}
} while ((row < numRows) || (column < numColumns));
if (resultOffset != version.getTotalCodewords()) {
throw FormatException.getFormatInstance();
}
return result;
}
/**
* <p>Reads a bit of the mapping matrix accounting for boundary wrapping.</p>
*
* @param row Row to read in the mapping matrix
* @param column Column to read in the mapping matrix
* @param numRows Number of rows in the mapping matrix
* @param numColumns Number of columns in the mapping matrix
* @return value of the given bit in the mapping matrix
*/
boolean readModule(int row, int column, int numRows, int numColumns) {
// Adjust the row and column indices based on boundary wrapping
if (row < 0) {
row += numRows;
column += 4 - ((numRows + 4) & 0x07);
}
if (column < 0) {
column += numColumns;
row += 4 - ((numColumns + 4) & 0x07);
}
readMappingMatrix.set(column, row);
return mappingBitMatrix.get(column, row);
}
/**
* <p>Reads the 8 bits of the standard Utah-shaped pattern.</p>
*
* <p>See ISO 16022:2006, 5.8.1 Figure 6</p>
*
* @param row Current row in the mapping matrix, anchored at the 8th bit (LSB) of the pattern
* @param column Current column in the mapping matrix, anchored at the 8th bit (LSB) of the pattern
* @param numRows Number of rows in the mapping matrix
* @param numColumns Number of columns in the mapping matrix
* @return byte from the utah shape
*/
int readUtah(int row, int column, int numRows, int numColumns) {
int currentByte = 0;
if (readModule(row - 2, column - 2, numRows, numColumns)) {
currentByte |= 1;
}
currentByte <<= 1;
if (readModule(row - 2, column - 1, numRows, numColumns)) {
currentByte |= 1;
}
currentByte <<= 1;
if (readModule(row - 1, column - 2, numRows, numColumns)) {
currentByte |= 1;
}
currentByte <<= 1;
if (readModule(row - 1, column - 1, numRows, numColumns)) {
currentByte |= 1;
}
currentByte <<= 1;
if (readModule(row - 1, column, numRows, numColumns)) {
currentByte |= 1;
}
currentByte <<= 1;
if (readModule(row, column - 2, numRows, numColumns)) {
currentByte |= 1;
}
currentByte <<= 1;
if (readModule(row, column - 1, numRows, numColumns)) {
currentByte |= 1;
}
currentByte <<= 1;
if (readModule(row, column, numRows, numColumns)) {
currentByte |= 1;
}
return currentByte;
}
/**
* <p>Reads the 8 bits of the special corner condition 1.</p>
*
* <p>See ISO 16022:2006, Figure F.3</p>
*
* @param numRows Number of rows in the mapping matrix
* @param numColumns Number of columns in the mapping matrix
* @return byte from the Corner condition 1
*/
int readCorner1(int numRows, int numColumns) {
int currentByte = 0;
if (readModule(numRows - 1, 0, numRows, numColumns)) {
currentByte |= 1;
}
currentByte <<= 1;
if (readModule(numRows - 1, 1, numRows, numColumns)) {
currentByte |= 1;
}
currentByte <<= 1;
if (readModule(numRows - 1, 2, numRows, numColumns)) {
currentByte |= 1;
}
currentByte <<= 1;
if (readModule(0, numColumns - 2, numRows, numColumns)) {
currentByte |= 1;
}
currentByte <<= 1;
if (readModule(0, numColumns - 1, numRows, numColumns)) {
currentByte |= 1;
}
currentByte <<= 1;
if (readModule(1, numColumns - 1, numRows, numColumns)) {
currentByte |= 1;
}
currentByte <<= 1;
if (readModule(2, numColumns - 1, numRows, numColumns)) {
currentByte |= 1;
}
currentByte <<= 1;
if (readModule(3, numColumns - 1, numRows, numColumns)) {
currentByte |= 1;
}
return currentByte;
}
/**
* <p>Reads the 8 bits of the special corner condition 2.</p>
*
* <p>See ISO 16022:2006, Figure F.4</p>
*
* @param numRows Number of rows in the mapping matrix
* @param numColumns Number of columns in the mapping matrix
* @return byte from the Corner condition 2
*/
int readCorner2(int numRows, int numColumns) {
int currentByte = 0;
if (readModule(numRows - 3, 0, numRows, numColumns)) {
currentByte |= 1;
}
currentByte <<= 1;
if (readModule(numRows - 2, 0, numRows, numColumns)) {
currentByte |= 1;
}
currentByte <<= 1;
if (readModule(numRows - 1, 0, numRows, numColumns)) {
currentByte |= 1;
}
currentByte <<= 1;
if (readModule(0, numColumns - 4, numRows, numColumns)) {
currentByte |= 1;
}
currentByte <<= 1;
if (readModule(0, numColumns - 3, numRows, numColumns)) {
currentByte |= 1;
}
currentByte <<= 1;
if (readModule(0, numColumns - 2, numRows, numColumns)) {
currentByte |= 1;
}
currentByte <<= 1;
if (readModule(0, numColumns - 1, numRows, numColumns)) {
currentByte |= 1;
}
currentByte <<= 1;
if (readModule(1, numColumns - 1, numRows, numColumns)) {
currentByte |= 1;
}
return currentByte;
}
/**
* <p>Reads the 8 bits of the special corner condition 3.</p>
*
* <p>See ISO 16022:2006, Figure F.5</p>
*
* @param numRows Number of rows in the mapping matrix
* @param numColumns Number of columns in the mapping matrix
* @return byte from the Corner condition 3
*/
int readCorner3(int numRows, int numColumns) {
int currentByte = 0;
if (readModule(numRows - 1, 0, numRows, numColumns)) {
currentByte |= 1;
}
currentByte <<= 1;
if (readModule(numRows - 1, numColumns - 1, numRows, numColumns)) {
currentByte |= 1;
}
currentByte <<= 1;
if (readModule(0, numColumns - 3, numRows, numColumns)) {
currentByte |= 1;
}
currentByte <<= 1;
if (readModule(0, numColumns - 2, numRows, numColumns)) {
currentByte |= 1;
}
currentByte <<= 1;
if (readModule(0, numColumns - 1, numRows, numColumns)) {
currentByte |= 1;
}
currentByte <<= 1;
if (readModule(1, numColumns - 3, numRows, numColumns)) {
currentByte |= 1;
}
currentByte <<= 1;
if (readModule(1, numColumns - 2, numRows, numColumns)) {
currentByte |= 1;
}
currentByte <<= 1;
if (readModule(1, numColumns - 1, numRows, numColumns)) {
currentByte |= 1;
}
return currentByte;
}
/**
* <p>Reads the 8 bits of the special corner condition 4.</p>
*
* <p>See ISO 16022:2006, Figure F.6</p>
*
* @param numRows Number of rows in the mapping matrix
* @param numColumns Number of columns in the mapping matrix
* @return byte from the Corner condition 4
*/
int readCorner4(int numRows, int numColumns) {
int currentByte = 0;
if (readModule(numRows - 3, 0, numRows, numColumns)) {
currentByte |= 1;
}
currentByte <<= 1;
if (readModule(numRows - 2, 0, numRows, numColumns)) {
currentByte |= 1;
}
currentByte <<= 1;
if (readModule(numRows - 1, 0, numRows, numColumns)) {
currentByte |= 1;
}
currentByte <<= 1;
if (readModule(0, numColumns - 2, numRows, numColumns)) {
currentByte |= 1;
}
currentByte <<= 1;
if (readModule(0, numColumns - 1, numRows, numColumns)) {
currentByte |= 1;
}
currentByte <<= 1;
if (readModule(1, numColumns - 1, numRows, numColumns)) {
currentByte |= 1;
}
currentByte <<= 1;
if (readModule(2, numColumns - 1, numRows, numColumns)) {
currentByte |= 1;
}
currentByte <<= 1;
if (readModule(3, numColumns - 1, numRows, numColumns)) {
currentByte |= 1;
}
return currentByte;
}
/**
* <p>Extracts the data region from a {@link BitMatrix} that contains
* alignment patterns.</p>
*
* @param bitMatrix Original {@link BitMatrix} with alignment patterns
* @return BitMatrix that has the alignment patterns removed
*/
BitMatrix extractDataRegion(BitMatrix bitMatrix) {
int symbolSizeRows = version.getSymbolSizeRows();
int symbolSizeColumns = version.getSymbolSizeColumns();
// TODO(bbrown): Make this work with rectangular codes
if (bitMatrix.getHeight() != symbolSizeRows) {
throw new IllegalArgumentException("Dimension of bitMarix must match the version size");
}
int dataRegionSizeRows = version.getDataRegionSizeRows();
int dataRegionSizeColumns = version.getDataRegionSizeColumns();
int numDataRegionsRow = symbolSizeRows / dataRegionSizeRows;
int numDataRegionsColumn = symbolSizeColumns / dataRegionSizeColumns;
int sizeDataRegionRow = numDataRegionsRow * dataRegionSizeRows;
//int sizeDataRegionColumn = numDataRegionsColumn * dataRegionSizeColumns;
// TODO(bbrown): Make this work with rectangular codes
BitMatrix bitMatrixWithoutAlignment = new BitMatrix(sizeDataRegionRow);
for (int dataRegionRow = 0; dataRegionRow < numDataRegionsRow; ++dataRegionRow) {
int dataRegionRowOffset = dataRegionRow * dataRegionSizeRows;
for (int dataRegionColumn = 0; dataRegionColumn < numDataRegionsColumn; ++dataRegionColumn) {
int dataRegionColumnOffset = dataRegionColumn * dataRegionSizeColumns;
for (int i = 0; i < dataRegionSizeRows; ++i) {
int readRowOffset = dataRegionRow * (dataRegionSizeRows + 2) + 1 + i;
int writeRowOffset = dataRegionRowOffset + i;
for (int j = 0; j < dataRegionSizeColumns; ++j) {
int readColumnOffset = dataRegionColumn * (dataRegionSizeColumns + 2) + 1 + j;
if (bitMatrix.get(readColumnOffset, readRowOffset)) {
int writeColumnOffset = dataRegionColumnOffset + j;
bitMatrixWithoutAlignment.set(writeColumnOffset, writeRowOffset);
}
}
}
}
}
return bitMatrixWithoutAlignment;
}
}

View File

@ -0,0 +1,118 @@
/*
* Copyright 2008 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.datamatrix.decoder;
/**
* <p>Encapsulates a block of data within a Data Matrix Code. Data Matrix Codes may split their data into
* multiple blocks, each of which is a unit of data and error-correction codewords. Each
* is represented by an instance of this class.</p>
*
* @author bbrown@google.com (Brian Brown)
*/
final class DataBlock {
private final int numDataCodewords;
private final byte[] codewords;
private DataBlock(int numDataCodewords, byte[] codewords) {
this.numDataCodewords = numDataCodewords;
this.codewords = codewords;
}
/**
* <p>When Data Matrix Codes use multiple data blocks, they actually interleave the bytes of each of them.
* That is, the first byte of data block 1 to n is written, then the second bytes, and so on. This
* method will separate the data into original blocks.</p>
*
* @param rawCodewords bytes as read directly from the Data Matrix Code
* @param version version of the Data Matrix Code
* @return {@link DataBlock}s containing original bytes, "de-interleaved" from representation in the
* Data Matrix Code
*/
static DataBlock[] getDataBlocks(byte[] rawCodewords,
Version version) {
// Figure out the number and size of data blocks used by this version
Version.ECBlocks ecBlocks = version.getECBlocks();
// First count the total number of data blocks
int totalBlocks = 0;
Version.ECB[] ecBlockArray = ecBlocks.getECBlocks();
for (int i = 0; i < ecBlockArray.length; i++) {
totalBlocks += ecBlockArray[i].getCount();
}
// Now establish DataBlocks of the appropriate size and number of data codewords
DataBlock[] result = new DataBlock[totalBlocks];
int numResultBlocks = 0;
for (int j = 0; j < ecBlockArray.length; j++) {
Version.ECB ecBlock = ecBlockArray[j];
for (int i = 0; i < ecBlock.getCount(); i++) {
int numDataCodewords = ecBlock.getDataCodewords();
int numBlockCodewords = ecBlocks.getECCodewords() + numDataCodewords;
result[numResultBlocks++] = new DataBlock(numDataCodewords, new byte[numBlockCodewords]);
}
}
// All blocks have the same amount of data, except that the last n
// (where n may be 0) have 1 less byte. Figure out where these start.
// TODO(bbrown): There is only one case where there is a difference for Data Matrix for size 144
int longerBlocksTotalCodewords = result[0].codewords.length;
//int shorterBlocksTotalCodewords = longerBlocksTotalCodewords - 1;
int longerBlocksNumDataCodewords = longerBlocksTotalCodewords - ecBlocks.getECCodewords();
int shorterBlocksNumDataCodewords = longerBlocksNumDataCodewords - 1;
// The last elements of result may be 1 element shorter for 144 matrix
// first fill out as many elements as all of them have minus 1
int rawCodewordsOffset = 0;
for (int i = 0; i < shorterBlocksNumDataCodewords; i++) {
for (int j = 0; j < numResultBlocks; j++) {
result[j].codewords[i] = rawCodewords[rawCodewordsOffset++];
}
}
// Fill out the last data block in the longer ones
boolean specialVersion = version.getVersionNumber() == 24;
int numLongerBlocks = specialVersion ? 8 : numResultBlocks;
for (int j = 0; j < numLongerBlocks; j++) {
result[j].codewords[longerBlocksNumDataCodewords - 1] = rawCodewords[rawCodewordsOffset++];
}
// Now add in error correction blocks
int max = result[0].codewords.length;
for (int i = longerBlocksNumDataCodewords; i < max; i++) {
for (int j = 0; j < numResultBlocks; j++) {
int iOffset = (specialVersion && j > 7) ? i - 1 : i;
result[j].codewords[iOffset] = rawCodewords[rawCodewordsOffset++];
}
}
if (rawCodewordsOffset != rawCodewords.length) {
throw new IllegalArgumentException();
}
return result;
}
int getNumDataCodewords() {
return numDataCodewords;
}
byte[] getCodewords() {
return codewords;
}
}

View File

@ -0,0 +1,462 @@
/*
* Copyright 2008 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.datamatrix.decoder;
import com.google.zxing.FormatException;
import com.google.zxing.common.BitSource;
import com.google.zxing.common.DecoderResult;
import java.io.UnsupportedEncodingException;
import java.util.Vector;
/**
* <p>Data Matrix Codes can encode text as bits in one of several modes, and can use multiple modes
* in one Data Matrix Code. This class decodes the bits back into text.</p>
*
* <p>See ISO 16022:2006, 5.2.1 - 5.2.9.2</p>
*
* @author bbrown@google.com (Brian Brown)
* @author Sean Owen
*/
final class DecodedBitStreamParser {
/**
* See ISO 16022:2006, Annex C Table C.1
* The C40 Basic Character Set (*'s used for placeholders for the shift values)
*/
private static final char[] C40_BASIC_SET_CHARS = {
'*', '*', '*', ' ', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'
};
private static final char[] C40_SHIFT2_SET_CHARS = {
'!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.',
'/', ':', ';', '<', '=', '>', '?', '@', '[', '\\', ']', '^', '_'
};
/**
* See ISO 16022:2006, Annex C Table C.2
* The Text Basic Character Set (*'s used for placeholders for the shift values)
*/
private static final char[] TEXT_BASIC_SET_CHARS = {
'*', '*', '*', ' ', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
};
private static final char[] TEXT_SHIFT3_SET_CHARS = {
'\'', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '{', '|', '}', '~', (char) 127
};
private static final int PAD_ENCODE = 0; // Not really an encoding
private static final int ASCII_ENCODE = 1;
private static final int C40_ENCODE = 2;
private static final int TEXT_ENCODE = 3;
private static final int ANSIX12_ENCODE = 4;
private static final int EDIFACT_ENCODE = 5;
private static final int BASE256_ENCODE = 6;
private DecodedBitStreamParser() {
}
static DecoderResult decode(byte[] bytes) throws FormatException {
BitSource bits = new BitSource(bytes);
StringBuffer result = new StringBuffer(100);
StringBuffer resultTrailer = new StringBuffer(0);
Vector byteSegments = new Vector(1);
int mode = ASCII_ENCODE;
do {
if (mode == ASCII_ENCODE) {
mode = decodeAsciiSegment(bits, result, resultTrailer);
} else {
switch (mode) {
case C40_ENCODE:
decodeC40Segment(bits, result);
break;
case TEXT_ENCODE:
decodeTextSegment(bits, result);
break;
case ANSIX12_ENCODE:
decodeAnsiX12Segment(bits, result);
break;
case EDIFACT_ENCODE:
decodeEdifactSegment(bits, result);
break;
case BASE256_ENCODE:
decodeBase256Segment(bits, result, byteSegments);
break;
default:
throw FormatException.getFormatInstance();
}
mode = ASCII_ENCODE;
}
} while (mode != PAD_ENCODE && bits.available() > 0);
if (resultTrailer.length() > 0) {
result.append(resultTrailer.toString());
}
return new DecoderResult(bytes, result.toString(), byteSegments.isEmpty() ? null : byteSegments, null);
}
/**
* See ISO 16022:2006, 5.2.3 and Annex C, Table C.2
*/
private static int decodeAsciiSegment(BitSource bits, StringBuffer result, StringBuffer resultTrailer)
throws FormatException {
boolean upperShift = false;
do {
int oneByte = bits.readBits(8);
if (oneByte == 0) {
throw FormatException.getFormatInstance();
} else if (oneByte <= 128) { // ASCII data (ASCII value + 1)
oneByte = upperShift ? (oneByte + 128) : oneByte;
upperShift = false;
result.append((char) (oneByte - 1));
return ASCII_ENCODE;
} else if (oneByte == 129) { // Pad
return PAD_ENCODE;
} else if (oneByte <= 229) { // 2-digit data 00-99 (Numeric Value + 130)
int value = oneByte - 130;
if (value < 10) { // padd with '0' for single digit values
result.append('0');
}
result.append(value);
} else if (oneByte == 230) { // Latch to C40 encodation
return C40_ENCODE;
} else if (oneByte == 231) { // Latch to Base 256 encodation
return BASE256_ENCODE;
} else if (oneByte == 232) { // FNC1
//throw ReaderException.getInstance();
// Ignore this symbol for now
} else if (oneByte == 233) { // Structured Append
//throw ReaderException.getInstance();
// Ignore this symbol for now
} else if (oneByte == 234) { // Reader Programming
//throw ReaderException.getInstance();
// Ignore this symbol for now
} else if (oneByte == 235) { // Upper Shift (shift to Extended ASCII)
upperShift = true;
} else if (oneByte == 236) { // 05 Macro
result.append("[)>\u001E05\u001D");
resultTrailer.insert(0, "\u001E\u0004");
} else if (oneByte == 237) { // 06 Macro
result.append("[)>\u001E06\u001D");
resultTrailer.insert(0, "\u001E\u0004");
} else if (oneByte == 238) { // Latch to ANSI X12 encodation
return ANSIX12_ENCODE;
} else if (oneByte == 239) { // Latch to Text encodation
return TEXT_ENCODE;
} else if (oneByte == 240) { // Latch to EDIFACT encodation
return EDIFACT_ENCODE;
} else if (oneByte == 241) { // ECI Character
// TODO(bbrown): I think we need to support ECI
//throw ReaderException.getInstance();
// Ignore this symbol for now
} else if (oneByte >= 242) { // Not to be used in ASCII encodation
throw FormatException.getFormatInstance();
}
} while (bits.available() > 0);
return ASCII_ENCODE;
}
/**
* See ISO 16022:2006, 5.2.5 and Annex C, Table C.1
*/
private static void decodeC40Segment(BitSource bits, StringBuffer result) throws FormatException {
// Three C40 values are encoded in a 16-bit value as
// (1600 * C1) + (40 * C2) + C3 + 1
// TODO(bbrown): The Upper Shift with C40 doesn't work in the 4 value scenario all the time
boolean upperShift = false;
int[] cValues = new int[3];
do {
// If there is only one byte left then it will be encoded as ASCII
if (bits.available() == 8) {
return;
}
int firstByte = bits.readBits(8);
if (firstByte == 254) { // Unlatch codeword
return;
}
parseTwoBytes(firstByte, bits.readBits(8), cValues);
int shift = 0;
for (int i = 0; i < 3; i++) {
int cValue = cValues[i];
switch (shift) {
case 0:
if (cValue < 3) {
shift = cValue + 1;
} else {
if (upperShift) {
result.append((char) (C40_BASIC_SET_CHARS[cValue] + 128));
upperShift = false;
} else {
result.append(C40_BASIC_SET_CHARS[cValue]);
}
}
break;
case 1:
if (upperShift) {
result.append((char) (cValue + 128));
upperShift = false;
} else {
result.append(cValue);
}
shift = 0;
break;
case 2:
if (cValue < 27) {
if (upperShift) {
result.append((char) (C40_SHIFT2_SET_CHARS[cValue] + 128));
upperShift = false;
} else {
result.append(C40_SHIFT2_SET_CHARS[cValue]);
}
} else if (cValue == 27) { // FNC1
throw FormatException.getFormatInstance();
} else if (cValue == 30) { // Upper Shift
upperShift = true;
} else {
throw FormatException.getFormatInstance();
}
shift = 0;
break;
case 3:
if (upperShift) {
result.append((char) (cValue + 224));
upperShift = false;
} else {
result.append((char) (cValue + 96));
}
shift = 0;
break;
default:
throw FormatException.getFormatInstance();
}
}
} while (bits.available() > 0);
}
/**
* See ISO 16022:2006, 5.2.6 and Annex C, Table C.2
*/
private static void decodeTextSegment(BitSource bits, StringBuffer result) throws FormatException {
// Three Text values are encoded in a 16-bit value as
// (1600 * C1) + (40 * C2) + C3 + 1
// TODO(bbrown): The Upper Shift with Text doesn't work in the 4 value scenario all the time
boolean upperShift = false;
int[] cValues = new int[3];
do {
// If there is only one byte left then it will be encoded as ASCII
if (bits.available() == 8) {
return;
}
int firstByte = bits.readBits(8);
if (firstByte == 254) { // Unlatch codeword
return;
}
parseTwoBytes(firstByte, bits.readBits(8), cValues);
int shift = 0;
for (int i = 0; i < 3; i++) {
int cValue = cValues[i];
switch (shift) {
case 0:
if (cValue < 3) {
shift = cValue + 1;
} else {
if (upperShift) {
result.append((char) (TEXT_BASIC_SET_CHARS[cValue] + 128));
upperShift = false;
} else {
result.append(TEXT_BASIC_SET_CHARS[cValue]);
}
}
break;
case 1:
if (upperShift) {
result.append((char) (cValue + 128));
upperShift = false;
} else {
result.append(cValue);
}
shift = 0;
break;
case 2:
// Shift 2 for Text is the same encoding as C40
if (cValue < 27) {
if (upperShift) {
result.append((char) (C40_SHIFT2_SET_CHARS[cValue] + 128));
upperShift = false;
} else {
result.append(C40_SHIFT2_SET_CHARS[cValue]);
}
} else if (cValue == 27) { // FNC1
throw FormatException.getFormatInstance();
} else if (cValue == 30) { // Upper Shift
upperShift = true;
} else {
throw FormatException.getFormatInstance();
}
shift = 0;
break;
case 3:
if (upperShift) {
result.append((char) (TEXT_SHIFT3_SET_CHARS[cValue] + 128));
upperShift = false;
} else {
result.append(TEXT_SHIFT3_SET_CHARS[cValue]);
}
shift = 0;
break;
default:
throw FormatException.getFormatInstance();
}
}
} while (bits.available() > 0);
}
/**
* See ISO 16022:2006, 5.2.7
*/
private static void decodeAnsiX12Segment(BitSource bits, StringBuffer result) throws FormatException {
// Three ANSI X12 values are encoded in a 16-bit value as
// (1600 * C1) + (40 * C2) + C3 + 1
int[] cValues = new int[3];
do {
// If there is only one byte left then it will be encoded as ASCII
if (bits.available() == 8) {
return;
}
int firstByte = bits.readBits(8);
if (firstByte == 254) { // Unlatch codeword
return;
}
parseTwoBytes(firstByte, bits.readBits(8), cValues);
for (int i = 0; i < 3; i++) {
int cValue = cValues[i];
if (cValue == 0) { // X12 segment terminator <CR>
result.append('\r');
} else if (cValue == 1) { // X12 segment separator *
result.append('*');
} else if (cValue == 2) { // X12 sub-element separator >
result.append('>');
} else if (cValue == 3) { // space
result.append(' ');
} else if (cValue < 14) { // 0 - 9
result.append((char) (cValue + 44));
} else if (cValue < 40) { // A - Z
result.append((char) (cValue + 51));
} else {
throw FormatException.getFormatInstance();
}
}
} while (bits.available() > 0);
}
private static void parseTwoBytes(int firstByte, int secondByte, int[] result) {
int fullBitValue = (firstByte << 8) + secondByte - 1;
int temp = fullBitValue / 1600;
result[0] = temp;
fullBitValue -= temp * 1600;
temp = fullBitValue / 40;
result[1] = temp;
result[2] = fullBitValue - temp * 40;
}
/**
* See ISO 16022:2006, 5.2.8 and Annex C Table C.3
*/
private static void decodeEdifactSegment(BitSource bits, StringBuffer result) {
boolean unlatch = false;
do {
// If there is only two or less bytes left then it will be encoded as ASCII
if (bits.available() <= 16) {
return;
}
for (int i = 0; i < 4; i++) {
int edifactValue = bits.readBits(6);
// Check for the unlatch character
if (edifactValue == 0x2B67) { // 011111
unlatch = true;
// If we encounter the unlatch code then continue reading because the Codeword triple
// is padded with 0's
}
if (!unlatch) {
if ((edifactValue & 32) == 0) { // no 1 in the leading (6th) bit
edifactValue |= 64; // Add a leading 01 to the 6 bit binary value
}
result.append(edifactValue);
}
}
} while (!unlatch && bits.available() > 0);
}
/**
* See ISO 16022:2006, 5.2.9 and Annex B, B.2
*/
private static void decodeBase256Segment(BitSource bits, StringBuffer result, Vector byteSegments)
throws FormatException {
// Figure out how long the Base 256 Segment is.
int d1 = bits.readBits(8);
int count;
if (d1 == 0) { // Read the remainder of the symbol
count = bits.available() / 8;
} else if (d1 < 250) {
count = d1;
} else {
count = 250 * (d1 - 249) + bits.readBits(8);
}
byte[] bytes = new byte[count];
for (int i = 0; i < count; i++) {
// Have seen this particular error in the wild, such as at
// http://www.bcgen.com/demo/IDAutomationStreamingDataMatrix.aspx?MODE=3&D=Fred&PFMT=3&PT=F&X=0.3&O=0&LM=0.2
if (bits.available() < 8) {
throw FormatException.getFormatInstance();
}
bytes[i] = unrandomize255State(bits.readBits(8), i);
}
byteSegments.addElement(bytes);
try {
result.append(new String(bytes, "ISO8859_1"));
} catch (UnsupportedEncodingException uee) {
throw new RuntimeException("Platform does not support required encoding: " + uee);
}
}
/**
* See ISO 16022:2006, Annex B, B.2
*/
private static byte unrandomize255State(int randomizedBase256Codeword,
int base256CodewordPosition) {
int pseudoRandomNumber = ((149 * base256CodewordPosition) % 255) + 1;
int tempVariable = randomizedBase256Codeword - pseudoRandomNumber;
return (byte) (tempVariable >= 0 ? tempVariable : (tempVariable + 256));
}
}

View File

@ -0,0 +1,134 @@
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.datamatrix.decoder;
import com.google.zxing.ChecksumException;
import com.google.zxing.FormatException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.DecoderResult;
import com.google.zxing.common.reedsolomon.GF256;
import com.google.zxing.common.reedsolomon.ReedSolomonDecoder;
import com.google.zxing.common.reedsolomon.ReedSolomonException;
/**
* <p>The main class which implements Data Matrix Code decoding -- as opposed to locating and extracting
* the Data Matrix Code from an image.</p>
*
* @author bbrown@google.com (Brian Brown)
*/
public final class Decoder {
private final ReedSolomonDecoder rsDecoder;
public Decoder() {
rsDecoder = new ReedSolomonDecoder(GF256.DATA_MATRIX_FIELD);
}
/**
* <p>Convenience method that can decode a Data Matrix Code represented as a 2D array of booleans.
* "true" is taken to mean a black module.</p>
*
* @param image booleans representing white/black Data Matrix Code modules
* @return text and bytes encoded within the Data Matrix Code
* @throws FormatException if the Data Matrix Code cannot be decoded
* @throws ChecksumException if error correction fails
*/
public DecoderResult decode(boolean[][] image) throws FormatException, ChecksumException {
int dimension = image.length;
BitMatrix bits = new BitMatrix(dimension);
for (int i = 0; i < dimension; i++) {
for (int j = 0; j < dimension; j++) {
if (image[i][j]) {
bits.set(j, i);
}
}
}
return decode(bits);
}
/**
* <p>Decodes a Data Matrix Code represented as a {@link BitMatrix}. A 1 or "true" is taken
* to mean a black module.</p>
*
* @param bits booleans representing white/black Data Matrix Code modules
* @return text and bytes encoded within the Data Matrix Code
* @throws FormatException if the Data Matrix Code cannot be decoded
* @throws ChecksumException if error correction fails
*/
public DecoderResult decode(BitMatrix bits) throws FormatException, ChecksumException {
// Construct a parser and read version, error-correction level
BitMatrixParser parser = new BitMatrixParser(bits);
Version version = parser.readVersion(bits);
// Read codewords
byte[] codewords = parser.readCodewords();
// Separate into data blocks
DataBlock[] dataBlocks = DataBlock.getDataBlocks(codewords, version);
// Count total number of data bytes
int totalBytes = 0;
for (int i = 0; i < dataBlocks.length; i++) {
totalBytes += dataBlocks[i].getNumDataCodewords();
}
byte[] resultBytes = new byte[totalBytes];
int resultOffset = 0;
// Error-correct and copy data blocks together into a stream of bytes
for (int j = 0; j < dataBlocks.length; j++) {
DataBlock dataBlock = dataBlocks[j];
byte[] codewordBytes = dataBlock.getCodewords();
int numDataCodewords = dataBlock.getNumDataCodewords();
correctErrors(codewordBytes, numDataCodewords);
for (int i = 0; i < numDataCodewords; i++) {
resultBytes[resultOffset++] = codewordBytes[i];
}
}
// Decode the contents of that stream of bytes
return DecodedBitStreamParser.decode(resultBytes);
}
/**
* <p>Given data and error-correction codewords received, possibly corrupted by errors, attempts to
* correct the errors in-place using Reed-Solomon error correction.</p>
*
* @param codewordBytes data and error correction codewords
* @param numDataCodewords number of codewords that are data bytes
* @throws ChecksumException if error correction fails
*/
private void correctErrors(byte[] codewordBytes, int numDataCodewords) throws ChecksumException {
int numCodewords = codewordBytes.length;
// First read into an array of ints
int[] codewordsInts = new int[numCodewords];
for (int i = 0; i < numCodewords; i++) {
codewordsInts[i] = codewordBytes[i] & 0xFF;
}
int numECCodewords = codewordBytes.length - numDataCodewords;
try {
rsDecoder.decode(codewordsInts, numECCodewords);
} catch (ReedSolomonException rse) {
throw ChecksumException.getChecksumInstance();
}
// Copy back into array of bytes -- only need to worry about the bytes that were data
// We don't care about errors in the error-correction codewords
for (int i = 0; i < numDataCodewords; i++) {
codewordBytes[i] = (byte) codewordsInts[i];
}
}
}

View File

@ -0,0 +1,242 @@
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.datamatrix.decoder;
import com.google.zxing.FormatException;
/**
* The Version object encapsulates attributes about a particular
* size Data Matrix Code.
*
* @author bbrown@google.com (Brian Brown)
*/
public final class Version {
private static final Version[] VERSIONS = buildVersions();
private final int versionNumber;
private final int symbolSizeRows;
private final int symbolSizeColumns;
private final int dataRegionSizeRows;
private final int dataRegionSizeColumns;
private final ECBlocks ecBlocks;
private final int totalCodewords;
private Version(int versionNumber,
int symbolSizeRows,
int symbolSizeColumns,
int dataRegionSizeRows,
int dataRegionSizeColumns,
ECBlocks ecBlocks) {
this.versionNumber = versionNumber;
this.symbolSizeRows = symbolSizeRows;
this.symbolSizeColumns = symbolSizeColumns;
this.dataRegionSizeRows = dataRegionSizeRows;
this.dataRegionSizeColumns = dataRegionSizeColumns;
this.ecBlocks = ecBlocks;
// Calculate the total number of codewords
int total = 0;
int ecCodewords = ecBlocks.getECCodewords();
ECB[] ecbArray = ecBlocks.getECBlocks();
for (int i = 0; i < ecbArray.length; i++) {
ECB ecBlock = ecbArray[i];
total += ecBlock.getCount() * (ecBlock.getDataCodewords() + ecCodewords);
}
this.totalCodewords = total;
}
public int getVersionNumber() {
return versionNumber;
}
public int getSymbolSizeRows() {
return symbolSizeRows;
}
public int getSymbolSizeColumns() {
return symbolSizeColumns;
}
public int getDataRegionSizeRows() {
return dataRegionSizeRows;
}
public int getDataRegionSizeColumns() {
return dataRegionSizeColumns;
}
public int getTotalCodewords() {
return totalCodewords;
}
ECBlocks getECBlocks() {
return ecBlocks;
}
/**
* <p>Deduces version information from Data Matrix dimensions.</p>
*
* @param numRows Number of rows in modules
* @param numColumns Number of columns in modules
* @return {@link Version} for a Data Matrix Code of those dimensions
* @throws FormatException if dimensions do correspond to a valid Data Matrix size
*/
public static Version getVersionForDimensions(int numRows, int numColumns) throws FormatException {
if ((numRows & 0x01) != 0 || (numColumns & 0x01) != 0) {
throw FormatException.getFormatInstance();
}
// TODO(bbrown): This is doing a linear search through the array of versions.
// If we interleave the rectangular versions with the square versions we could
// do a binary search.
int numVersions = VERSIONS.length;
for (int i = 0; i < numVersions; ++i){
Version version = VERSIONS[i];
if (version.symbolSizeRows == numRows && version.symbolSizeColumns == numColumns) {
return version;
}
}
throw FormatException.getFormatInstance();
}
/**
* <p>Encapsulates a set of error-correction blocks in one symbol version. Most versions will
* use blocks of differing sizes within one version, so, this encapsulates the parameters for
* each set of blocks. It also holds the number of error-correction codewords per block since it
* will be the same across all blocks within one version.</p>
*/
static final class ECBlocks {
private final int ecCodewords;
private final ECB[] ecBlocks;
private ECBlocks(int ecCodewords, ECB ecBlocks) {
this.ecCodewords = ecCodewords;
this.ecBlocks = new ECB[] { ecBlocks };
}
private ECBlocks(int ecCodewords, ECB ecBlocks1, ECB ecBlocks2) {
this.ecCodewords = ecCodewords;
this.ecBlocks = new ECB[] { ecBlocks1, ecBlocks2 };
}
int getECCodewords() {
return ecCodewords;
}
ECB[] getECBlocks() {
return ecBlocks;
}
}
/**
* <p>Encapsualtes the parameters for one error-correction block in one symbol version.
* This includes the number of data codewords, and the number of times a block with these
* parameters is used consecutively in the Data Matrix code version's format.</p>
*/
static final class ECB {
private final int count;
private final int dataCodewords;
private ECB(int count, int dataCodewords) {
this.count = count;
this.dataCodewords = dataCodewords;
}
int getCount() {
return count;
}
int getDataCodewords() {
return dataCodewords;
}
}
public String toString() {
return String.valueOf(versionNumber);
}
/**
* See ISO 16022:2006 5.5.1 Table 7
*/
private static Version[] buildVersions() {
return new Version[]{
new Version(1, 10, 10, 8, 8,
new ECBlocks(5, new ECB(1, 3))),
new Version(2, 12, 12, 10, 10,
new ECBlocks(7, new ECB(1, 5))),
new Version(3, 14, 14, 12, 12,
new ECBlocks(10, new ECB(1, 8))),
new Version(4, 16, 16, 14, 14,
new ECBlocks(12, new ECB(1, 12))),
new Version(5, 18, 18, 16, 16,
new ECBlocks(14, new ECB(1, 18))),
new Version(6, 20, 20, 18, 18,
new ECBlocks(18, new ECB(1, 22))),
new Version(7, 22, 22, 20, 20,
new ECBlocks(20, new ECB(1, 30))),
new Version(8, 24, 24, 22, 22,
new ECBlocks(24, new ECB(1, 36))),
new Version(9, 26, 26, 24, 24,
new ECBlocks(28, new ECB(1, 44))),
new Version(10, 32, 32, 14, 14,
new ECBlocks(36, new ECB(1, 62))),
new Version(11, 36, 36, 16, 16,
new ECBlocks(42, new ECB(1, 86))),
new Version(12, 40, 40, 18, 18,
new ECBlocks(48, new ECB(1, 114))),
new Version(13, 44, 44, 20, 20,
new ECBlocks(56, new ECB(1, 144))),
new Version(14, 48, 48, 22, 22,
new ECBlocks(68, new ECB(1, 174))),
new Version(15, 52, 52, 24, 24,
new ECBlocks(42, new ECB(2, 102))),
new Version(16, 64, 64, 14, 14,
new ECBlocks(56, new ECB(2, 140))),
new Version(17, 72, 72, 16, 16,
new ECBlocks(36, new ECB(4, 92))),
new Version(18, 80, 80, 18, 18,
new ECBlocks(48, new ECB(4, 114))),
new Version(19, 88, 88, 20, 20,
new ECBlocks(56, new ECB(4, 144))),
new Version(20, 96, 96, 22, 22,
new ECBlocks(68, new ECB(4, 174))),
new Version(21, 104, 104, 24, 24,
new ECBlocks(56, new ECB(6, 136))),
new Version(22, 120, 120, 18, 18,
new ECBlocks(68, new ECB(6, 175))),
new Version(23, 132, 132, 20, 20,
new ECBlocks(62, new ECB(8, 163))),
new Version(24, 144, 144, 22, 22,
new ECBlocks(62, new ECB(8, 156), new ECB(2, 155))),
new Version(25, 8, 18, 6, 16,
new ECBlocks(7, new ECB(1, 5))),
new Version(26, 8, 32, 6, 14,
new ECBlocks(11, new ECB(1, 10))),
new Version(27, 12, 26, 10, 24,
new ECBlocks(14, new ECB(1, 16))),
new Version(28, 12, 36, 10, 16,
new ECBlocks(18, new ECB(1, 22))),
new Version(29, 16, 36, 10, 16,
new ECBlocks(24, new ECB(1, 32))),
new Version(30, 16, 48, 14, 22,
new ECBlocks(28, new ECB(1, 49)))
};
}
}

View File

@ -0,0 +1,348 @@
/*
* Copyright 2008 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.datamatrix.detector;
import com.google.zxing.NotFoundException;
import com.google.zxing.ResultPoint;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.Collections;
import com.google.zxing.common.Comparator;
import com.google.zxing.common.DetectorResult;
import com.google.zxing.common.GridSampler;
import com.google.zxing.common.detector.WhiteRectangleDetector;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
/**
* <p>Encapsulates logic that can detect a Data Matrix Code in an image, even if the Data Matrix Code
* is rotated or skewed, or partially obscured.</p>
*
* @author Sean Owen
*/
public final class Detector {
// Trick to avoid creating new Integer objects below -- a sort of crude copy of
// the Integer.valueOf(int) optimization added in Java 5, not in J2ME
private static final Integer[] INTEGERS =
{ new Integer(0), new Integer(1), new Integer(2), new Integer(3), new Integer(4) };
// No, can't use valueOf()
private final BitMatrix image;
private final WhiteRectangleDetector rectangleDetector;
public Detector(BitMatrix image) {
this.image = image;
rectangleDetector = new WhiteRectangleDetector(image);
}
/**
* <p>Detects a Data Matrix Code in an image.</p>
*
* @return {@link DetectorResult} encapsulating results of detecting a Data Matrix Code
* @throws NotFoundException if no Data Matrix Code can be found
*/
public DetectorResult detect() throws NotFoundException {
ResultPoint[] cornerPoints = rectangleDetector.detect();
ResultPoint pointA = cornerPoints[0];
ResultPoint pointB = cornerPoints[1];
ResultPoint pointC = cornerPoints[2];
ResultPoint pointD = cornerPoints[3];
// Point A and D are across the diagonal from one another,
// as are B and C. Figure out which are the solid black lines
// by counting transitions
Vector transitions = new Vector(4);
transitions.addElement(transitionsBetween(pointA, pointB));
transitions.addElement(transitionsBetween(pointA, pointC));
transitions.addElement(transitionsBetween(pointB, pointD));
transitions.addElement(transitionsBetween(pointC, pointD));
Collections.insertionSort(transitions, new ResultPointsAndTransitionsComparator());
// Sort by number of transitions. First two will be the two solid sides; last two
// will be the two alternating black/white sides
ResultPointsAndTransitions lSideOne = (ResultPointsAndTransitions) transitions.elementAt(0);
ResultPointsAndTransitions lSideTwo = (ResultPointsAndTransitions) transitions.elementAt(1);
// Figure out which point is their intersection by tallying up the number of times we see the
// endpoints in the four endpoints. One will show up twice.
Hashtable pointCount = new Hashtable();
increment(pointCount, lSideOne.getFrom());
increment(pointCount, lSideOne.getTo());
increment(pointCount, lSideTwo.getFrom());
increment(pointCount, lSideTwo.getTo());
ResultPoint maybeTopLeft = null;
ResultPoint bottomLeft = null;
ResultPoint maybeBottomRight = null;
Enumeration points = pointCount.keys();
while (points.hasMoreElements()) {
ResultPoint point = (ResultPoint) points.nextElement();
Integer value = (Integer) pointCount.get(point);
if (value.intValue() == 2) {
bottomLeft = point; // this is definitely the bottom left, then -- end of two L sides
} else {
// Otherwise it's either top left or bottom right -- just assign the two arbitrarily now
if (maybeTopLeft == null) {
maybeTopLeft = point;
} else {
maybeBottomRight = point;
}
}
}
if (maybeTopLeft == null || bottomLeft == null || maybeBottomRight == null) {
throw NotFoundException.getNotFoundInstance();
}
// Bottom left is correct but top left and bottom right might be switched
ResultPoint[] corners = { maybeTopLeft, bottomLeft, maybeBottomRight };
// Use the dot product trick to sort them out
ResultPoint.orderBestPatterns(corners);
// Now we know which is which:
ResultPoint bottomRight = corners[0];
bottomLeft = corners[1];
ResultPoint topLeft = corners[2];
// Which point didn't we find in relation to the "L" sides? that's the top right corner
ResultPoint topRight;
if (!pointCount.containsKey(pointA)) {
topRight = pointA;
} else if (!pointCount.containsKey(pointB)) {
topRight = pointB;
} else if (!pointCount.containsKey(pointC)) {
topRight = pointC;
} else {
topRight = pointD;
}
// Next determine the dimension by tracing along the top or right side and counting black/white
// transitions. Since we start inside a black module, we should see a number of transitions
// equal to 1 less than the code dimension. Well, actually 2 less, because we are going to
// end on a black module:
// The top right point is actually the corner of a module, which is one of the two black modules
// adjacent to the white module at the top right. Tracing to that corner from either the top left
// or bottom right should work here.
int dimension = Math.min(transitionsBetween(topLeft, topRight).getTransitions(),
transitionsBetween(bottomRight, topRight).getTransitions());
if ((dimension & 0x01) == 1) {
// it can't be odd, so, round... up?
dimension++;
}
dimension += 2;
//correct top right point to match the white module
ResultPoint correctedTopRight = correctTopRight(bottomLeft, bottomRight, topLeft, topRight, dimension);
if (correctedTopRight == null){
correctedTopRight = topRight;
}
//We redetermine the dimension using the corrected top right point
int dimension2 = Math.max(transitionsBetween(topLeft, correctedTopRight).getTransitions(),
transitionsBetween(bottomRight, correctedTopRight).getTransitions());
dimension2++;
if ((dimension2 & 0x01) == 1) {
dimension2++;
}
BitMatrix bits = sampleGrid(image, topLeft, bottomLeft, bottomRight, correctedTopRight, dimension2);
return new DetectorResult(bits, new ResultPoint[]{topLeft, bottomLeft, bottomRight, correctedTopRight});
}
/**
* Calculates the position of the white top right module using the output of the rectangle detector
*/
private ResultPoint correctTopRight(ResultPoint bottomLeft,
ResultPoint bottomRight,
ResultPoint topLeft,
ResultPoint topRight,
int dimension) {
float corr = distance(bottomLeft, bottomRight) / (float)dimension;
int norm = distance(topLeft, topRight);
float cos = (topRight.getX() - topLeft.getX()) / norm;
float sin = (topRight.getY() - topLeft.getY()) / norm;
ResultPoint c1 = new ResultPoint(topRight.getX()+corr*cos, topRight.getY()+corr*sin);
corr = distance(bottomLeft, bottomRight) / (float)dimension;
norm = distance(bottomRight, topRight);
cos = (topRight.getX() - bottomRight.getX()) / norm;
sin = (topRight.getY() - bottomRight.getY()) / norm;
ResultPoint c2 = new ResultPoint(topRight.getX()+corr*cos, topRight.getY()+corr*sin);
if (!isValid(c1)){
if (isValid(c2)){
return c2;
}
return null;
} else if (!isValid(c2)){
return c1;
}
int l1 = Math.abs(transitionsBetween(topLeft, c1).getTransitions() - transitionsBetween(bottomRight, c1).getTransitions());
int l2 = Math.abs(transitionsBetween(topLeft, c2).getTransitions() - transitionsBetween(bottomRight, c2).getTransitions());
if (l1 <= l2){
return c1;
}
return c2;
}
private boolean isValid(ResultPoint p) {
return (p.getX() >= 0 && p.getX() < image.width && p.getY() > 0 && p.getY() < image.height);
}
/**
* Ends up being a bit faster than Math.round(). This merely rounds its
* argument to the nearest int, where x.5 rounds up.
*/
private static int round(float d) {
return (int) (d + 0.5f);
}
// L2 distance
private static int distance(ResultPoint a, ResultPoint b) {
return round((float) Math.sqrt((a.getX() - b.getX())
* (a.getX() - b.getX()) + (a.getY() - b.getY())
* (a.getY() - b.getY())));
}
/**
* Increments the Integer associated with a key by one.
*/
private static void increment(Hashtable table, ResultPoint key) {
Integer value = (Integer) table.get(key);
table.put(key, value == null ? INTEGERS[1] : INTEGERS[value.intValue() + 1]);
}
private static BitMatrix sampleGrid(BitMatrix image,
ResultPoint topLeft,
ResultPoint bottomLeft,
ResultPoint bottomRight,
ResultPoint topRight,
int dimension) throws NotFoundException {
GridSampler sampler = GridSampler.getInstance();
return sampler.sampleGrid(image,
dimension,
0.5f,
0.5f,
dimension - 0.5f,
0.5f,
dimension - 0.5f,
dimension - 0.5f,
0.5f,
dimension - 0.5f,
topLeft.getX(),
topLeft.getY(),
topRight.getX(),
topRight.getY(),
bottomRight.getX(),
bottomRight.getY(),
bottomLeft.getX(),
bottomLeft.getY());
}
/**
* Counts the number of black/white transitions between two points, using something like Bresenham's algorithm.
*/
private ResultPointsAndTransitions transitionsBetween(ResultPoint from, ResultPoint to) {
// See QR Code Detector, sizeOfBlackWhiteBlackRun()
int fromX = (int) from.getX();
int fromY = (int) from.getY();
int toX = (int) to.getX();
int toY = (int) to.getY();
boolean steep = Math.abs(toY - fromY) > Math.abs(toX - fromX);
if (steep) {
int temp = fromX;
fromX = fromY;
fromY = temp;
temp = toX;
toX = toY;
toY = temp;
}
int dx = Math.abs(toX - fromX);
int dy = Math.abs(toY - fromY);
int error = -dx >> 1;
int ystep = fromY < toY ? 1 : -1;
int xstep = fromX < toX ? 1 : -1;
int transitions = 0;
boolean inBlack = image.get(steep ? fromY : fromX, steep ? fromX : fromY);
for (int x = fromX, y = fromY; x != toX; x += xstep) {
boolean isBlack = image.get(steep ? y : x, steep ? x : y);
if (isBlack != inBlack) {
transitions++;
inBlack = isBlack;
}
error += dy;
if (error > 0) {
if (y == toY) {
break;
}
y += ystep;
error -= dx;
}
}
return new ResultPointsAndTransitions(from, to, transitions);
}
/**
* Simply encapsulates two points and a number of transitions between them.
*/
private static class ResultPointsAndTransitions {
private final ResultPoint from;
private final ResultPoint to;
private final int transitions;
private ResultPointsAndTransitions(ResultPoint from, ResultPoint to, int transitions) {
this.from = from;
this.to = to;
this.transitions = transitions;
}
public ResultPoint getFrom() {
return from;
}
public ResultPoint getTo() {
return to;
}
public int getTransitions() {
return transitions;
}
public String toString() {
return from + "/" + to + '/' + transitions;
}
}
/**
* Orders ResultPointsAndTransitions by number of transitions, ascending.
*/
private static class ResultPointsAndTransitionsComparator implements Comparator {
public int compare(Object o1, Object o2) {
return ((ResultPointsAndTransitions) o1).getTransitions() - ((ResultPointsAndTransitions) o2).getTransitions();
}
}
}

View File

@ -0,0 +1,96 @@
/*
* Copyright 2009 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.multi;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.ChecksumException;
import com.google.zxing.FormatException;
import com.google.zxing.NotFoundException;
import com.google.zxing.Reader;
import com.google.zxing.Result;
import java.util.Hashtable;
/**
* This class attempts to decode a barcode from an image, not by scanning the whole image,
* but by scanning subsets of the image. This is important when there may be multiple barcodes in
* an image, and detecting a barcode may find parts of multiple barcode and fail to decode
* (e.g. QR Codes). Instead this scans the four quadrants of the image -- and also the center
* 'quadrant' to cover the case where a barcode is found in the center.
*
* @see GenericMultipleBarcodeReader
*/
public final class ByQuadrantReader implements Reader {
private final Reader delegate;
public ByQuadrantReader(Reader delegate) {
this.delegate = delegate;
}
public Result decode(BinaryBitmap image)
throws NotFoundException, ChecksumException, FormatException {
return decode(image, null);
}
public Result decode(BinaryBitmap image, Hashtable hints)
throws NotFoundException, ChecksumException, FormatException {
int width = image.getWidth();
int height = image.getHeight();
int halfWidth = width / 2;
int halfHeight = height / 2;
BinaryBitmap topLeft = image.crop(0, 0, halfWidth, halfHeight);
try {
return delegate.decode(topLeft, hints);
} catch (NotFoundException re) {
// continue
}
BinaryBitmap topRight = image.crop(halfWidth, 0, halfWidth, halfHeight);
try {
return delegate.decode(topRight, hints);
} catch (NotFoundException re) {
// continue
}
BinaryBitmap bottomLeft = image.crop(0, halfHeight, halfWidth, halfHeight);
try {
return delegate.decode(bottomLeft, hints);
} catch (NotFoundException re) {
// continue
}
BinaryBitmap bottomRight = image.crop(halfWidth, halfHeight, halfWidth, halfHeight);
try {
return delegate.decode(bottomRight, hints);
} catch (NotFoundException re) {
// continue
}
int quarterWidth = halfWidth / 2;
int quarterHeight = halfHeight / 2;
BinaryBitmap center = image.crop(quarterWidth, quarterHeight, halfWidth, halfHeight);
return delegate.decode(center, hints);
}
public void reset() {
delegate.reset();
}
}

View File

@ -0,0 +1,156 @@
/*
* Copyright 2009 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.multi;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.NotFoundException;
import com.google.zxing.Reader;
import com.google.zxing.ReaderException;
import com.google.zxing.Result;
import com.google.zxing.ResultPoint;
import java.util.Hashtable;
import java.util.Vector;
/**
* <p>Attempts to locate multiple barcodes in an image by repeatedly decoding portion of the image.
* After one barcode is found, the areas left, above, right and below the barcode's
* {@link com.google.zxing.ResultPoint}s are scanned, recursively.</p>
*
* <p>A caller may want to also employ {@link ByQuadrantReader} when attempting to find multiple
* 2D barcodes, like QR Codes, in an image, where the presence of multiple barcodes might prevent
* detecting any one of them.</p>
*
* <p>That is, instead of passing a {@link Reader} a caller might pass
* <code>new ByQuadrantReader(reader)</code>.</p>
*
* @author Sean Owen
*/
public final class GenericMultipleBarcodeReader implements MultipleBarcodeReader {
private static final int MIN_DIMENSION_TO_RECUR = 100;
private final Reader delegate;
public GenericMultipleBarcodeReader(Reader delegate) {
this.delegate = delegate;
}
public Result[] decodeMultiple(BinaryBitmap image) throws NotFoundException {
return decodeMultiple(image, null);
}
public Result[] decodeMultiple(BinaryBitmap image, Hashtable hints)
throws NotFoundException {
Vector results = new Vector();
doDecodeMultiple(image, hints, results, 0, 0);
if (results.isEmpty()) {
throw NotFoundException.getNotFoundInstance();
}
int numResults = results.size();
Result[] resultArray = new Result[numResults];
for (int i = 0; i < numResults; i++) {
resultArray[i] = (Result) results.elementAt(i);
}
return resultArray;
}
private void doDecodeMultiple(BinaryBitmap image,
Hashtable hints,
Vector results,
int xOffset,
int yOffset) {
Result result;
try {
result = delegate.decode(image, hints);
} catch (ReaderException re) {
return;
}
boolean alreadyFound = false;
for (int i = 0; i < results.size(); i++) {
Result existingResult = (Result) results.elementAt(i);
if (existingResult.getText().equals(result.getText())) {
alreadyFound = true;
break;
}
}
if (alreadyFound) {
return;
}
results.addElement(translateResultPoints(result, xOffset, yOffset));
ResultPoint[] resultPoints = result.getResultPoints();
if (resultPoints == null || resultPoints.length == 0) {
return;
}
int width = image.getWidth();
int height = image.getHeight();
float minX = width;
float minY = height;
float maxX = 0.0f;
float maxY = 0.0f;
for (int i = 0; i < resultPoints.length; i++) {
ResultPoint point = resultPoints[i];
float x = point.getX();
float y = point.getY();
if (x < minX) {
minX = x;
}
if (y < minY) {
minY = y;
}
if (x > maxX) {
maxX = x;
}
if (y > maxY) {
maxY = y;
}
}
// Decode left of barcode
if (minX > MIN_DIMENSION_TO_RECUR) {
doDecodeMultiple(image.crop(0, 0, (int) minX, height),
hints, results, xOffset, yOffset);
}
// Decode above barcode
if (minY > MIN_DIMENSION_TO_RECUR) {
doDecodeMultiple(image.crop(0, 0, width, (int) minY),
hints, results, xOffset, yOffset);
}
// Decode right of barcode
if (maxX < width - MIN_DIMENSION_TO_RECUR) {
doDecodeMultiple(image.crop((int) maxX, 0, width - (int) maxX, height),
hints, results, xOffset + (int) maxX, yOffset);
}
// Decode below barcode
if (maxY < height - MIN_DIMENSION_TO_RECUR) {
doDecodeMultiple(image.crop(0, (int) maxY, width, height - (int) maxY),
hints, results, xOffset, yOffset + (int) maxY);
}
}
private static Result translateResultPoints(Result result, int xOffset, int yOffset) {
ResultPoint[] oldResultPoints = result.getResultPoints();
ResultPoint[] newResultPoints = new ResultPoint[oldResultPoints.length];
for (int i = 0; i < oldResultPoints.length; i++) {
ResultPoint oldPoint = oldResultPoints[i];
newResultPoints[i] = new ResultPoint(oldPoint.getX() + xOffset, oldPoint.getY() + yOffset);
}
return new Result(result.getText(), result.getRawBytes(), newResultPoints,
result.getBarcodeFormat());
}
}

Some files were not shown because too many files have changed in this diff Show More