Migrate common errors all over

This commit is contained in:
Henry
2022-08-12 22:18:58 -05:00
parent 2056221066
commit 328e691288
51 changed files with 4712 additions and 5065 deletions

View File

@@ -0,0 +1,359 @@
/*
* 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);
}
}

View File

@@ -0,0 +1,537 @@
/*
* 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());
}
}

View File

@@ -0,0 +1,111 @@
/*
* 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;
}
}

View File

@@ -0,0 +1,133 @@
/*
* 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);
}
}

View File

@@ -0,0 +1,176 @@
/*
* 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;
}
}

View File

@@ -0,0 +1,88 @@
/*
* 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;
}
}

View File

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

View File

@@ -0,0 +1,199 @@
/*
* 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());
}
}

View File

@@ -0,0 +1,107 @@
/*
* 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);
}

View File

@@ -0,0 +1,145 @@
/*
* 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();
}
}

View File

@@ -0,0 +1,203 @@
/*
* 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;
}
}

View File

@@ -0,0 +1,174 @@
/*
* 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;
}
}
}
}

View File

@@ -0,0 +1,237 @@
/*
* 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;
}
}

View File

@@ -0,0 +1,346 @@
/*
* 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;
}
}
}

View File

@@ -0,0 +1,156 @@
/*
* 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);
}
}

View File

@@ -0,0 +1,229 @@
/*
* 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;
}
}

View File

@@ -0,0 +1,78 @@
/*
* 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;
}
}

View File

@@ -0,0 +1,217 @@
/*
* 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;
}
}

View File

@@ -0,0 +1,325 @@
/*
* 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;
}
}

View File

@@ -0,0 +1,166 @@
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.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 + ')';
}
}

View File

@@ -0,0 +1,271 @@
/*
* 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();
}
}

View File

@@ -0,0 +1,191 @@
/*
* 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;
}
}

View File

@@ -0,0 +1,74 @@
/*
* 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);
}
}

View File

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