mirror of
https://github.com/starovoid/rxing.git
synced 2026-07-26 20:32:34 +00:00
Migrate common errors all over
This commit is contained in:
@@ -1,359 +0,0 @@
|
||||
/*
|
||||
* 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.Arrays;
|
||||
|
||||
/**
|
||||
* <p>A simple, fast array of bits, represented compactly by an array of ints internally.</p>
|
||||
*
|
||||
* @author Sean Owen
|
||||
*/
|
||||
public final class BitArray implements Cloneable {
|
||||
|
||||
private static final int[] EMPTY_BITS = {};
|
||||
private static final float LOAD_FACTOR = 0.75f;
|
||||
|
||||
private int[] bits;
|
||||
private int size;
|
||||
|
||||
public BitArray() {
|
||||
this.size = 0;
|
||||
this.bits = EMPTY_BITS;
|
||||
}
|
||||
|
||||
public BitArray(int size) {
|
||||
this.size = size;
|
||||
this.bits = makeArray(size);
|
||||
}
|
||||
|
||||
// For testing only
|
||||
BitArray(int[] bits, int size) {
|
||||
this.bits = bits;
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
public int getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public int getSizeInBytes() {
|
||||
return (size + 7) / 8;
|
||||
}
|
||||
|
||||
private void ensureCapacity(int newSize) {
|
||||
if (newSize > bits.length * 32) {
|
||||
int[] newBits = makeArray((int) Math.ceil(newSize / LOAD_FACTOR));
|
||||
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 / 32] & (1 << (i & 0x1F))) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets bit i.
|
||||
*
|
||||
* @param i bit to set
|
||||
*/
|
||||
public void set(int i) {
|
||||
bits[i / 32] |= 1 << (i & 0x1F);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flips bit i.
|
||||
*
|
||||
* @param i bit to set
|
||||
*/
|
||||
public void flip(int i) {
|
||||
bits[i / 32] ^= 1 << (i & 0x1F);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param from first bit to check
|
||||
* @return index of first bit that is set, starting from the given index, or size if none are set
|
||||
* at or beyond this given index
|
||||
* @see #getNextUnset(int)
|
||||
*/
|
||||
public int getNextSet(int from) {
|
||||
if (from >= size) {
|
||||
return size;
|
||||
}
|
||||
int bitsOffset = from / 32;
|
||||
int currentBits = bits[bitsOffset];
|
||||
// mask off lesser bits first
|
||||
currentBits &= -(1 << (from & 0x1F));
|
||||
while (currentBits == 0) {
|
||||
if (++bitsOffset == bits.length) {
|
||||
return size;
|
||||
}
|
||||
currentBits = bits[bitsOffset];
|
||||
}
|
||||
int result = (bitsOffset * 32) + Integer.numberOfTrailingZeros(currentBits);
|
||||
return Math.min(result, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param from index to start looking for unset bit
|
||||
* @return index of next unset bit, or {@code size} if none are unset until the end
|
||||
* @see #getNextSet(int)
|
||||
*/
|
||||
public int getNextUnset(int from) {
|
||||
if (from >= size) {
|
||||
return size;
|
||||
}
|
||||
int bitsOffset = from / 32;
|
||||
int currentBits = ~bits[bitsOffset];
|
||||
// mask off lesser bits first
|
||||
currentBits &= -(1 << (from & 0x1F));
|
||||
while (currentBits == 0) {
|
||||
if (++bitsOffset == bits.length) {
|
||||
return size;
|
||||
}
|
||||
currentBits = ~bits[bitsOffset];
|
||||
}
|
||||
int result = (bitsOffset * 32) + Integer.numberOfTrailingZeros(currentBits);
|
||||
return Math.min(result, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 / 32] = newBits;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a range of bits.
|
||||
*
|
||||
* @param start start of range, inclusive.
|
||||
* @param end end of range, exclusive
|
||||
*/
|
||||
public void setRange(int start, int end) {
|
||||
if (end < start || start < 0 || end > size) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
if (end == start) {
|
||||
return;
|
||||
}
|
||||
end--; // will be easier to treat this as the last actually set bit -- inclusive
|
||||
int firstInt = start / 32;
|
||||
int lastInt = end / 32;
|
||||
for (int i = firstInt; i <= lastInt; i++) {
|
||||
int firstBit = i > firstInt ? 0 : start & 0x1F;
|
||||
int lastBit = i < lastInt ? 31 : end & 0x1F;
|
||||
// Ones from firstBit to lastBit, inclusive
|
||||
int mask = (2 << lastBit) - (1 << firstBit);
|
||||
bits[i] |= mask;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 start or the range is not contained in the array
|
||||
*/
|
||||
public boolean isRange(int start, int end, boolean value) {
|
||||
if (end < start || start < 0 || end > size) {
|
||||
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 / 32;
|
||||
int lastInt = end / 32;
|
||||
for (int i = firstInt; i <= lastInt; i++) {
|
||||
int firstBit = i > firstInt ? 0 : start & 0x1F;
|
||||
int lastBit = i < lastInt ? 31 : end & 0x1F;
|
||||
// Ones from firstBit to lastBit, inclusive
|
||||
int mask = (2 << lastBit) - (1 << firstBit);
|
||||
|
||||
// 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 / 32] |= 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.
|
||||
*
|
||||
* @param value {@code int} containing bits to append
|
||||
* @param numBits bits from value to append
|
||||
*/
|
||||
public void appendBits(int value, int numBits) {
|
||||
if (numBits < 0 || numBits > 32) {
|
||||
throw new IllegalArgumentException("Num bits must be between 0 and 32");
|
||||
}
|
||||
int nextSize = size;
|
||||
ensureCapacity(nextSize + numBits);
|
||||
for (int numBitsLeft = numBits - 1; numBitsLeft >= 0; numBitsLeft--) {
|
||||
if ((value & (1 << numBitsLeft)) != 0) {
|
||||
bits[nextSize / 32] |= 1 << (nextSize & 0x1F);
|
||||
}
|
||||
nextSize++;
|
||||
}
|
||||
size = nextSize;
|
||||
}
|
||||
|
||||
public void appendBitArray(BitArray other) {
|
||||
int otherSize = other.size;
|
||||
ensureCapacity(size + otherSize);
|
||||
for (int i = 0; i < otherSize; i++) {
|
||||
appendBit(other.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
public void xor(BitArray other) {
|
||||
if (size != other.size) {
|
||||
throw new IllegalArgumentException("Sizes don't match");
|
||||
}
|
||||
for (int i = 0; i < bits.length; i++) {
|
||||
// The last int could be incomplete (i.e. not have 32 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];
|
||||
// reverse all int's first
|
||||
int len = (size - 1) / 32;
|
||||
int oldBitsLen = len + 1;
|
||||
for (int i = 0; i < oldBitsLen; i++) {
|
||||
newBits[len - i] = Integer.reverse(bits[i]);
|
||||
}
|
||||
// now correct the int's if the bit size isn't a multiple of 32
|
||||
if (size != oldBitsLen * 32) {
|
||||
int leftOffset = oldBitsLen * 32 - size;
|
||||
int currentInt = newBits[0] >>> leftOffset;
|
||||
for (int i = 1; i < oldBitsLen; i++) {
|
||||
int nextInt = newBits[i];
|
||||
currentInt |= nextInt << (32 - leftOffset);
|
||||
newBits[i - 1] = currentInt;
|
||||
currentInt = nextInt >>> leftOffset;
|
||||
}
|
||||
newBits[oldBitsLen - 1] = currentInt;
|
||||
}
|
||||
bits = newBits;
|
||||
}
|
||||
|
||||
private static int[] makeArray(int size) {
|
||||
return new int[(size + 31) / 32];
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof BitArray)) {
|
||||
return false;
|
||||
}
|
||||
BitArray other = (BitArray) o;
|
||||
return size == other.size && Arrays.equals(bits, other.bits);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 31 * size + Arrays.hashCode(bits);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder result = new StringBuilder(size + (size / 8) + 1);
|
||||
for (int i = 0; i < size; i++) {
|
||||
if ((i & 0x07) == 0) {
|
||||
result.append(' ');
|
||||
}
|
||||
result.append(get(i) ? 'X' : '.');
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BitArray clone() {
|
||||
return new BitArray(bits.clone(), size);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,537 +0,0 @@
|
||||
/*
|
||||
* 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.Arrays;
|
||||
|
||||
/**
|
||||
* <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 implements Cloneable {
|
||||
|
||||
private int width;
|
||||
private int height;
|
||||
private int rowSize;
|
||||
private int[] bits;
|
||||
|
||||
/**
|
||||
* Creates an empty square {@code BitMatrix}.
|
||||
*
|
||||
* @param dimension height and width
|
||||
*/
|
||||
public BitMatrix(int dimension) {
|
||||
this(dimension, dimension);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an empty {@code BitMatrix}.
|
||||
*
|
||||
* @param width bit matrix width
|
||||
* @param height bit matrix height
|
||||
*/
|
||||
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) / 32;
|
||||
bits = new int[rowSize * height];
|
||||
}
|
||||
|
||||
private BitMatrix(int width, int height, int rowSize, int[] bits) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.rowSize = rowSize;
|
||||
this.bits = bits;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interprets a 2D array of booleans as a {@code BitMatrix}, where "true" means an "on" bit.
|
||||
*
|
||||
* @param image bits of the image, as a row-major 2D array. Elements are arrays representing rows
|
||||
* @return {@code BitMatrix} representation of image
|
||||
*/
|
||||
public static BitMatrix parse(boolean[][] image) {
|
||||
int height = image.length;
|
||||
int width = image[0].length;
|
||||
BitMatrix bits = new BitMatrix(width, height);
|
||||
for (int i = 0; i < height; i++) {
|
||||
boolean[] imageI = image[i];
|
||||
for (int j = 0; j < width; j++) {
|
||||
if (imageI[j]) {
|
||||
bits.set(j, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
return bits;
|
||||
}
|
||||
|
||||
public static BitMatrix parse(String stringRepresentation, String setString, String unsetString) {
|
||||
if (stringRepresentation == null) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
boolean[] bits = new boolean[stringRepresentation.length()];
|
||||
int bitsPos = 0;
|
||||
int rowStartPos = 0;
|
||||
int rowLength = -1;
|
||||
int nRows = 0;
|
||||
int pos = 0;
|
||||
while (pos < stringRepresentation.length()) {
|
||||
if (stringRepresentation.charAt(pos) == '\n' ||
|
||||
stringRepresentation.charAt(pos) == '\r') {
|
||||
if (bitsPos > rowStartPos) {
|
||||
if (rowLength == -1) {
|
||||
rowLength = bitsPos - rowStartPos;
|
||||
} else if (bitsPos - rowStartPos != rowLength) {
|
||||
throw new IllegalArgumentException("row lengths do not match");
|
||||
}
|
||||
rowStartPos = bitsPos;
|
||||
nRows++;
|
||||
}
|
||||
pos++;
|
||||
} else if (stringRepresentation.startsWith(setString, pos)) {
|
||||
pos += setString.length();
|
||||
bits[bitsPos] = true;
|
||||
bitsPos++;
|
||||
} else if (stringRepresentation.startsWith(unsetString, pos)) {
|
||||
pos += unsetString.length();
|
||||
bits[bitsPos] = false;
|
||||
bitsPos++;
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"illegal character encountered: " + stringRepresentation.substring(pos));
|
||||
}
|
||||
}
|
||||
|
||||
// no EOL at end?
|
||||
if (bitsPos > rowStartPos) {
|
||||
if (rowLength == -1) {
|
||||
rowLength = bitsPos - rowStartPos;
|
||||
} else if (bitsPos - rowStartPos != rowLength) {
|
||||
throw new IllegalArgumentException("row lengths do not match");
|
||||
}
|
||||
nRows++;
|
||||
}
|
||||
|
||||
BitMatrix matrix = new BitMatrix(rowLength, nRows);
|
||||
for (int i = 0; i < bitsPos; i++) {
|
||||
if (bits[i]) {
|
||||
matrix.set(i % rowLength, i / rowLength);
|
||||
}
|
||||
}
|
||||
return matrix;
|
||||
}
|
||||
|
||||
/**
|
||||
* <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 / 32);
|
||||
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 / 32);
|
||||
bits[offset] |= 1 << (x & 0x1f);
|
||||
}
|
||||
|
||||
public void unset(int x, int y) {
|
||||
int offset = y * rowSize + (x / 32);
|
||||
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 / 32);
|
||||
bits[offset] ^= 1 << (x & 0x1f);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Flips every bit in the matrix.</p>
|
||||
*/
|
||||
public void flip() {
|
||||
int max = bits.length;
|
||||
for (int i = 0; i < max; i++) {
|
||||
bits[i] = ~bits[i];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exclusive-or (XOR): Flip the bit in this {@code BitMatrix} if the corresponding
|
||||
* mask bit is set.
|
||||
*
|
||||
* @param mask XOR mask
|
||||
*/
|
||||
public void xor(BitMatrix mask) {
|
||||
if (width != mask.width || height != mask.height || rowSize != mask.rowSize) {
|
||||
throw new IllegalArgumentException("input matrix dimensions do not match");
|
||||
}
|
||||
BitArray rowArray = new BitArray(width);
|
||||
for (int y = 0; y < height; y++) {
|
||||
int offset = y * rowSize;
|
||||
int[] row = mask.getRow(y, rowArray).getBitArray();
|
||||
for (int x = 0; x < rowSize; x++) {
|
||||
bits[offset + x] ^= row[x];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 / 32)] |= 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);
|
||||
} else {
|
||||
row.clear();
|
||||
}
|
||||
int offset = y * rowSize;
|
||||
for (int x = 0; x < rowSize; x++) {
|
||||
row.setBulk(x * 32, bits[offset + x]);
|
||||
}
|
||||
return row;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param y row to set
|
||||
* @param row {@link BitArray} to copy from
|
||||
*/
|
||||
public void setRow(int y, BitArray row) {
|
||||
System.arraycopy(row.getBitArray(), 0, bits, y * rowSize, rowSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies this {@code BitMatrix} to represent the same but rotated the given degrees (0, 90, 180, 270)
|
||||
*
|
||||
* @param degrees number of degrees to rotate through counter-clockwise (0, 90, 180, 270)
|
||||
*/
|
||||
public void rotate(int degrees) {
|
||||
switch (degrees % 360) {
|
||||
case 0:
|
||||
return;
|
||||
case 90:
|
||||
rotate90();
|
||||
return;
|
||||
case 180:
|
||||
rotate180();
|
||||
return;
|
||||
case 270:
|
||||
rotate90();
|
||||
rotate180();
|
||||
return;
|
||||
}
|
||||
throw new IllegalArgumentException("degrees must be a multiple of 0, 90, 180, or 270");
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies this {@code BitMatrix} to represent the same but rotated 180 degrees
|
||||
*/
|
||||
public void rotate180() {
|
||||
BitArray topRow = new BitArray(width);
|
||||
BitArray bottomRow = new BitArray(width);
|
||||
int maxHeight = (height + 1) / 2;
|
||||
for (int i = 0; i < maxHeight; i++) {
|
||||
topRow = getRow(i, topRow);
|
||||
int bottomRowIndex = height - 1 - i;
|
||||
bottomRow = getRow(bottomRowIndex, bottomRow);
|
||||
topRow.reverse();
|
||||
bottomRow.reverse();
|
||||
setRow(i, bottomRow);
|
||||
setRow(bottomRowIndex, topRow);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies this {@code BitMatrix} to represent the same but rotated 90 degrees counterclockwise
|
||||
*/
|
||||
public void rotate90() {
|
||||
int newWidth = height;
|
||||
int newHeight = width;
|
||||
int newRowSize = (newWidth + 31) / 32;
|
||||
int[] newBits = new int[newRowSize * newHeight];
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
int offset = y * rowSize + (x / 32);
|
||||
if (((bits[offset] >>> (x & 0x1f)) & 1) != 0) {
|
||||
int newOffset = (newHeight - 1 - x) * newRowSize + (y / 32);
|
||||
newBits[newOffset] |= 1 << (y & 0x1f);
|
||||
}
|
||||
}
|
||||
}
|
||||
width = newWidth;
|
||||
height = newHeight;
|
||||
rowSize = newRowSize;
|
||||
bits = newBits;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is useful in detecting the enclosing rectangle of a 'pure' barcode.
|
||||
*
|
||||
* @return {@code left,top,width,height} enclosing rectangle of all 1 bits, or null if it is all white
|
||||
*/
|
||||
public int[] getEnclosingRectangle() {
|
||||
int left = width;
|
||||
int top = height;
|
||||
int right = -1;
|
||||
int bottom = -1;
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x32 = 0; x32 < rowSize; x32++) {
|
||||
int theBits = bits[y * rowSize + x32];
|
||||
if (theBits != 0) {
|
||||
if (y < top) {
|
||||
top = y;
|
||||
}
|
||||
if (y > bottom) {
|
||||
bottom = y;
|
||||
}
|
||||
if (x32 * 32 < left) {
|
||||
int bit = 0;
|
||||
while ((theBits << (31 - bit)) == 0) {
|
||||
bit++;
|
||||
}
|
||||
if ((x32 * 32 + bit) < left) {
|
||||
left = x32 * 32 + bit;
|
||||
}
|
||||
}
|
||||
if (x32 * 32 + 31 > right) {
|
||||
int bit = 31;
|
||||
while ((theBits >>> bit) == 0) {
|
||||
bit--;
|
||||
}
|
||||
if ((x32 * 32 + bit) > right) {
|
||||
right = x32 * 32 + bit;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (right < left || bottom < top) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new int[] {left, top, right - left + 1, bottom - top + 1};
|
||||
}
|
||||
|
||||
/**
|
||||
* This is useful in detecting a corner of a 'pure' barcode.
|
||||
*
|
||||
* @return {@code 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) * 32;
|
||||
|
||||
int theBits = bits[bitsOffset];
|
||||
int bit = 0;
|
||||
while ((theBits << (31 - bit)) == 0) {
|
||||
bit++;
|
||||
}
|
||||
x += bit;
|
||||
return new int[] {x, y};
|
||||
}
|
||||
|
||||
public int[] getBottomRightOnBit() {
|
||||
int bitsOffset = bits.length - 1;
|
||||
while (bitsOffset >= 0 && bits[bitsOffset] == 0) {
|
||||
bitsOffset--;
|
||||
}
|
||||
if (bitsOffset < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int y = bitsOffset / rowSize;
|
||||
int x = (bitsOffset % rowSize) * 32;
|
||||
|
||||
int theBits = bits[bitsOffset];
|
||||
int bit = 31;
|
||||
while ((theBits >>> 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The row size of the matrix
|
||||
*/
|
||||
public int getRowSize() {
|
||||
return rowSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof BitMatrix)) {
|
||||
return false;
|
||||
}
|
||||
BitMatrix other = (BitMatrix) o;
|
||||
return width == other.width && height == other.height && rowSize == other.rowSize &&
|
||||
Arrays.equals(bits, other.bits);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = width;
|
||||
hash = 31 * hash + width;
|
||||
hash = 31 * hash + height;
|
||||
hash = 31 * hash + rowSize;
|
||||
hash = 31 * hash + Arrays.hashCode(bits);
|
||||
return hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string representation using "X" for set and " " for unset bits
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return toString("X ", " ");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param setString representation of a set bit
|
||||
* @param unsetString representation of an unset bit
|
||||
* @return string representation of entire matrix utilizing given strings
|
||||
*/
|
||||
public String toString(String setString, String unsetString) {
|
||||
return buildToString(setString, unsetString, "\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param setString representation of a set bit
|
||||
* @param unsetString representation of an unset bit
|
||||
* @param lineSeparator newline character in string representation
|
||||
* @return string representation of entire matrix utilizing given strings and line separator
|
||||
* @deprecated call {@link #toString(String,String)} only, which uses \n line separator always
|
||||
*/
|
||||
@Deprecated
|
||||
public String toString(String setString, String unsetString, String lineSeparator) {
|
||||
return buildToString(setString, unsetString, lineSeparator);
|
||||
}
|
||||
|
||||
private String buildToString(String setString, String unsetString, String lineSeparator) {
|
||||
StringBuilder result = new StringBuilder(height * (width + 1));
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
result.append(get(x, y) ? setString : unsetString);
|
||||
}
|
||||
result.append(lineSeparator);
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BitMatrix clone() {
|
||||
return new BitMatrix(width, height, rowSize, bits.clone());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return index of next bit in current byte which would be read by the next call to {@link #readBits(int)}.
|
||||
*/
|
||||
public int getBitOffset() {
|
||||
return bitOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return index of next byte in input byte array which would be read by the next call to {@link #readBits(int)}.
|
||||
*/
|
||||
public int getByteOffset() {
|
||||
return byteOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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] or more than is available
|
||||
*/
|
||||
public int readBits(int numBits) {
|
||||
if (numBits < 1 || numBits > 32 || numBits > available()) {
|
||||
throw new IllegalArgumentException(String.valueOf(numBits));
|
||||
}
|
||||
|
||||
int result = 0;
|
||||
|
||||
// First, read remainder from current byte
|
||||
if (bitOffset > 0) {
|
||||
int bitsLeft = 8 - bitOffset;
|
||||
int toRead = Math.min(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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
/*
|
||||
* 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 com.google.zxing.FormatException;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Encapsulates a Character Set ECI, according to "Extended Channel Interpretations" 5.3.1.1
|
||||
* of ISO 18004.
|
||||
*
|
||||
* @author Sean Owen
|
||||
*/
|
||||
public enum CharacterSetECI {
|
||||
|
||||
// Enum name is a Java encoding valid for java.lang and java.io
|
||||
Cp437(new int[]{0,2}),
|
||||
ISO8859_1(new int[]{1,3}, "ISO-8859-1"),
|
||||
ISO8859_2(4, "ISO-8859-2"),
|
||||
ISO8859_3(5, "ISO-8859-3"),
|
||||
ISO8859_4(6, "ISO-8859-4"),
|
||||
ISO8859_5(7, "ISO-8859-5"),
|
||||
// ISO8859_6(8, "ISO-8859-6"),
|
||||
ISO8859_7(9, "ISO-8859-7"),
|
||||
// ISO8859_8(10, "ISO-8859-8"),
|
||||
ISO8859_9(11, "ISO-8859-9"),
|
||||
// ISO8859_10(12, "ISO-8859-10"),
|
||||
// ISO8859_11(13, "ISO-8859-11"),
|
||||
ISO8859_13(15, "ISO-8859-13"),
|
||||
// ISO8859_14(16, "ISO-8859-14"),
|
||||
ISO8859_15(17, "ISO-8859-15"),
|
||||
ISO8859_16(18, "ISO-8859-16"),
|
||||
SJIS(20, "Shift_JIS"),
|
||||
Cp1250(21, "windows-1250"),
|
||||
Cp1251(22, "windows-1251"),
|
||||
Cp1252(23, "windows-1252"),
|
||||
Cp1256(24, "windows-1256"),
|
||||
UnicodeBigUnmarked(25, "UTF-16BE", "UnicodeBig"),
|
||||
UTF8(26, "UTF-8"),
|
||||
ASCII(new int[] {27, 170}, "US-ASCII"),
|
||||
Big5(28),
|
||||
GB18030(29, "GB2312", "EUC_CN", "GBK"),
|
||||
EUC_KR(30, "EUC-KR");
|
||||
|
||||
private static final Map<Integer,CharacterSetECI> VALUE_TO_ECI = new HashMap<>();
|
||||
private static final Map<String,CharacterSetECI> NAME_TO_ECI = new HashMap<>();
|
||||
static {
|
||||
for (CharacterSetECI eci : values()) {
|
||||
for (int value : eci.values) {
|
||||
VALUE_TO_ECI.put(value, eci);
|
||||
}
|
||||
NAME_TO_ECI.put(eci.name(), eci);
|
||||
for (String name : eci.otherEncodingNames) {
|
||||
NAME_TO_ECI.put(name, eci);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final int[] values;
|
||||
private final String[] otherEncodingNames;
|
||||
|
||||
CharacterSetECI(int value) {
|
||||
this(new int[] {value});
|
||||
}
|
||||
|
||||
CharacterSetECI(int value, String... otherEncodingNames) {
|
||||
this.values = new int[] {value};
|
||||
this.otherEncodingNames = otherEncodingNames;
|
||||
}
|
||||
|
||||
CharacterSetECI(int[] values, String... otherEncodingNames) {
|
||||
this.values = values;
|
||||
this.otherEncodingNames = otherEncodingNames;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return values[0];
|
||||
}
|
||||
|
||||
public Charset getCharset() {
|
||||
return Charset.forName(name());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param charset Java character set object
|
||||
* @return CharacterSetECI representing ECI for character encoding, or null if it is legal
|
||||
* but unsupported
|
||||
*/
|
||||
public static CharacterSetECI getCharacterSetECI(Charset charset) {
|
||||
return NAME_TO_ECI.get(charset.name());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param value character set ECI value
|
||||
* @return {@code CharacterSetECI} representing ECI of given value, or null if it is legal but
|
||||
* unsupported
|
||||
* @throws FormatException if ECI value is invalid
|
||||
*/
|
||||
public static CharacterSetECI getCharacterSetECIByValue(int value) throws FormatException {
|
||||
if (value < 0 || value >= 900) {
|
||||
throw FormatException.getFormatInstance();
|
||||
}
|
||||
return VALUE_TO_ECI.get(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param name character set ECI encoding name
|
||||
* @return CharacterSetECI representing ECI for character encoding, or null if it is legal
|
||||
* but unsupported
|
||||
*/
|
||||
public static CharacterSetECI getCharacterSetECIByName(String name) {
|
||||
return NAME_TO_ECI.get(name);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,176 +0,0 @@
|
||||
/*
|
||||
* 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.List;
|
||||
|
||||
/**
|
||||
* <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 int numBits;
|
||||
private final String text;
|
||||
private final List<byte[]> byteSegments;
|
||||
private final String ecLevel;
|
||||
private Integer errorsCorrected;
|
||||
private Integer erasures;
|
||||
private Object other;
|
||||
private final int structuredAppendParity;
|
||||
private final int structuredAppendSequenceNumber;
|
||||
private final int symbologyModifier;
|
||||
|
||||
public DecoderResult(byte[] rawBytes,
|
||||
String text,
|
||||
List<byte[]> byteSegments,
|
||||
String ecLevel) {
|
||||
this(rawBytes, text, byteSegments, ecLevel, -1, -1, 0);
|
||||
}
|
||||
|
||||
public DecoderResult(byte[] rawBytes,
|
||||
String text,
|
||||
List<byte[]> byteSegments,
|
||||
String ecLevel,
|
||||
int symbologyModifier) {
|
||||
this(rawBytes, text, byteSegments, ecLevel, -1, -1, symbologyModifier);
|
||||
}
|
||||
|
||||
public DecoderResult(byte[] rawBytes,
|
||||
String text,
|
||||
List<byte[]> byteSegments,
|
||||
String ecLevel,
|
||||
int saSequence,
|
||||
int saParity) {
|
||||
this(rawBytes, text, byteSegments, ecLevel, saSequence, saParity, 0);
|
||||
}
|
||||
|
||||
public DecoderResult(byte[] rawBytes,
|
||||
String text,
|
||||
List<byte[]> byteSegments,
|
||||
String ecLevel,
|
||||
int saSequence,
|
||||
int saParity,
|
||||
int symbologyModifier) {
|
||||
this.rawBytes = rawBytes;
|
||||
this.numBits = rawBytes == null ? 0 : 8 * rawBytes.length;
|
||||
this.text = text;
|
||||
this.byteSegments = byteSegments;
|
||||
this.ecLevel = ecLevel;
|
||||
this.structuredAppendParity = saParity;
|
||||
this.structuredAppendSequenceNumber = saSequence;
|
||||
this.symbologyModifier = symbologyModifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return raw bytes representing the result, or {@code null} if not applicable
|
||||
*/
|
||||
public byte[] getRawBytes() {
|
||||
return rawBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return how many bits of {@link #getRawBytes()} are valid; typically 8 times its length
|
||||
* @since 3.3.0
|
||||
*/
|
||||
public int getNumBits() {
|
||||
return numBits;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param numBits overrides the number of bits that are valid in {@link #getRawBytes()}
|
||||
* @since 3.3.0
|
||||
*/
|
||||
public void setNumBits(int numBits) {
|
||||
this.numBits = numBits;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return text representation of the result
|
||||
*/
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list of byte segments in the result, or {@code null} if not applicable
|
||||
*/
|
||||
public List<byte[]> getByteSegments() {
|
||||
return byteSegments;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return name of error correction level used, or {@code null} if not applicable
|
||||
*/
|
||||
public String getECLevel() {
|
||||
return ecLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return number of errors corrected, or {@code null} if not applicable
|
||||
*/
|
||||
public Integer getErrorsCorrected() {
|
||||
return errorsCorrected;
|
||||
}
|
||||
|
||||
public void setErrorsCorrected(Integer errorsCorrected) {
|
||||
this.errorsCorrected = errorsCorrected;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return number of erasures corrected, or {@code null} if not applicable
|
||||
*/
|
||||
public Integer getErasures() {
|
||||
return erasures;
|
||||
}
|
||||
|
||||
public void setErasures(Integer erasures) {
|
||||
this.erasures = erasures;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return arbitrary additional metadata
|
||||
*/
|
||||
public Object getOther() {
|
||||
return other;
|
||||
}
|
||||
|
||||
public void setOther(Object other) {
|
||||
this.other = other;
|
||||
}
|
||||
|
||||
public boolean hasStructuredAppend() {
|
||||
return structuredAppendParity >= 0 && structuredAppendSequenceNumber >= 0;
|
||||
}
|
||||
|
||||
public int getStructuredAppendParity() {
|
||||
return structuredAppendParity;
|
||||
}
|
||||
|
||||
public int getStructuredAppendSequenceNumber() {
|
||||
return structuredAppendSequenceNumber;
|
||||
}
|
||||
|
||||
public int getSymbologyModifier() {
|
||||
return symbologyModifier;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
/*
|
||||
* 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 {
|
||||
|
||||
@Override
|
||||
public BitMatrix sampleGrid(BitMatrix image,
|
||||
int dimensionX,
|
||||
int dimensionY,
|
||||
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, dimensionX, dimensionY, transform);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BitMatrix sampleGrid(BitMatrix image,
|
||||
int dimensionX,
|
||||
int dimensionY,
|
||||
PerspectiveTransform transform) throws NotFoundException {
|
||||
if (dimensionX <= 0 || dimensionY <= 0) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
BitMatrix bits = new BitMatrix(dimensionX, dimensionY);
|
||||
float[] points = new float[2 * dimensionX];
|
||||
for (int y = 0; y < dimensionY; y++) {
|
||||
int max = points.length;
|
||||
float iValue = y + 0.5f;
|
||||
for (int x = 0; x < max; x += 2) {
|
||||
points[x] = (float) (x / 2) + 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 / 2, 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* 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 class DetectorResult {
|
||||
|
||||
private final BitMatrix bits;
|
||||
private final ResultPoint[] points;
|
||||
|
||||
public DetectorResult(BitMatrix bits, ResultPoint[] points) {
|
||||
this.bits = bits;
|
||||
this.points = points;
|
||||
}
|
||||
|
||||
public final BitMatrix getBits() {
|
||||
return bits;
|
||||
}
|
||||
|
||||
public final ResultPoint[] getPoints() {
|
||||
return points;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,199 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 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.nio.charset.Charset;
|
||||
import java.nio.charset.CharsetEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.charset.UnsupportedCharsetException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Set of CharsetEncoders for a given input string
|
||||
*
|
||||
* Invariants:
|
||||
* - The list contains only encoders from CharacterSetECI (list is shorter then the list of encoders available on
|
||||
* the platform for which ECI values are defined).
|
||||
* - The list contains encoders at least one encoder for every character in the input.
|
||||
* - The first encoder in the list is always the ISO-8859-1 encoder even of no character in the input can be encoded
|
||||
* by it.
|
||||
* - If the input contains a character that is not in ISO-8859-1 then the last two entries in the list will be the
|
||||
* UTF-8 encoder and the UTF-16BE encoder.
|
||||
*
|
||||
* @author Alex Geller
|
||||
*/
|
||||
public final class ECIEncoderSet {
|
||||
|
||||
// List of encoders that potentially encode characters not in ISO-8859-1 in one byte.
|
||||
private static final List<CharsetEncoder> ENCODERS = new ArrayList<>();
|
||||
static {
|
||||
String[] names = { "IBM437",
|
||||
"ISO-8859-2",
|
||||
"ISO-8859-3",
|
||||
"ISO-8859-4",
|
||||
"ISO-8859-5",
|
||||
"ISO-8859-6",
|
||||
"ISO-8859-7",
|
||||
"ISO-8859-8",
|
||||
"ISO-8859-9",
|
||||
"ISO-8859-10",
|
||||
"ISO-8859-11",
|
||||
"ISO-8859-13",
|
||||
"ISO-8859-14",
|
||||
"ISO-8859-15",
|
||||
"ISO-8859-16",
|
||||
"windows-1250",
|
||||
"windows-1251",
|
||||
"windows-1252",
|
||||
"windows-1256",
|
||||
"Shift_JIS" };
|
||||
for (String name : names) {
|
||||
if (CharacterSetECI.getCharacterSetECIByName(name) != null) {
|
||||
try {
|
||||
ENCODERS.add(Charset.forName(name).newEncoder());
|
||||
} catch (UnsupportedCharsetException e) {
|
||||
// continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final CharsetEncoder[] encoders;
|
||||
private final int priorityEncoderIndex;
|
||||
|
||||
/**
|
||||
* Constructs an encoder set
|
||||
*
|
||||
* @param stringToEncode the string that needs to be encoded
|
||||
* @param priorityCharset The preferred {@link Charset} or null.
|
||||
* @param fnc1 fnc1 denotes the character in the input that represents the FNC1 character or -1 for a non-GS1 bar
|
||||
* code. When specified, it is considered an error to pass it as argument to the methods canEncode() or encode().
|
||||
*/
|
||||
public ECIEncoderSet(String stringToEncode, Charset priorityCharset, int fnc1) {
|
||||
List<CharsetEncoder> neededEncoders = new ArrayList<>();
|
||||
|
||||
//we always need the ISO-8859-1 encoder. It is the default encoding
|
||||
neededEncoders.add(StandardCharsets.ISO_8859_1.newEncoder());
|
||||
boolean needUnicodeEncoder = priorityCharset != null && priorityCharset.name().startsWith("UTF");
|
||||
|
||||
//Walk over the input string and see if all characters can be encoded with the list of encoders
|
||||
for (int i = 0; i < stringToEncode.length(); i++) {
|
||||
boolean canEncode = false;
|
||||
for (CharsetEncoder encoder : neededEncoders) {
|
||||
char c = stringToEncode.charAt(i);
|
||||
if (c == fnc1 || encoder.canEncode(c)) {
|
||||
canEncode = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!canEncode) {
|
||||
//for the character at position i we don't yet have an encoder in the list
|
||||
for (CharsetEncoder encoder : ENCODERS) {
|
||||
if (encoder.canEncode(stringToEncode.charAt(i))) {
|
||||
//Good, we found an encoder that can encode the character. We add him to the list and continue scanning
|
||||
//the input
|
||||
neededEncoders.add(encoder);
|
||||
canEncode = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!canEncode) {
|
||||
//The character is not encodeable by any of the single byte encoders so we remember that we will need a
|
||||
//Unicode encoder.
|
||||
needUnicodeEncoder = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (neededEncoders.size() == 1 && !needUnicodeEncoder) {
|
||||
//the entire input can be encoded by the ISO-8859-1 encoder
|
||||
encoders = new CharsetEncoder[] { neededEncoders.get(0) };
|
||||
} else {
|
||||
// we need more than one single byte encoder or we need a Unicode encoder.
|
||||
// In this case we append a UTF-8 and UTF-16 encoder to the list
|
||||
encoders = new CharsetEncoder[neededEncoders.size() + 2];
|
||||
int index = 0;
|
||||
for (CharsetEncoder encoder : neededEncoders) {
|
||||
encoders[index++] = encoder;
|
||||
}
|
||||
|
||||
encoders[index] = StandardCharsets.UTF_8.newEncoder();
|
||||
encoders[index + 1] = StandardCharsets.UTF_16BE.newEncoder();
|
||||
}
|
||||
|
||||
//Compute priorityEncoderIndex by looking up priorityCharset in encoders
|
||||
int priorityEncoderIndexValue = -1;
|
||||
if (priorityCharset != null) {
|
||||
for (int i = 0; i < encoders.length; i++) {
|
||||
if (encoders[i] != null && priorityCharset.name().equals(encoders[i].charset().name())) {
|
||||
priorityEncoderIndexValue = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
priorityEncoderIndex = priorityEncoderIndexValue;
|
||||
//invariants
|
||||
assert encoders[0].charset().equals(StandardCharsets.ISO_8859_1);
|
||||
}
|
||||
|
||||
public int length() {
|
||||
return encoders.length;
|
||||
}
|
||||
|
||||
public String getCharsetName(int index) {
|
||||
assert index < length();
|
||||
return encoders[index].charset().name();
|
||||
}
|
||||
|
||||
public Charset getCharset(int index) {
|
||||
assert index < length();
|
||||
return encoders[index].charset();
|
||||
}
|
||||
|
||||
public int getECIValue(int encoderIndex) {
|
||||
return CharacterSetECI.getCharacterSetECI(encoders[encoderIndex].charset()).getValue();
|
||||
}
|
||||
|
||||
/*
|
||||
* returns -1 if no priority charset was defined
|
||||
*/
|
||||
public int getPriorityEncoderIndex() {
|
||||
return priorityEncoderIndex;
|
||||
}
|
||||
|
||||
public boolean canEncode(char c, int encoderIndex) {
|
||||
assert encoderIndex < length();
|
||||
CharsetEncoder encoder = encoders[encoderIndex];
|
||||
return encoder.canEncode("" + c);
|
||||
}
|
||||
|
||||
public byte[] encode(char c, int encoderIndex) {
|
||||
assert encoderIndex < length();
|
||||
CharsetEncoder encoder = encoders[encoderIndex];
|
||||
assert encoder.canEncode("" + c);
|
||||
return ("" + c).getBytes(encoder.charset());
|
||||
}
|
||||
|
||||
public byte[] encode(String s, int encoderIndex) {
|
||||
assert encoderIndex < length();
|
||||
CharsetEncoder encoder = encoders[encoderIndex];
|
||||
return s.getBytes(encoder.charset());
|
||||
}
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 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;
|
||||
|
||||
/**
|
||||
* Interface to navigate a sequence of ECIs and bytes.
|
||||
*
|
||||
* @author Alex Geller
|
||||
*/
|
||||
public interface ECIInput {
|
||||
|
||||
/**
|
||||
* Returns the length of this input. The length is the number
|
||||
* of {@code byte}s in or ECIs in the sequence.
|
||||
*
|
||||
* @return the number of {@code char}s in this sequence
|
||||
*/
|
||||
int length();
|
||||
|
||||
/**
|
||||
* Returns the {@code byte} value at the specified index. An index ranges from zero
|
||||
* to {@code length() - 1}. The first {@code byte} value of the sequence is at
|
||||
* index zero, the next at index one, and so on, as for array
|
||||
* indexing.
|
||||
*
|
||||
* @param index the index of the {@code byte} value to be returned
|
||||
*
|
||||
* @return the specified {@code byte} value as character or the FNC1 character
|
||||
*
|
||||
* @throws IndexOutOfBoundsException
|
||||
* if the {@code index} argument is negative or not less than
|
||||
* {@code length()}
|
||||
* @throws IllegalArgumentException
|
||||
* if the value at the {@code index} argument is an ECI (@see #isECI)
|
||||
*/
|
||||
char charAt(int index);
|
||||
|
||||
/**
|
||||
* Returns a {@code CharSequence} that is a subsequence of this sequence.
|
||||
* The subsequence starts with the {@code char} value at the specified index and
|
||||
* ends with the {@code char} value at index {@code end - 1}. The length
|
||||
* (in {@code char}s) of the
|
||||
* returned sequence is {@code end - start}, so if {@code start == end}
|
||||
* then an empty sequence is returned.
|
||||
*
|
||||
* @param start the start index, inclusive
|
||||
* @param end the end index, exclusive
|
||||
*
|
||||
* @return the specified subsequence
|
||||
*
|
||||
* @throws IndexOutOfBoundsException
|
||||
* if {@code start} or {@code end} are negative,
|
||||
* if {@code end} is greater than {@code length()},
|
||||
* or if {@code start} is greater than {@code end}
|
||||
* @throws IllegalArgumentException
|
||||
* if a value in the range {@code start}-{@code end} is an ECI (@see #isECI)
|
||||
*/
|
||||
CharSequence subSequence(int start, int end);
|
||||
|
||||
/**
|
||||
* Determines if a value is an ECI
|
||||
*
|
||||
* @param index the index of the value
|
||||
*
|
||||
* @return true if the value at position {@code index} is an ECI
|
||||
*
|
||||
* @throws IndexOutOfBoundsException
|
||||
* if the {@code index} argument is negative or not less than
|
||||
* {@code length()}
|
||||
*/
|
||||
boolean isECI(int index);
|
||||
|
||||
/**
|
||||
* Returns the {@code int} ECI value at the specified index. An index ranges from zero
|
||||
* to {@code length() - 1}. The first {@code byte} value of the sequence is at
|
||||
* index zero, the next at index one, and so on, as for array
|
||||
* indexing.
|
||||
*
|
||||
* @param index the index of the {@code int} value to be returned
|
||||
*
|
||||
* @return the specified {@code int} ECI value.
|
||||
* The ECI specified the encoding of all bytes with a higher index until the
|
||||
* next ECI or until the end of the input if no other ECI follows.
|
||||
*
|
||||
* @throws IndexOutOfBoundsException
|
||||
* if the {@code index} argument is negative or not less than
|
||||
* {@code length()}
|
||||
* @throws IllegalArgumentException
|
||||
* if the value at the {@code index} argument is not an ECI (@see #isECI)
|
||||
*/
|
||||
int getECIValue(int index);
|
||||
boolean haveNCharacters(int index, int n);
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 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.FormatException;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* Class that converts a sequence of ECIs and bytes into a string
|
||||
*
|
||||
* @author Alex Geller
|
||||
*/
|
||||
public final class ECIStringBuilder {
|
||||
private StringBuilder currentBytes;
|
||||
private StringBuilder result;
|
||||
private Charset currentCharset = StandardCharsets.ISO_8859_1;
|
||||
|
||||
public ECIStringBuilder() {
|
||||
currentBytes = new StringBuilder();
|
||||
}
|
||||
public ECIStringBuilder(int initialCapacity) {
|
||||
currentBytes = new StringBuilder(initialCapacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends {@code value} as a byte value
|
||||
*
|
||||
* @param value character whose lowest byte is to be appended
|
||||
*/
|
||||
public void append(char value) {
|
||||
currentBytes.append((char) (value & 0xff));
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends {@code value} as a byte value
|
||||
*
|
||||
* @param value byte to append
|
||||
*/
|
||||
public void append(byte value) {
|
||||
currentBytes.append((char) (value & 0xff));
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the characters in {@code value} as bytes values
|
||||
*
|
||||
* @param value string to append
|
||||
*/
|
||||
public void append(String value) {
|
||||
currentBytes.append(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append the string repesentation of {@code value} (short for {@code append(String.valueOf(value))})
|
||||
*
|
||||
* @param value int to append as a string
|
||||
*/
|
||||
public void append(int value) {
|
||||
append(String.valueOf(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends ECI value to output.
|
||||
*
|
||||
* @param value ECI value to append, as an int
|
||||
* @throws FormatException on invalid ECI value
|
||||
*/
|
||||
public void appendECI(int value) throws FormatException {
|
||||
encodeCurrentBytesIfAny();
|
||||
CharacterSetECI characterSetECI = CharacterSetECI.getCharacterSetECIByValue(value);
|
||||
if (characterSetECI == null) {
|
||||
throw FormatException.getFormatInstance();
|
||||
}
|
||||
currentCharset = characterSetECI.getCharset();
|
||||
}
|
||||
|
||||
private void encodeCurrentBytesIfAny() {
|
||||
if (currentCharset.equals(StandardCharsets.ISO_8859_1)) {
|
||||
if (currentBytes.length() > 0) {
|
||||
if (result == null) {
|
||||
result = currentBytes;
|
||||
currentBytes = new StringBuilder();
|
||||
} else {
|
||||
result.append(currentBytes);
|
||||
currentBytes = new StringBuilder();
|
||||
}
|
||||
}
|
||||
} else if (currentBytes.length() > 0) {
|
||||
byte[] bytes = currentBytes.toString().getBytes(StandardCharsets.ISO_8859_1);
|
||||
currentBytes = new StringBuilder();
|
||||
if (result == null) {
|
||||
result = new StringBuilder(new String(bytes, currentCharset));
|
||||
} else {
|
||||
result.append(new String(bytes, currentCharset));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the characters from {@code value} (unlike all other append methods of this class who append bytes)
|
||||
*
|
||||
* @param value characters to append
|
||||
*/
|
||||
public void appendCharacters(StringBuilder value) {
|
||||
encodeCurrentBytesIfAny();
|
||||
result.append(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Short for {@code toString().length()} (if possible, use {@link #isEmpty()} instead)
|
||||
*
|
||||
* @return length of string representation in characters
|
||||
*/
|
||||
public int length() {
|
||||
return toString().length();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true iff nothing has been appended
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return currentBytes.length() == 0 && (result == null || result.length() == 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
encodeCurrentBytesIfAny();
|
||||
return result == null ? "" : result.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,203 +0,0 @@
|
||||
/*
|
||||
* 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 static final byte[] EMPTY = new byte[0];
|
||||
|
||||
private byte[] luminances;
|
||||
private final int[] buckets;
|
||||
|
||||
public GlobalHistogramBinarizer(LuminanceSource source) {
|
||||
super(source);
|
||||
luminances = EMPTY;
|
||||
buckets = new int[LUMINANCE_BUCKETS];
|
||||
}
|
||||
|
||||
// Applies simple sharpening to the row data to improve performance of the 1D Readers.
|
||||
@Override
|
||||
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++) {
|
||||
localBuckets[(localLuminances[x] & 0xff) >> LUMINANCE_SHIFT]++;
|
||||
}
|
||||
int blackPoint = estimateBlackPoint(localBuckets);
|
||||
|
||||
if (width < 3) {
|
||||
// Special case for very small images
|
||||
for (int x = 0; x < width; x++) {
|
||||
if ((localLuminances[x] & 0xff) < blackPoint) {
|
||||
row.set(x);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
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.
|
||||
if (((center * 4) - left - right) / 2 < 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.
|
||||
@Override
|
||||
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 * 4) / 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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Binarizer createBinarizer(LuminanceSource source) {
|
||||
return new GlobalHistogramBinarizer(source);
|
||||
}
|
||||
|
||||
private void initArrays(int luminanceSize) {
|
||||
if (luminances.length < luminanceSize) {
|
||||
luminances = new byte[luminanceSize];
|
||||
}
|
||||
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.
|
||||
if (secondPeak - firstPeak <= numBuckets / 16) {
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,174 +0,0 @@
|
||||
/*
|
||||
* 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 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) {
|
||||
gridSampler = newGridSampler;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the current implementation of GridSampler
|
||||
*/
|
||||
public static GridSampler getInstance() {
|
||||
return gridSampler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Samples an image for a rectangular matrix of bits of the given dimension. The sampling
|
||||
* transformation is determined by the coordinates of 4 points, in the original and transformed
|
||||
* image space.
|
||||
*
|
||||
* @param image image to sample
|
||||
* @param dimensionX width of {@link BitMatrix} to sample from image
|
||||
* @param dimensionY height of {@link BitMatrix} to sample from image
|
||||
* @param p1ToX point 1 preimage X
|
||||
* @param p1ToY point 1 preimage Y
|
||||
* @param p2ToX point 2 preimage X
|
||||
* @param p2ToY point 2 preimage Y
|
||||
* @param p3ToX point 3 preimage X
|
||||
* @param p3ToY point 3 preimage Y
|
||||
* @param p4ToX point 4 preimage X
|
||||
* @param p4ToY point 4 preimage Y
|
||||
* @param p1FromX point 1 image X
|
||||
* @param p1FromY point 1 image Y
|
||||
* @param p2FromX point 2 image X
|
||||
* @param p2FromY point 2 image Y
|
||||
* @param p3FromX point 3 image X
|
||||
* @param p3FromY point 3 image Y
|
||||
* @param p4FromX point 4 image X
|
||||
* @param p4FromY point 4 image Y
|
||||
* @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 dimensionX,
|
||||
int dimensionY,
|
||||
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 abstract BitMatrix sampleGrid(BitMatrix image,
|
||||
int dimensionX,
|
||||
int dimensionY,
|
||||
PerspectiveTransform transform) throws NotFoundException;
|
||||
|
||||
/**
|
||||
* <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;
|
||||
int maxOffset = points.length - 1; // points.length must be even
|
||||
for (int offset = 0; offset < maxOffset && 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,237 +0,0 @@
|
||||
/*
|
||||
* 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 BLOCK_SIZE_POWER = 3;
|
||||
private static final int BLOCK_SIZE = 1 << BLOCK_SIZE_POWER; // ...0100...00
|
||||
private static final int BLOCK_SIZE_MASK = BLOCK_SIZE - 1; // ...0011...11
|
||||
private static final int MINIMUM_DIMENSION = BLOCK_SIZE * 5;
|
||||
private static final int MIN_DYNAMIC_RANGE = 24;
|
||||
|
||||
private BitMatrix matrix;
|
||||
|
||||
public HybridBinarizer(LuminanceSource source) {
|
||||
super(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.
|
||||
*/
|
||||
@Override
|
||||
public BitMatrix getBlackMatrix() throws NotFoundException {
|
||||
if (matrix != null) {
|
||||
return matrix;
|
||||
}
|
||||
LuminanceSource source = getLuminanceSource();
|
||||
int width = source.getWidth();
|
||||
int height = source.getHeight();
|
||||
if (width >= MINIMUM_DIMENSION && height >= MINIMUM_DIMENSION) {
|
||||
byte[] luminances = source.getMatrix();
|
||||
int subWidth = width >> BLOCK_SIZE_POWER;
|
||||
if ((width & BLOCK_SIZE_MASK) != 0) {
|
||||
subWidth++;
|
||||
}
|
||||
int subHeight = height >> BLOCK_SIZE_POWER;
|
||||
if ((height & BLOCK_SIZE_MASK) != 0) {
|
||||
subHeight++;
|
||||
}
|
||||
int[][] blackPoints = calculateBlackPoints(luminances, subWidth, subHeight, width, height);
|
||||
|
||||
BitMatrix newMatrix = new BitMatrix(width, height);
|
||||
calculateThresholdForBlock(luminances, subWidth, subHeight, width, height, blackPoints, newMatrix);
|
||||
matrix = newMatrix;
|
||||
} else {
|
||||
// If the image is too small, fall back to the global histogram approach.
|
||||
matrix = super.getBlackMatrix();
|
||||
}
|
||||
return matrix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Binarizer createBinarizer(LuminanceSource source) {
|
||||
return new HybridBinarizer(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* For each 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 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) {
|
||||
int maxYOffset = height - BLOCK_SIZE;
|
||||
int maxXOffset = width - BLOCK_SIZE;
|
||||
for (int y = 0; y < subHeight; y++) {
|
||||
int yoffset = y << BLOCK_SIZE_POWER;
|
||||
if (yoffset > maxYOffset) {
|
||||
yoffset = maxYOffset;
|
||||
}
|
||||
int top = cap(y, subHeight - 3);
|
||||
for (int x = 0; x < subWidth; x++) {
|
||||
int xoffset = x << BLOCK_SIZE_POWER;
|
||||
if (xoffset > maxXOffset) {
|
||||
xoffset = maxXOffset;
|
||||
}
|
||||
int left = cap(x, subWidth - 3);
|
||||
int sum = 0;
|
||||
for (int z = -2; z <= 2; z++) {
|
||||
int[] blackRow = blackPoints[top + z];
|
||||
sum += blackRow[left - 2] + blackRow[left - 1] + blackRow[left] + blackRow[left + 1] + blackRow[left + 2];
|
||||
}
|
||||
int average = sum / 25;
|
||||
thresholdBlock(luminances, xoffset, yoffset, average, width, matrix);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int cap(int value, int max) {
|
||||
return value < 2 ? 2 : Math.min(value, max);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a single threshold to a block of pixels.
|
||||
*/
|
||||
private static void thresholdBlock(byte[] luminances,
|
||||
int xoffset,
|
||||
int yoffset,
|
||||
int threshold,
|
||||
int stride,
|
||||
BitMatrix matrix) {
|
||||
for (int y = 0, offset = yoffset * stride + xoffset; y < BLOCK_SIZE; y++, offset += stride) {
|
||||
for (int x = 0; x < BLOCK_SIZE; x++) {
|
||||
// Comparison needs to be <= so that black == 0 pixels are black even if the threshold is 0.
|
||||
if ((luminances[offset + x] & 0xFF) <= threshold) {
|
||||
matrix.set(xoffset + x, yoffset + y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates a single black point for each block of pixels and saves it away.
|
||||
* See the following thread for a discussion of this algorithm:
|
||||
* http://groups.google.com/group/zxing/browse_thread/thread/d06efa2c35a7ddc0
|
||||
*/
|
||||
private static int[][] calculateBlackPoints(byte[] luminances,
|
||||
int subWidth,
|
||||
int subHeight,
|
||||
int width,
|
||||
int height) {
|
||||
int maxYOffset = height - BLOCK_SIZE;
|
||||
int maxXOffset = width - BLOCK_SIZE;
|
||||
int[][] blackPoints = new int[subHeight][subWidth];
|
||||
for (int y = 0; y < subHeight; y++) {
|
||||
int yoffset = y << BLOCK_SIZE_POWER;
|
||||
if (yoffset > maxYOffset) {
|
||||
yoffset = maxYOffset;
|
||||
}
|
||||
for (int x = 0; x < subWidth; x++) {
|
||||
int xoffset = x << BLOCK_SIZE_POWER;
|
||||
if (xoffset > maxXOffset) {
|
||||
xoffset = maxXOffset;
|
||||
}
|
||||
int sum = 0;
|
||||
int min = 0xFF;
|
||||
int max = 0;
|
||||
for (int yy = 0, offset = yoffset * width + xoffset; yy < BLOCK_SIZE; yy++, offset += width) {
|
||||
for (int xx = 0; xx < BLOCK_SIZE; xx++) {
|
||||
int pixel = luminances[offset + xx] & 0xFF;
|
||||
sum += pixel;
|
||||
// still looking for good contrast
|
||||
if (pixel < min) {
|
||||
min = pixel;
|
||||
}
|
||||
if (pixel > max) {
|
||||
max = pixel;
|
||||
}
|
||||
}
|
||||
// short-circuit min/max tests once dynamic range is met
|
||||
if (max - min > MIN_DYNAMIC_RANGE) {
|
||||
// finish the rest of the rows quickly
|
||||
for (yy++, offset += width; yy < BLOCK_SIZE; yy++, offset += width) {
|
||||
for (int xx = 0; xx < BLOCK_SIZE; xx++) {
|
||||
sum += luminances[offset + xx] & 0xFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The default estimate is the average of the values in the block.
|
||||
int average = sum >> (BLOCK_SIZE_POWER * 2);
|
||||
if (max - min <= MIN_DYNAMIC_RANGE) {
|
||||
// If variation within the block is low, assume this is a block with only light or only
|
||||
// dark pixels. In that case we do not want to use the average, as it would divide this
|
||||
// low contrast area into black and white pixels, essentially creating data out of noise.
|
||||
//
|
||||
// The default assumption is that the block is light/background. Since no estimate for
|
||||
// the level of dark pixels exists locally, use half the min for the block.
|
||||
average = min / 2;
|
||||
|
||||
if (y > 0 && x > 0) {
|
||||
// Correct the "white background" assumption for blocks that have neighbors by comparing
|
||||
// the pixels in this block to the previously calculated black points. This is based on
|
||||
// the fact that dark barcode symbology is always surrounded by some amount of light
|
||||
// background for which reasonable black point estimates were made. The bp estimated at
|
||||
// the boundaries is used for the interior.
|
||||
|
||||
// The (min < bp) is arbitrary but works better than other heuristics that were tried.
|
||||
int averageNeighborBlackPoint =
|
||||
(blackPoints[y - 1][x] + (2 * blackPoints[y][x - 1]) + blackPoints[y - 1][x - 1]) / 4;
|
||||
if (min < averageNeighborBlackPoint) {
|
||||
average = averageNeighborBlackPoint;
|
||||
}
|
||||
}
|
||||
}
|
||||
blackPoints[y][x] = average;
|
||||
}
|
||||
}
|
||||
return blackPoints;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,346 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 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.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Class that converts a character string into a sequence of ECIs and bytes
|
||||
*
|
||||
* The implementation uses the Dijkstra algorithm to produce minimal encodings
|
||||
*
|
||||
* @author Alex Geller
|
||||
*/
|
||||
public class MinimalECIInput implements ECIInput {
|
||||
|
||||
private static final int COST_PER_ECI = 3; // approximated (latch + 2 codewords)
|
||||
private final int[] bytes;
|
||||
private final int fnc1;
|
||||
|
||||
/**
|
||||
* Constructs a minimal input
|
||||
*
|
||||
* @param stringToEncode the character string to encode
|
||||
* @param priorityCharset The preferred {@link Charset}. When the value of the argument is null, the algorithm
|
||||
* chooses charsets that leads to a minimal representation. Otherwise the algorithm will use the priority
|
||||
* charset to encode any character in the input that can be encoded by it if the charset is among the
|
||||
* supported charsets.
|
||||
* @param fnc1 denotes the character in the input that represents the FNC1 character or -1 if this is not GS1
|
||||
* input.
|
||||
*/
|
||||
public MinimalECIInput(String stringToEncode, Charset priorityCharset, int fnc1) {
|
||||
this.fnc1 = fnc1;
|
||||
ECIEncoderSet encoderSet = new ECIEncoderSet(stringToEncode, priorityCharset, fnc1);
|
||||
if (encoderSet.length() == 1) { //optimization for the case when all can be encoded without ECI in ISO-8859-1
|
||||
bytes = new int[stringToEncode.length()];
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
char c = stringToEncode.charAt(i);
|
||||
bytes[i] = c == fnc1 ? 1000 : (int) c;
|
||||
}
|
||||
} else {
|
||||
bytes = encodeMinimally(stringToEncode, encoderSet, fnc1);
|
||||
}
|
||||
}
|
||||
|
||||
public int getFNC1Character() {
|
||||
return fnc1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the length of this input. The length is the number
|
||||
* of {@code byte}s, FNC1 characters or ECIs in the sequence.
|
||||
*
|
||||
* @return the number of {@code char}s in this sequence
|
||||
*/
|
||||
public int length() {
|
||||
return bytes.length;
|
||||
}
|
||||
|
||||
public boolean haveNCharacters(int index, int n) {
|
||||
if (index + n - 1 >= bytes.length) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (isECI(index + i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@code byte} value at the specified index. An index ranges from zero
|
||||
* to {@code length() - 1}. The first {@code byte} value of the sequence is at
|
||||
* index zero, the next at index one, and so on, as for array
|
||||
* indexing.
|
||||
*
|
||||
* @param index the index of the {@code byte} value to be returned
|
||||
*
|
||||
* @return the specified {@code byte} value as character or the FNC1 character
|
||||
*
|
||||
* @throws IndexOutOfBoundsException
|
||||
* if the {@code index} argument is negative or not less than
|
||||
* {@code length()}
|
||||
* @throws IllegalArgumentException
|
||||
* if the value at the {@code index} argument is an ECI (@see #isECI)
|
||||
*/
|
||||
public char charAt(int index) {
|
||||
if (index < 0 || index >= length()) {
|
||||
throw new IndexOutOfBoundsException("" + index);
|
||||
}
|
||||
if (isECI(index)) {
|
||||
throw new IllegalArgumentException("value at " + index + " is not a character but an ECI");
|
||||
}
|
||||
return isFNC1(index) ? (char) fnc1 : (char) bytes[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@code CharSequence} that is a subsequence of this sequence.
|
||||
* The subsequence starts with the {@code char} value at the specified index and
|
||||
* ends with the {@code char} value at index {@code end - 1}. The length
|
||||
* (in {@code char}s) of the
|
||||
* returned sequence is {@code end - start}, so if {@code start == end}
|
||||
* then an empty sequence is returned.
|
||||
*
|
||||
* @param start the start index, inclusive
|
||||
* @param end the end index, exclusive
|
||||
*
|
||||
* @return the specified subsequence
|
||||
*
|
||||
* @throws IndexOutOfBoundsException
|
||||
* if {@code start} or {@code end} are negative,
|
||||
* if {@code end} is greater than {@code length()},
|
||||
* or if {@code start} is greater than {@code end}
|
||||
* @throws IllegalArgumentException
|
||||
* if a value in the range {@code start}-{@code end} is an ECI (@see #isECI)
|
||||
*/
|
||||
public CharSequence subSequence(int start, int end) {
|
||||
if (start < 0 || start > end || end > length()) {
|
||||
throw new IndexOutOfBoundsException("" + start);
|
||||
}
|
||||
StringBuilder result = new StringBuilder();
|
||||
for (int i = start; i < end; i++) {
|
||||
if (isECI(i)) {
|
||||
throw new IllegalArgumentException("value at " + i + " is not a character but an ECI");
|
||||
}
|
||||
result.append(charAt(i));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a value is an ECI
|
||||
*
|
||||
* @param index the index of the value
|
||||
*
|
||||
* @return true if the value at position {@code index} is an ECI
|
||||
*
|
||||
* @throws IndexOutOfBoundsException
|
||||
* if the {@code index} argument is negative or not less than
|
||||
* {@code length()}
|
||||
*/
|
||||
public boolean isECI(int index) {
|
||||
if (index < 0 || index >= length()) {
|
||||
throw new IndexOutOfBoundsException("" + index);
|
||||
}
|
||||
return bytes[index] > 255 && bytes[index] <= 999;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a value is the FNC1 character
|
||||
*
|
||||
* @param index the index of the value
|
||||
*
|
||||
* @return true if the value at position {@code index} is the FNC1 character
|
||||
*
|
||||
* @throws IndexOutOfBoundsException
|
||||
* if the {@code index} argument is negative or not less than
|
||||
* {@code length()}
|
||||
*/
|
||||
public boolean isFNC1(int index) {
|
||||
if (index < 0 || index >= length()) {
|
||||
throw new IndexOutOfBoundsException("" + index);
|
||||
}
|
||||
return bytes[index] == 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@code int} ECI value at the specified index. An index ranges from zero
|
||||
* to {@code length() - 1}. The first {@code byte} value of the sequence is at
|
||||
* index zero, the next at index one, and so on, as for array
|
||||
* indexing.
|
||||
*
|
||||
* @param index the index of the {@code int} value to be returned
|
||||
*
|
||||
* @return the specified {@code int} ECI value.
|
||||
* The ECI specified the encoding of all bytes with a higher index until the
|
||||
* next ECI or until the end of the input if no other ECI follows.
|
||||
*
|
||||
* @throws IndexOutOfBoundsException
|
||||
* if the {@code index} argument is negative or not less than
|
||||
* {@code length()}
|
||||
* @throws IllegalArgumentException
|
||||
* if the value at the {@code index} argument is not an ECI (@see #isECI)
|
||||
*/
|
||||
public int getECIValue(int index) {
|
||||
if (index < 0 || index >= length()) {
|
||||
throw new IndexOutOfBoundsException("" + index);
|
||||
}
|
||||
if (!isECI(index)) {
|
||||
throw new IllegalArgumentException("value at " + index + " is not an ECI but a character");
|
||||
}
|
||||
return bytes[index] - 256;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder result = new StringBuilder();
|
||||
for (int i = 0; i < length(); i++) {
|
||||
if (i > 0) {
|
||||
result.append(", ");
|
||||
}
|
||||
if (isECI(i)) {
|
||||
result.append("ECI(");
|
||||
result.append(getECIValue(i));
|
||||
result.append(')');
|
||||
} else if (charAt(i) < 128) {
|
||||
result.append('\'');
|
||||
result.append(charAt(i));
|
||||
result.append('\'');
|
||||
} else {
|
||||
result.append((int) charAt(i));
|
||||
}
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
static void addEdge(InputEdge[][] edges, int to, InputEdge edge) {
|
||||
if (edges[to][edge.encoderIndex] == null ||
|
||||
edges[to][edge.encoderIndex].cachedTotalSize > edge.cachedTotalSize) {
|
||||
edges[to][edge.encoderIndex] = edge;
|
||||
}
|
||||
}
|
||||
|
||||
static void addEdges(String stringToEncode,
|
||||
ECIEncoderSet encoderSet,
|
||||
InputEdge[][] edges,
|
||||
int from,
|
||||
InputEdge previous,
|
||||
int fnc1) {
|
||||
|
||||
char ch = stringToEncode.charAt(from);
|
||||
|
||||
int start = 0;
|
||||
int end = encoderSet.length();
|
||||
if (encoderSet.getPriorityEncoderIndex() >= 0 && (ch == fnc1 || encoderSet.canEncode(ch,
|
||||
encoderSet.getPriorityEncoderIndex()))) {
|
||||
start = encoderSet.getPriorityEncoderIndex();
|
||||
end = start + 1;
|
||||
}
|
||||
|
||||
for (int i = start; i < end; i++) {
|
||||
if (ch == fnc1 || encoderSet.canEncode(ch,i)) {
|
||||
addEdge(edges, from + 1, new InputEdge(ch, encoderSet, i, previous, fnc1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int[] encodeMinimally(String stringToEncode, ECIEncoderSet encoderSet, int fnc1) {
|
||||
int inputLength = stringToEncode.length();
|
||||
|
||||
// Array that represents vertices. There is a vertex for every character and encoding.
|
||||
InputEdge[][] edges = new InputEdge[inputLength + 1][encoderSet.length()];
|
||||
addEdges(stringToEncode, encoderSet, edges, 0, null, fnc1);
|
||||
|
||||
for (int i = 1; i <= inputLength; i++) {
|
||||
for (int j = 0; j < encoderSet.length(); j++) {
|
||||
if (edges[i][j] != null && i < inputLength) {
|
||||
addEdges(stringToEncode, encoderSet, edges, i, edges[i][j], fnc1);
|
||||
}
|
||||
}
|
||||
//optimize memory by removing edges that have been passed.
|
||||
for (int j = 0; j < encoderSet.length(); j++) {
|
||||
edges[i - 1][j] = null;
|
||||
}
|
||||
}
|
||||
int minimalJ = -1;
|
||||
int minimalSize = Integer.MAX_VALUE;
|
||||
for (int j = 0; j < encoderSet.length(); j++) {
|
||||
if (edges[inputLength][j] != null) {
|
||||
InputEdge edge = edges[inputLength][j];
|
||||
if (edge.cachedTotalSize < minimalSize) {
|
||||
minimalSize = edge.cachedTotalSize;
|
||||
minimalJ = j;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (minimalJ < 0) {
|
||||
throw new RuntimeException("Internal error: failed to encode \"" + stringToEncode + "\"");
|
||||
}
|
||||
List<Integer> intsAL = new ArrayList<>();
|
||||
InputEdge current = edges[inputLength][minimalJ];
|
||||
while (current != null) {
|
||||
if (current.isFNC1()) {
|
||||
intsAL.add(0, 1000);
|
||||
} else {
|
||||
byte[] bytes = encoderSet.encode(current.c,current.encoderIndex);
|
||||
for (int i = bytes.length - 1; i >= 0; i--) {
|
||||
intsAL.add(0, (bytes[i] & 0xFF));
|
||||
}
|
||||
}
|
||||
int previousEncoderIndex = current.previous == null ? 0 : current.previous.encoderIndex;
|
||||
if (previousEncoderIndex != current.encoderIndex) {
|
||||
intsAL.add(0,256 + encoderSet.getECIValue(current.encoderIndex));
|
||||
}
|
||||
current = current.previous;
|
||||
}
|
||||
int[] ints = new int[intsAL.size()];
|
||||
for (int i = 0; i < ints.length; i++) {
|
||||
ints[i] = intsAL.get(i);
|
||||
}
|
||||
return ints;
|
||||
}
|
||||
|
||||
private static final class InputEdge {
|
||||
private final char c;
|
||||
private final int encoderIndex; //the encoding of this edge
|
||||
private final InputEdge previous;
|
||||
private final int cachedTotalSize;
|
||||
|
||||
private InputEdge(char c, ECIEncoderSet encoderSet, int encoderIndex, InputEdge previous, int fnc1) {
|
||||
this.c = c == fnc1 ? 1000 : c;
|
||||
this.encoderIndex = encoderIndex;
|
||||
this.previous = previous;
|
||||
|
||||
int size = this.c == 1000 ? 1 : encoderSet.encode(c, encoderIndex).length;
|
||||
int previousEncoderIndex = previous == null ? 0 : previous.encoderIndex;
|
||||
if (previousEncoderIndex != encoderIndex) {
|
||||
size += COST_PER_ECI;
|
||||
}
|
||||
if (previous != null) {
|
||||
size += previous.cachedTotalSize;
|
||||
}
|
||||
this.cachedTotalSize = size;
|
||||
}
|
||||
|
||||
boolean isFNC1() {
|
||||
return c == 1000;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
private final float a12;
|
||||
private final float a13;
|
||||
private final float a21;
|
||||
private final float a22;
|
||||
private final float a23;
|
||||
private final float a31;
|
||||
private final float a32;
|
||||
private final float 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) {
|
||||
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;
|
||||
int maxI = points.length - 1; // points.length must be even
|
||||
for (int i = 0; i < maxI; 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;
|
||||
}
|
||||
}
|
||||
|
||||
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 dx3 = x0 - x1 + x2 - x3;
|
||||
float dy3 = y0 - y1 + y2 - y3;
|
||||
if (dx3 == 0.0f && dy3 == 0.0f) {
|
||||
// Affine
|
||||
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 dy1 = y1 - y2;
|
||||
float dy2 = y3 - 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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,229 +0,0 @@
|
||||
/*
|
||||
* 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.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.zxing.DecodeHintType;
|
||||
|
||||
/**
|
||||
* Common string-related functions.
|
||||
*
|
||||
* @author Sean Owen
|
||||
* @author Alex Dupre
|
||||
*/
|
||||
public final class StringUtils {
|
||||
|
||||
private static final Charset PLATFORM_DEFAULT_ENCODING = Charset.defaultCharset();
|
||||
public static final Charset SHIFT_JIS_CHARSET = Charset.forName("SJIS");
|
||||
public static final Charset GB2312_CHARSET = Charset.forName("GB2312");
|
||||
private static final Charset EUC_JP = Charset.forName("EUC_JP");
|
||||
private static final boolean ASSUME_SHIFT_JIS =
|
||||
SHIFT_JIS_CHARSET.equals(PLATFORM_DEFAULT_ENCODING) ||
|
||||
EUC_JP.equals(PLATFORM_DEFAULT_ENCODING);
|
||||
|
||||
// Retained for ABI compatibility with earlier versions
|
||||
public static final String SHIFT_JIS = "SJIS";
|
||||
public static final String GB2312 = "GB2312";
|
||||
|
||||
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:
|
||||
* "SJIS", "UTF8", "ISO8859_1", or the platform default encoding if none
|
||||
* of these can possibly be correct
|
||||
*/
|
||||
public static String guessEncoding(byte[] bytes, Map<DecodeHintType,?> hints) {
|
||||
Charset c = guessCharset(bytes, hints);
|
||||
if (c == SHIFT_JIS_CHARSET) {
|
||||
return "SJIS";
|
||||
} else if (c == StandardCharsets.UTF_8) {
|
||||
return "UTF8";
|
||||
} else if (c == StandardCharsets.ISO_8859_1) {
|
||||
return "ISO8859_1";
|
||||
}
|
||||
return c.name();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bytes bytes encoding a string, whose encoding should be guessed
|
||||
* @param hints decode hints if applicable
|
||||
* @return Charset of guessed encoding; at the moment will only guess one of:
|
||||
* {@link #SHIFT_JIS_CHARSET}, {@link StandardCharsets#UTF_8},
|
||||
* {@link StandardCharsets#ISO_8859_1}, {@link StandardCharsets#UTF_16},
|
||||
* or the platform default encoding if
|
||||
* none of these can possibly be correct
|
||||
*/
|
||||
public static Charset guessCharset(byte[] bytes, Map<DecodeHintType,?> hints) {
|
||||
if (hints != null && hints.containsKey(DecodeHintType.CHARACTER_SET)) {
|
||||
return Charset.forName(hints.get(DecodeHintType.CHARACTER_SET).toString());
|
||||
}
|
||||
|
||||
// First try UTF-16, assuming anything with its BOM is UTF-16
|
||||
if (bytes.length > 2 &&
|
||||
((bytes[0] == (byte) 0xFE && bytes[1] == (byte) 0xFF) ||
|
||||
(bytes[0] == (byte) 0xFF && bytes[1] == (byte) 0xFE))) {
|
||||
return StandardCharsets.UTF_16;
|
||||
}
|
||||
|
||||
// For now, merely tries to distinguish ISO-8859-1, UTF-8 and Shift_JIS,
|
||||
// which should be by far the most common encodings.
|
||||
int length = bytes.length;
|
||||
boolean canBeISO88591 = true;
|
||||
boolean canBeShiftJIS = true;
|
||||
boolean canBeUTF8 = true;
|
||||
int utf8BytesLeft = 0;
|
||||
int utf2BytesChars = 0;
|
||||
int utf3BytesChars = 0;
|
||||
int utf4BytesChars = 0;
|
||||
int sjisBytesLeft = 0;
|
||||
int sjisKatakanaChars = 0;
|
||||
int sjisCurKatakanaWordLength = 0;
|
||||
int sjisCurDoubleBytesWordLength = 0;
|
||||
int sjisMaxKatakanaWordLength = 0;
|
||||
int sjisMaxDoubleBytesWordLength = 0;
|
||||
int isoHighOther = 0;
|
||||
|
||||
boolean utf8bom = bytes.length > 3 &&
|
||||
bytes[0] == (byte) 0xEF &&
|
||||
bytes[1] == (byte) 0xBB &&
|
||||
bytes[2] == (byte) 0xBF;
|
||||
|
||||
for (int i = 0;
|
||||
i < length && (canBeISO88591 || canBeShiftJIS || canBeUTF8);
|
||||
i++) {
|
||||
|
||||
int value = bytes[i] & 0xFF;
|
||||
|
||||
// UTF-8 stuff
|
||||
if (canBeUTF8) {
|
||||
if (utf8BytesLeft > 0) {
|
||||
if ((value & 0x80) == 0) {
|
||||
canBeUTF8 = false;
|
||||
} else {
|
||||
utf8BytesLeft--;
|
||||
}
|
||||
} else if ((value & 0x80) != 0) {
|
||||
if ((value & 0x40) == 0) {
|
||||
canBeUTF8 = false;
|
||||
} else {
|
||||
utf8BytesLeft++;
|
||||
if ((value & 0x20) == 0) {
|
||||
utf2BytesChars++;
|
||||
} else {
|
||||
utf8BytesLeft++;
|
||||
if ((value & 0x10) == 0) {
|
||||
utf3BytesChars++;
|
||||
} else {
|
||||
utf8BytesLeft++;
|
||||
if ((value & 0x08) == 0) {
|
||||
utf4BytesChars++;
|
||||
} else {
|
||||
canBeUTF8 = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ISO-8859-1 stuff
|
||||
if (canBeISO88591) {
|
||||
if (value > 0x7F && value < 0xA0) {
|
||||
canBeISO88591 = false;
|
||||
} else if (value > 0x9F && (value < 0xC0 || value == 0xD7 || value == 0xF7)) {
|
||||
isoHighOther++;
|
||||
}
|
||||
}
|
||||
|
||||
// Shift_JIS stuff
|
||||
if (canBeShiftJIS) {
|
||||
if (sjisBytesLeft > 0) {
|
||||
if (value < 0x40 || value == 0x7F || value > 0xFC) {
|
||||
canBeShiftJIS = false;
|
||||
} else {
|
||||
sjisBytesLeft--;
|
||||
}
|
||||
} else if (value == 0x80 || value == 0xA0 || value > 0xEF) {
|
||||
canBeShiftJIS = false;
|
||||
} else if (value > 0xA0 && value < 0xE0) {
|
||||
sjisKatakanaChars++;
|
||||
sjisCurDoubleBytesWordLength = 0;
|
||||
sjisCurKatakanaWordLength++;
|
||||
if (sjisCurKatakanaWordLength > sjisMaxKatakanaWordLength) {
|
||||
sjisMaxKatakanaWordLength = sjisCurKatakanaWordLength;
|
||||
}
|
||||
} else if (value > 0x7F) {
|
||||
sjisBytesLeft++;
|
||||
//sjisDoubleBytesChars++;
|
||||
sjisCurKatakanaWordLength = 0;
|
||||
sjisCurDoubleBytesWordLength++;
|
||||
if (sjisCurDoubleBytesWordLength > sjisMaxDoubleBytesWordLength) {
|
||||
sjisMaxDoubleBytesWordLength = sjisCurDoubleBytesWordLength;
|
||||
}
|
||||
} else {
|
||||
//sjisLowChars++;
|
||||
sjisCurKatakanaWordLength = 0;
|
||||
sjisCurDoubleBytesWordLength = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (canBeUTF8 && utf8BytesLeft > 0) {
|
||||
canBeUTF8 = false;
|
||||
}
|
||||
if (canBeShiftJIS && sjisBytesLeft > 0) {
|
||||
canBeShiftJIS = false;
|
||||
}
|
||||
|
||||
// Easy -- if there is BOM or at least 1 valid not-single byte character (and no evidence it can't be UTF-8), done
|
||||
if (canBeUTF8 && (utf8bom || utf2BytesChars + utf3BytesChars + utf4BytesChars > 0)) {
|
||||
return StandardCharsets.UTF_8;
|
||||
}
|
||||
// Easy -- if assuming Shift_JIS or >= 3 valid consecutive not-ascii characters (and no evidence it can't be), done
|
||||
if (canBeShiftJIS && (ASSUME_SHIFT_JIS || sjisMaxKatakanaWordLength >= 3 || sjisMaxDoubleBytesWordLength >= 3)) {
|
||||
return SHIFT_JIS_CHARSET;
|
||||
}
|
||||
// Distinguishing Shift_JIS and ISO-8859-1 can be a little tough for short words. The crude heuristic is:
|
||||
// - If we saw
|
||||
// - only two consecutive katakana chars in the whole text, or
|
||||
// - at least 10% of bytes that could be "upper" not-alphanumeric Latin1,
|
||||
// - then we conclude Shift_JIS, else ISO-8859-1
|
||||
if (canBeISO88591 && canBeShiftJIS) {
|
||||
return (sjisMaxKatakanaWordLength == 2 && sjisKatakanaChars == 2) || isoHighOther * 10 >= length
|
||||
? SHIFT_JIS_CHARSET : StandardCharsets.ISO_8859_1;
|
||||
}
|
||||
|
||||
// Otherwise, try in order ISO-8859-1, Shift JIS, UTF-8 and fall back to default platform encoding
|
||||
if (canBeISO88591) {
|
||||
return StandardCharsets.ISO_8859_1;
|
||||
}
|
||||
if (canBeShiftJIS) {
|
||||
return SHIFT_JIS_CHARSET;
|
||||
}
|
||||
if (canBeUTF8) {
|
||||
return StandardCharsets.UTF_8;
|
||||
}
|
||||
// Otherwise, we take a wild guess with platform encoding
|
||||
return PLATFORM_DEFAULT_ENCODING;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012 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;
|
||||
|
||||
/**
|
||||
* General math-related and numeric utility functions.
|
||||
*/
|
||||
public final class MathUtils {
|
||||
|
||||
private MathUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Ends up being a bit faster than {@link Math#round(float)}. This merely rounds its
|
||||
* argument to the nearest int, where x.5 rounds up to x+1. Semantics of this shortcut
|
||||
* differ slightly from {@link Math#round(float)} in that half rounds down for negative
|
||||
* values. -2.5 rounds to -3, not -2. For purposes here it makes no difference.
|
||||
*
|
||||
* @param d real value to round
|
||||
* @return nearest {@code int}
|
||||
*/
|
||||
public static int round(float d) {
|
||||
return (int) (d + (d < 0.0f ? -0.5f : 0.5f));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param aX point A x coordinate
|
||||
* @param aY point A y coordinate
|
||||
* @param bX point B x coordinate
|
||||
* @param bY point B y coordinate
|
||||
* @return Euclidean distance between points A and B
|
||||
*/
|
||||
public static float distance(float aX, float aY, float bX, float bY) {
|
||||
double xDiff = aX - bX;
|
||||
double yDiff = aY - bY;
|
||||
return (float) Math.sqrt(xDiff * xDiff + yDiff * yDiff);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param aX point A x coordinate
|
||||
* @param aY point A y coordinate
|
||||
* @param bX point B x coordinate
|
||||
* @param bY point B y coordinate
|
||||
* @return Euclidean distance between points A and B
|
||||
*/
|
||||
public static float distance(int aX, int aY, int bX, int bY) {
|
||||
double xDiff = aX - bX;
|
||||
double yDiff = aY - bY;
|
||||
return (float) Math.sqrt(xDiff * xDiff + yDiff * yDiff);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array values to sum
|
||||
* @return sum of values in array
|
||||
*/
|
||||
public static int sum(int[] array) {
|
||||
int count = 0;
|
||||
for (int a : array) {
|
||||
count += a;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,217 +0,0 @@
|
||||
/*
|
||||
* 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
|
||||
* @deprecated without replacement since 3.3.0
|
||||
*/
|
||||
@Deprecated
|
||||
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 / 2;
|
||||
int halfWidth = width / 2;
|
||||
int deltaY = Math.max(1, height / (MAX_MODULES * 8));
|
||||
int deltaX = Math.max(1, width / (MAX_MODULES * 8));
|
||||
|
||||
int top = 0;
|
||||
int bottom = height;
|
||||
int left = 0;
|
||||
int right = width;
|
||||
ResultPoint pointA = findCornerFromCenter(halfWidth, 0, left, right,
|
||||
halfHeight, -deltaY, top, bottom, halfWidth / 2);
|
||||
top = (int) pointA.getY() - 1;
|
||||
ResultPoint pointB = findCornerFromCenter(halfWidth, -deltaX, left, right,
|
||||
halfHeight, 0, top, bottom, halfHeight / 2);
|
||||
left = (int) pointB.getX() - 1;
|
||||
ResultPoint pointC = findCornerFromCenter(halfWidth, deltaX, left, right,
|
||||
halfHeight, 0, top, bottom, halfHeight / 2);
|
||||
right = (int) pointC.getX() + 1;
|
||||
ResultPoint pointD = findCornerFromCenter(halfWidth, 0, left, right,
|
||||
halfHeight, deltaY, top, bottom, halfWidth / 2);
|
||||
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 / 4);
|
||||
|
||||
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 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(lastRange[deltaY > 0 ? 0 : 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, lastRange[deltaX < 0 ? 0 : 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) / 2;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,325 +0,0 @@
|
||||
/*
|
||||
* 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 = 10;
|
||||
private static final int CORR = 1;
|
||||
|
||||
private final BitMatrix image;
|
||||
private final int height;
|
||||
private final int width;
|
||||
private final int leftInit;
|
||||
private final int rightInit;
|
||||
private final int downInit;
|
||||
private final int upInit;
|
||||
|
||||
public WhiteRectangleDetector(BitMatrix image) throws NotFoundException {
|
||||
this(image, INIT_SIZE, image.getWidth() / 2, image.getHeight() / 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param image barcode image to find a rectangle in
|
||||
* @param initSize initial size of search area around center
|
||||
* @param x x position of search center
|
||||
* @param y y position of search center
|
||||
* @throws NotFoundException if image is too small to accommodate {@code initSize}
|
||||
*/
|
||||
public WhiteRectangleDetector(BitMatrix image, int initSize, int x, int y) throws NotFoundException {
|
||||
this.image = image;
|
||||
height = image.getHeight();
|
||||
width = image.getWidth();
|
||||
int halfsize = initSize / 2;
|
||||
leftInit = x - halfsize;
|
||||
rightInit = x + halfsize;
|
||||
upInit = y - halfsize;
|
||||
downInit = y + halfsize;
|
||||
if (upInit < 0 || leftInit < 0 || downInit >= height || rightInit >= width) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <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 = leftInit;
|
||||
int right = rightInit;
|
||||
int up = upInit;
|
||||
int down = downInit;
|
||||
boolean sizeExceeded = false;
|
||||
boolean aBlackPointFoundOnBorder = true;
|
||||
|
||||
boolean atLeastOneBlackPointFoundOnRight = false;
|
||||
boolean atLeastOneBlackPointFoundOnBottom = false;
|
||||
boolean atLeastOneBlackPointFoundOnLeft = false;
|
||||
boolean atLeastOneBlackPointFoundOnTop = false;
|
||||
|
||||
while (aBlackPointFoundOnBorder) {
|
||||
|
||||
aBlackPointFoundOnBorder = false;
|
||||
|
||||
// .....
|
||||
// . |
|
||||
// .....
|
||||
boolean rightBorderNotWhite = true;
|
||||
while ((rightBorderNotWhite || !atLeastOneBlackPointFoundOnRight) && right < width) {
|
||||
rightBorderNotWhite = containsBlackPoint(up, down, right, false);
|
||||
if (rightBorderNotWhite) {
|
||||
right++;
|
||||
aBlackPointFoundOnBorder = true;
|
||||
atLeastOneBlackPointFoundOnRight = true;
|
||||
} else if (!atLeastOneBlackPointFoundOnRight) {
|
||||
right++;
|
||||
}
|
||||
}
|
||||
|
||||
if (right >= width) {
|
||||
sizeExceeded = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// .....
|
||||
// . .
|
||||
// .___.
|
||||
boolean bottomBorderNotWhite = true;
|
||||
while ((bottomBorderNotWhite || !atLeastOneBlackPointFoundOnBottom) && down < height) {
|
||||
bottomBorderNotWhite = containsBlackPoint(left, right, down, true);
|
||||
if (bottomBorderNotWhite) {
|
||||
down++;
|
||||
aBlackPointFoundOnBorder = true;
|
||||
atLeastOneBlackPointFoundOnBottom = true;
|
||||
} else if (!atLeastOneBlackPointFoundOnBottom) {
|
||||
down++;
|
||||
}
|
||||
}
|
||||
|
||||
if (down >= height) {
|
||||
sizeExceeded = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// .....
|
||||
// | .
|
||||
// .....
|
||||
boolean leftBorderNotWhite = true;
|
||||
while ((leftBorderNotWhite || !atLeastOneBlackPointFoundOnLeft) && left >= 0) {
|
||||
leftBorderNotWhite = containsBlackPoint(up, down, left, false);
|
||||
if (leftBorderNotWhite) {
|
||||
left--;
|
||||
aBlackPointFoundOnBorder = true;
|
||||
atLeastOneBlackPointFoundOnLeft = true;
|
||||
} else if (!atLeastOneBlackPointFoundOnLeft) {
|
||||
left--;
|
||||
}
|
||||
}
|
||||
|
||||
if (left < 0) {
|
||||
sizeExceeded = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// .___.
|
||||
// . .
|
||||
// .....
|
||||
boolean topBorderNotWhite = true;
|
||||
while ((topBorderNotWhite || !atLeastOneBlackPointFoundOnTop) && up >= 0) {
|
||||
topBorderNotWhite = containsBlackPoint(left, right, up, true);
|
||||
if (topBorderNotWhite) {
|
||||
up--;
|
||||
aBlackPointFoundOnBorder = true;
|
||||
atLeastOneBlackPointFoundOnTop = true;
|
||||
} else if (!atLeastOneBlackPointFoundOnTop) {
|
||||
up--;
|
||||
}
|
||||
}
|
||||
|
||||
if (up < 0) {
|
||||
sizeExceeded = true;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!sizeExceeded) {
|
||||
|
||||
int maxSize = right - left;
|
||||
|
||||
ResultPoint z = null;
|
||||
for (int i = 1; z == null && i < maxSize; i++) {
|
||||
z = getBlackPointOnSegment(left, down - i, left + i, down);
|
||||
}
|
||||
|
||||
if (z == null) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
|
||||
ResultPoint t = null;
|
||||
//go down right
|
||||
for (int i = 1; t == null && i < maxSize; i++) {
|
||||
t = getBlackPointOnSegment(left, up + i, left + i, up);
|
||||
}
|
||||
|
||||
if (t == null) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
|
||||
ResultPoint x = null;
|
||||
//go down left
|
||||
for (int i = 1; x == null && i < maxSize; i++) {
|
||||
x = getBlackPointOnSegment(right, up + i, right - i, up);
|
||||
}
|
||||
|
||||
if (x == null) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
|
||||
ResultPoint y = null;
|
||||
//go up left
|
||||
for (int i = 1; y == null && i < maxSize; i++) {
|
||||
y = getBlackPointOnSegment(right, down - i, right - i, down);
|
||||
}
|
||||
|
||||
if (y == null) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
|
||||
return centerEdges(y, z, x, t);
|
||||
|
||||
} else {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
}
|
||||
|
||||
private ResultPoint getBlackPointOnSegment(float aX, float aY, float bX, float bY) {
|
||||
int dist = MathUtils.round(MathUtils.distance(aX, aY, bX, bY));
|
||||
float xStep = (bX - aX) / dist;
|
||||
float yStep = (bY - aY) / dist;
|
||||
|
||||
for (int i = 0; i < dist; i++) {
|
||||
int x = MathUtils.round(aX + i * xStep);
|
||||
int y = MathUtils.round(aY + i * yStep);
|
||||
if (image.get(x, y)) {
|
||||
return new ResultPoint(x, y);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.0f) {
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,166 +0,0 @@
|
||||
/*
|
||||
* 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 Fields. Operations use a given primitive polynomial in calculations.</p>
|
||||
*
|
||||
* <p>Throughout this package, elements of the GF are represented as an {@code int}
|
||||
* for convenience and speed (but at the cost of memory).
|
||||
* </p>
|
||||
*
|
||||
* @author Sean Owen
|
||||
* @author David Olivier
|
||||
*/
|
||||
public final class GenericGF {
|
||||
|
||||
public static final GenericGF AZTEC_DATA_12 = new GenericGF(0x1069, 4096, 1); // x^12 + x^6 + x^5 + x^3 + 1
|
||||
public static final GenericGF AZTEC_DATA_10 = new GenericGF(0x409, 1024, 1); // x^10 + x^3 + 1
|
||||
public static final GenericGF AZTEC_DATA_6 = new GenericGF(0x43, 64, 1); // x^6 + x + 1
|
||||
public static final GenericGF AZTEC_PARAM = new GenericGF(0x13, 16, 1); // x^4 + x + 1
|
||||
public static final GenericGF QR_CODE_FIELD_256 = new GenericGF(0x011D, 256, 0); // x^8 + x^4 + x^3 + x^2 + 1
|
||||
public static final GenericGF DATA_MATRIX_FIELD_256 = new GenericGF(0x012D, 256, 1); // x^8 + x^5 + x^3 + x^2 + 1
|
||||
public static final GenericGF AZTEC_DATA_8 = DATA_MATRIX_FIELD_256;
|
||||
public static final GenericGF MAXICODE_FIELD_64 = AZTEC_DATA_6;
|
||||
|
||||
private final int[] expTable;
|
||||
private final int[] logTable;
|
||||
private final GenericGFPoly zero;
|
||||
private final GenericGFPoly one;
|
||||
private final int size;
|
||||
private final int primitive;
|
||||
private final int generatorBase;
|
||||
|
||||
/**
|
||||
* Create a representation of GF(size) 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
|
||||
* @param size the size of the field
|
||||
* @param b the factor b in the generator polynomial can be 0- or 1-based
|
||||
* (g(x) = (x+a^b)(x+a^(b+1))...(x+a^(b+2t-1))).
|
||||
* In most cases it should be 1, but for QR code it is 0.
|
||||
*/
|
||||
public GenericGF(int primitive, int size, int b) {
|
||||
this.primitive = primitive;
|
||||
this.size = size;
|
||||
this.generatorBase = b;
|
||||
|
||||
expTable = new int[size];
|
||||
logTable = new int[size];
|
||||
int x = 1;
|
||||
for (int i = 0; i < size; i++) {
|
||||
expTable[i] = x;
|
||||
x *= 2; // we're assuming the generator alpha is 2
|
||||
if (x >= size) {
|
||||
x ^= primitive;
|
||||
x &= size - 1;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < size - 1; i++) {
|
||||
logTable[expTable[i]] = i;
|
||||
}
|
||||
// logTable[0] == 0 but this should never be used
|
||||
zero = new GenericGFPoly(this, new int[]{0});
|
||||
one = new GenericGFPoly(this, new int[]{1});
|
||||
}
|
||||
|
||||
GenericGFPoly getZero() {
|
||||
return zero;
|
||||
}
|
||||
|
||||
GenericGFPoly getOne() {
|
||||
return one;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the monomial representing coefficient * x^degree
|
||||
*/
|
||||
GenericGFPoly 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 GenericGFPoly(this, coefficients);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements both addition and subtraction -- they are the same in GF(size).
|
||||
*
|
||||
* @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(size)
|
||||
*/
|
||||
int exp(int a) {
|
||||
return expTable[a];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return base 2 log of a in GF(size)
|
||||
*/
|
||||
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[size - logTable[a] - 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return product of a and b in GF(size)
|
||||
*/
|
||||
int multiply(int a, int b) {
|
||||
if (a == 0 || b == 0) {
|
||||
return 0;
|
||||
}
|
||||
return expTable[(logTable[a] + logTable[b]) % (size - 1)];
|
||||
}
|
||||
|
||||
public int getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public int getGeneratorBase() {
|
||||
return generatorBase;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "GF(0x" + Integer.toHexString(primitive) + ',' + size + ')';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,271 +0,0 @@
|
||||
/*
|
||||
* 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 a GF.
|
||||
* 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 GenericGFPoly {
|
||||
|
||||
private final GenericGF field;
|
||||
private final int[] coefficients;
|
||||
|
||||
/**
|
||||
* @param field the {@link GenericGF} instance representing the field to use
|
||||
* to perform computations
|
||||
* @param coefficients coefficients as ints representing elements of GF(size), 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")
|
||||
*/
|
||||
GenericGFPoly(GenericGF field, int[] coefficients) {
|
||||
if (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 = new int[]{0};
|
||||
} 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);
|
||||
}
|
||||
if (a == 1) {
|
||||
// Just the sum of the coefficients
|
||||
int result = 0;
|
||||
for (int coefficient : coefficients) {
|
||||
result = GenericGF.addOrSubtract(result, coefficient);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
int result = coefficients[0];
|
||||
int size = coefficients.length;
|
||||
for (int i = 1; i < size; i++) {
|
||||
result = GenericGF.addOrSubtract(field.multiply(a, result), coefficients[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
GenericGFPoly addOrSubtract(GenericGFPoly other) {
|
||||
if (!field.equals(other.field)) {
|
||||
throw new IllegalArgumentException("GenericGFPolys do not have same GenericGF 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] = GenericGF.addOrSubtract(smallerCoefficients[i - lengthDiff], largerCoefficients[i]);
|
||||
}
|
||||
|
||||
return new GenericGFPoly(field, sumDiff);
|
||||
}
|
||||
|
||||
GenericGFPoly multiply(GenericGFPoly other) {
|
||||
if (!field.equals(other.field)) {
|
||||
throw new IllegalArgumentException("GenericGFPolys do not have same GenericGF 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] = GenericGF.addOrSubtract(product[i + j],
|
||||
field.multiply(aCoeff, bCoefficients[j]));
|
||||
}
|
||||
}
|
||||
return new GenericGFPoly(field, product);
|
||||
}
|
||||
|
||||
GenericGFPoly 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 GenericGFPoly(field, product);
|
||||
}
|
||||
|
||||
GenericGFPoly 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 GenericGFPoly(field, product);
|
||||
}
|
||||
|
||||
GenericGFPoly[] divide(GenericGFPoly other) {
|
||||
if (!field.equals(other.field)) {
|
||||
throw new IllegalArgumentException("GenericGFPolys do not have same GenericGF field");
|
||||
}
|
||||
if (other.isZero()) {
|
||||
throw new IllegalArgumentException("Divide by 0");
|
||||
}
|
||||
|
||||
GenericGFPoly quotient = field.getZero();
|
||||
GenericGFPoly 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);
|
||||
GenericGFPoly term = other.multiplyByMonomial(degreeDifference, scale);
|
||||
GenericGFPoly iterationQuotient = field.buildMonomial(degreeDifference, scale);
|
||||
quotient = quotient.addOrSubtract(iterationQuotient);
|
||||
remainder = remainder.addOrSubtract(term);
|
||||
}
|
||||
|
||||
return new GenericGFPoly[] { quotient, remainder };
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (isZero()) {
|
||||
return "0";
|
||||
}
|
||||
StringBuilder result = new StringBuilder(8 * getDegree());
|
||||
for (int degree = getDegree(); degree >= 0; degree--) {
|
||||
int coefficient = getCoefficient(degree);
|
||||
if (coefficient != 0) {
|
||||
if (coefficient < 0) {
|
||||
if (degree == getDegree()) {
|
||||
result.append("-");
|
||||
} else {
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,191 +0,0 @@
|
||||
/*
|
||||
* 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 GenericGF field;
|
||||
|
||||
public ReedSolomonDecoder(GenericGF 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 {
|
||||
GenericGFPoly poly = new GenericGFPoly(field, received);
|
||||
int[] syndromeCoefficients = new int[twoS];
|
||||
boolean noError = true;
|
||||
for (int i = 0; i < twoS; i++) {
|
||||
int eval = poly.evaluateAt(field.exp(i + field.getGeneratorBase()));
|
||||
syndromeCoefficients[syndromeCoefficients.length - 1 - i] = eval;
|
||||
if (eval != 0) {
|
||||
noError = false;
|
||||
}
|
||||
}
|
||||
if (noError) {
|
||||
return;
|
||||
}
|
||||
GenericGFPoly syndrome = new GenericGFPoly(field, syndromeCoefficients);
|
||||
GenericGFPoly[] sigmaOmega =
|
||||
runEuclideanAlgorithm(field.buildMonomial(twoS, 1), syndrome, twoS);
|
||||
GenericGFPoly sigma = sigmaOmega[0];
|
||||
GenericGFPoly omega = sigmaOmega[1];
|
||||
int[] errorLocations = findErrorLocations(sigma);
|
||||
int[] errorMagnitudes = findErrorMagnitudes(omega, errorLocations);
|
||||
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] = GenericGF.addOrSubtract(received[position], errorMagnitudes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private GenericGFPoly[] runEuclideanAlgorithm(GenericGFPoly a, GenericGFPoly b, int R)
|
||||
throws ReedSolomonException {
|
||||
// Assume a's degree is >= b's
|
||||
if (a.getDegree() < b.getDegree()) {
|
||||
GenericGFPoly temp = a;
|
||||
a = b;
|
||||
b = temp;
|
||||
}
|
||||
|
||||
GenericGFPoly rLast = a;
|
||||
GenericGFPoly r = b;
|
||||
GenericGFPoly tLast = field.getZero();
|
||||
GenericGFPoly t = field.getOne();
|
||||
|
||||
// Run Euclidean algorithm until r's degree is less than R/2
|
||||
while (2 * r.getDegree() >= R) {
|
||||
GenericGFPoly rLastLast = rLast;
|
||||
GenericGFPoly tLastLast = tLast;
|
||||
rLast = r;
|
||||
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;
|
||||
GenericGFPoly 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));
|
||||
}
|
||||
|
||||
t = q.multiply(tLast).addOrSubtract(tLastLast);
|
||||
|
||||
if (r.getDegree() >= rLast.getDegree()) {
|
||||
throw new IllegalStateException("Division algorithm failed to reduce polynomial? " +
|
||||
"r: " + r + ", rLast: " + rLast);
|
||||
}
|
||||
}
|
||||
|
||||
int sigmaTildeAtZero = t.getCoefficient(0);
|
||||
if (sigmaTildeAtZero == 0) {
|
||||
throw new ReedSolomonException("sigmaTilde(0) was zero");
|
||||
}
|
||||
|
||||
int inverse = field.inverse(sigmaTildeAtZero);
|
||||
GenericGFPoly sigma = t.multiply(inverse);
|
||||
GenericGFPoly omega = r.multiply(inverse);
|
||||
return new GenericGFPoly[]{sigma, omega};
|
||||
}
|
||||
|
||||
private int[] findErrorLocations(GenericGFPoly 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 < field.getSize() && 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(GenericGFPoly errorEvaluator, int[] errorLocations) {
|
||||
// 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,
|
||||
// GenericGF.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));
|
||||
if (field.getGeneratorBase() != 0) {
|
||||
result[i] = field.multiply(result[i], xiInverse);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
* 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.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p>Implements Reed-Solomon encoding, as the name implies.</p>
|
||||
*
|
||||
* @author Sean Owen
|
||||
* @author William Rucklidge
|
||||
*/
|
||||
public final class ReedSolomonEncoder {
|
||||
|
||||
private final GenericGF field;
|
||||
private final List<GenericGFPoly> cachedGenerators;
|
||||
|
||||
public ReedSolomonEncoder(GenericGF field) {
|
||||
this.field = field;
|
||||
this.cachedGenerators = new ArrayList<>();
|
||||
cachedGenerators.add(new GenericGFPoly(field, new int[]{1}));
|
||||
}
|
||||
|
||||
private GenericGFPoly buildGenerator(int degree) {
|
||||
if (degree >= cachedGenerators.size()) {
|
||||
GenericGFPoly lastGenerator = cachedGenerators.get(cachedGenerators.size() - 1);
|
||||
for (int d = cachedGenerators.size(); d <= degree; d++) {
|
||||
GenericGFPoly nextGenerator = lastGenerator.multiply(
|
||||
new GenericGFPoly(field, new int[] { 1, field.exp(d - 1 + field.getGeneratorBase()) }));
|
||||
cachedGenerators.add(nextGenerator);
|
||||
lastGenerator = nextGenerator;
|
||||
}
|
||||
}
|
||||
return cachedGenerators.get(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");
|
||||
}
|
||||
GenericGFPoly generator = buildGenerator(ecBytes);
|
||||
int[] infoCoefficients = new int[dataBytes];
|
||||
System.arraycopy(toEncode, 0, infoCoefficients, 0, dataBytes);
|
||||
GenericGFPoly info = new GenericGFPoly(field, infoCoefficients);
|
||||
info = info.multiplyByMonomial(ecBytes, 1);
|
||||
GenericGFPoly 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user