summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Gredler <daniel.gredler@gmail.com>2025-11-11 05:59:52 +0300
committerGitHub <noreply@github.com>2025-11-11 05:59:52 +0300
commita1ba421bb4b7aa5cd455e670e3983f809dd17112 (patch)
treefd5e90f848ca6d2a96b99635128a2d1258b15d03
parent2b1499246b11415889e4a02d65023b86201a0610 (diff)
downloadzxing-a1ba421bb4b7aa5cd455e670e3983f809dd17112.tar.xz
Add GrayscaleLuminanceSource (#2013)
-rw-r--r--core/src/main/java/com/google/zxing/GrayscaleLuminanceSource.java144
-rw-r--r--core/src/main/java/com/google/zxing/RGBLuminanceSource.java126
-rw-r--r--core/src/test/java/com/google/zxing/GrayscaleLuminanceSourceTestCase.java126
3 files changed, 276 insertions, 120 deletions
diff --git a/core/src/main/java/com/google/zxing/GrayscaleLuminanceSource.java b/core/src/main/java/com/google/zxing/GrayscaleLuminanceSource.java
new file mode 100644
index 000000000..7941bad81
--- /dev/null
+++ b/core/src/main/java/com/google/zxing/GrayscaleLuminanceSource.java
@@ -0,0 +1,144 @@
+/*
+ * 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;
+
+/**
+ * This class is used to help decode images from files which arrive as a grayscale
+ * pixel array.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ * @author Betaminos
+ */
+public class GrayscaleLuminanceSource extends LuminanceSource {
+
+ private final byte[] luminances;
+ private final int dataWidth;
+ private final int dataHeight;
+ private final int left;
+ private final int top;
+
+ public GrayscaleLuminanceSource(int width, int height, byte[] pixels) {
+ super(width, height);
+ luminances = pixels;
+ dataWidth = width;
+ dataHeight = height;
+ left = 0;
+ top = 0;
+ }
+
+ private GrayscaleLuminanceSource(byte[] pixels,
+ int dataWidth,
+ int dataHeight,
+ int left,
+ int top,
+ int width,
+ int height) {
+ super(width, height);
+ if (left + width > dataWidth || top + height > dataHeight) {
+ throw new IllegalArgumentException("Crop rectangle does not fit within image data.");
+ }
+ this.luminances = pixels;
+ this.dataWidth = dataWidth;
+ this.dataHeight = dataHeight;
+ this.left = left;
+ this.top = top;
+ }
+
+ @Override
+ public byte[] getRow(int y, byte[] row) {
+ if (y < 0 || y >= getHeight()) {
+ throw new IllegalArgumentException("Requested row is outside the image: " + y);
+ }
+ int width = getWidth();
+ if (row == null || row.length < width) {
+ row = new byte[width];
+ }
+ int offset = (y + top) * dataWidth + left;
+ System.arraycopy(luminances, offset, row, 0, width);
+ return row;
+ }
+
+ @Override
+ public byte[] getMatrix() {
+ int width = getWidth();
+ int height = getHeight();
+
+ // If the caller asks for the entire underlying image, save the copy and give them the
+ // original data. The docs specifically warn that result.length must be ignored.
+ if (width == dataWidth && height == dataHeight) {
+ return luminances;
+ }
+
+ int area = width * height;
+ byte[] matrix = new byte[area];
+ int inputOffset = top * dataWidth + left;
+
+ // If the width matches the full width of the underlying data, perform a single copy.
+ if (width == dataWidth) {
+ System.arraycopy(luminances, inputOffset, matrix, 0, area);
+ return matrix;
+ }
+
+ // Otherwise copy one cropped row at a time.
+ for (int y = 0; y < height; y++) {
+ int outputOffset = y * width;
+ System.arraycopy(luminances, inputOffset, matrix, outputOffset, width);
+ inputOffset += dataWidth;
+ }
+ return matrix;
+ }
+
+ @Override
+ public boolean isCropSupported() {
+ return true;
+ }
+
+ @Override
+ public LuminanceSource crop(int left, int top, int width, int height) {
+ return new GrayscaleLuminanceSource(luminances,
+ dataWidth,
+ dataHeight,
+ this.left + left,
+ this.top + top,
+ width,
+ height);
+ }
+
+ @Override
+ public boolean isRotateSupported() {
+ return true;
+ }
+
+ @Override
+ public LuminanceSource rotateCounterClockwise() {
+ byte[] rotated = new byte[luminances.length];
+ for (int y = 0; y < dataHeight; y++) {
+ for (int x = 0; x < dataWidth; x++) {
+ int i = (y * dataWidth) + x;
+ int x2 = y;
+ int y2 = dataWidth - 1 - x;
+ int j = (y2 * dataHeight) + x2;
+ rotated[j] = luminances[i];
+ }
+ }
+ int newWidth = getHeight();
+ int newHeight = getWidth();
+ int newLeft = top;
+ int newTop = dataWidth - (left + getWidth());
+ return new GrayscaleLuminanceSource(rotated, dataHeight, dataWidth, newLeft, newTop, newWidth, newHeight);
+ }
+}
diff --git a/core/src/main/java/com/google/zxing/RGBLuminanceSource.java b/core/src/main/java/com/google/zxing/RGBLuminanceSource.java
index 25ba4975e..5e04e2e1a 100644
--- a/core/src/main/java/com/google/zxing/RGBLuminanceSource.java
+++ b/core/src/main/java/com/google/zxing/RGBLuminanceSource.java
@@ -23,31 +23,18 @@ package com.google.zxing;
* @author dswitkin@google.com (Daniel Switkin)
* @author Betaminos
*/
-public final class RGBLuminanceSource extends LuminanceSource {
-
- private final byte[] luminances;
- private final int dataWidth;
- private final int dataHeight;
- private final int left;
- private final int top;
+public final class RGBLuminanceSource extends GrayscaleLuminanceSource {
public RGBLuminanceSource(int width, int height, int[] pixels) {
- super(width, height);
-
- dataWidth = width;
- dataHeight = height;
- left = 0;
- top = 0;
+ super(width, height, toGrayscale(width, height, pixels));
+ }
- // In order to measure pure decoding speed, we convert the entire image to a greyscale array
- // up front, which is the same as the Y channel of the YUVLuminanceSource in the real app.
- //
- // Total number of pixels suffices, can ignore shape
+ private static byte[] toGrayscale(int width, int height, int[] pixels) {
int size = width * height;
if (pixels == null || pixels.length < size) {
throw new IllegalArgumentException("Pixel array length is less than width * height");
}
- luminances = new byte[size];
+ byte[] luminances = new byte[size];
for (int offset = 0; offset < size; offset++) {
int pixel = pixels[offset];
int r = (pixel >> 16) & 0xff; // red
@@ -56,107 +43,6 @@ public final class RGBLuminanceSource extends LuminanceSource {
// Calculate green-favouring average cheaply
luminances[offset] = (byte) ((r + g2 + b) / 4);
}
- }
-
- private RGBLuminanceSource(byte[] pixels,
- int dataWidth,
- int dataHeight,
- int left,
- int top,
- int width,
- int height) {
- super(width, height);
- if (left + width > dataWidth || top + height > dataHeight) {
- throw new IllegalArgumentException("Crop rectangle does not fit within image data.");
- }
- this.luminances = pixels;
- this.dataWidth = dataWidth;
- this.dataHeight = dataHeight;
- this.left = left;
- this.top = top;
- }
-
- @Override
- public byte[] getRow(int y, byte[] row) {
- if (y < 0 || y >= getHeight()) {
- throw new IllegalArgumentException("Requested row is outside the image: " + y);
- }
- int width = getWidth();
- if (row == null || row.length < width) {
- row = new byte[width];
- }
- int offset = (y + top) * dataWidth + left;
- System.arraycopy(luminances, offset, row, 0, width);
- return row;
- }
-
- @Override
- public byte[] getMatrix() {
- int width = getWidth();
- int height = getHeight();
-
- // If the caller asks for the entire underlying image, save the copy and give them the
- // original data. The docs specifically warn that result.length must be ignored.
- if (width == dataWidth && height == dataHeight) {
- return luminances;
- }
-
- int area = width * height;
- byte[] matrix = new byte[area];
- int inputOffset = top * dataWidth + left;
-
- // If the width matches the full width of the underlying data, perform a single copy.
- if (width == dataWidth) {
- System.arraycopy(luminances, inputOffset, matrix, 0, area);
- return matrix;
- }
-
- // Otherwise copy one cropped row at a time.
- for (int y = 0; y < height; y++) {
- int outputOffset = y * width;
- System.arraycopy(luminances, inputOffset, matrix, outputOffset, width);
- inputOffset += dataWidth;
- }
- return matrix;
- }
-
- @Override
- public boolean isCropSupported() {
- return true;
- }
-
- @Override
- public LuminanceSource crop(int left, int top, int width, int height) {
- return new RGBLuminanceSource(luminances,
- dataWidth,
- dataHeight,
- this.left + left,
- this.top + top,
- width,
- height);
- }
-
- @Override
- public boolean isRotateSupported() {
- return true;
- }
-
- @Override
- public LuminanceSource rotateCounterClockwise() {
- byte[] rotated = new byte[luminances.length];
- for (int y = 0; y < dataHeight; y++) {
- for (int x = 0; x < dataWidth; x++) {
- int i = (y * dataWidth) + x;
- int x2 = y;
- int y2 = dataWidth - 1 - x;
- int j = (y2 * dataHeight) + x2;
- rotated[j] = luminances[i];
- }
- }
- int newWidth = getHeight();
- int newHeight = getWidth();
- int newLeft = top;
- int newTop = dataWidth - (left + getWidth());
- return new RGBLuminanceSource(rotated, dataHeight, dataWidth, newLeft, newTop, newWidth, newHeight);
+ return luminances;
}
}
diff --git a/core/src/test/java/com/google/zxing/GrayscaleLuminanceSourceTestCase.java b/core/src/test/java/com/google/zxing/GrayscaleLuminanceSourceTestCase.java
new file mode 100644
index 000000000..bd911f6d6
--- /dev/null
+++ b/core/src/test/java/com/google/zxing/GrayscaleLuminanceSourceTestCase.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2014 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Tests {@link GrayscaleLuminanceSource}.
+ */
+public final class GrayscaleLuminanceSourceTestCase extends Assert {
+
+ private static final GrayscaleLuminanceSource SOURCE =
+ new GrayscaleLuminanceSource(3, 3, new byte[] {
+ 0x00, 0x7F, (byte) 0xFF,
+ 0x3F, 0x7F, 0x3F,
+ 0x3F, 0x7F, 0x3F});
+
+ @Test
+ public void testCrop() {
+ assertTrue(SOURCE.isCropSupported());
+ LuminanceSource cropped = SOURCE.crop(1, 1, 1, 1);
+ assertEquals(1, cropped.getHeight());
+ assertEquals(1, cropped.getWidth());
+ assertArrayEquals(new byte[] { 0x7F }, cropped.getRow(0, null));
+ }
+
+ @Test
+ public void testRotate() {
+ assertTrue(SOURCE.isRotateSupported());
+ assertArrayEquals(new byte[] { 0x00, 0x7F, (byte) 0xFF}, SOURCE.getRow(0, null));
+ assertArrayEquals(new byte[] { 0x3F, 0x7F, 0x3F}, SOURCE.getRow(1, null));
+ assertArrayEquals(new byte[] { 0x3F, 0x7F, 0x3F}, SOURCE.getRow(2, null));
+ LuminanceSource rot90 = SOURCE.rotateCounterClockwise();
+ assertArrayEquals(new byte[] { (byte) 0xFF, 0x3F, 0x3F}, rot90.getRow(0, null));
+ assertArrayEquals(new byte[] { 0x7F, 0x7F, 0x7F}, rot90.getRow(1, null));
+ assertArrayEquals(new byte[] { 0x00, 0x3F, 0x3F}, rot90.getRow(2, null));
+ LuminanceSource rot180 = rot90.rotateCounterClockwise();
+ assertArrayEquals(new byte[] { 0x3F, 0x7F, 0x3F}, rot180.getRow(0, null));
+ assertArrayEquals(new byte[] { 0x3F, 0x7F, 0x3F}, rot180.getRow(1, null));
+ assertArrayEquals(new byte[] { (byte) 0xFF, 0x7F, 0x00}, rot180.getRow(2, null));
+ LuminanceSource rot270 = rot180.rotateCounterClockwise();
+ assertArrayEquals(new byte[] { 0x3F, 0x3F, 0x00}, rot270.getRow(0, null));
+ assertArrayEquals(new byte[] { 0x7F, 0x7F, 0x7F}, rot270.getRow(1, null));
+ assertArrayEquals(new byte[] { 0x3F, 0x3F, (byte) 0xFF}, rot270.getRow(2, null));
+ LuminanceSource rot360 = rot270.rotateCounterClockwise();
+ assertArrayEquals(new byte[] { 0x00, 0x7F, (byte) 0xFF}, rot360.getRow(0, null));
+ assertArrayEquals(new byte[] { 0x3F, 0x7F, 0x3F}, rot360.getRow(1, null));
+ assertArrayEquals(new byte[] { 0x3F, 0x7F, 0x3F}, rot360.getRow(2, null));
+ assertArrayEquals(SOURCE.getMatrix(), rot360.getMatrix());
+ }
+
+ @Test
+ public void testRotateCropped() {
+ assertTrue(SOURCE.isCropSupported());
+ assertTrue(SOURCE.isRotateSupported());
+ LuminanceSource cropped = SOURCE.crop(1, 1, 2, 2);
+ assertArrayEquals(new byte[] { 0x7F, 0x3F}, cropped.getRow(0, null));
+ assertArrayEquals(new byte[] { 0x7F, 0x3F}, cropped.getRow(1, null));
+ LuminanceSource rot90 = cropped.rotateCounterClockwise();
+ assertArrayEquals(new byte[] { 0x3F, 0x3F}, rot90.getRow(0, null));
+ assertArrayEquals(new byte[] { 0x7F, 0x7F}, rot90.getRow(1, null));
+ LuminanceSource rot180 = rot90.rotateCounterClockwise();
+ assertArrayEquals(new byte[] { 0x3F, 0x7F}, rot180.getRow(0, null));
+ assertArrayEquals(new byte[] { 0x3F, 0x7F}, rot180.getRow(1, null));
+ LuminanceSource rot270 = rot180.rotateCounterClockwise();
+ assertArrayEquals(new byte[] { 0x7F, 0x7F}, rot270.getRow(0, null));
+ assertArrayEquals(new byte[] { 0x3F, 0x3F}, rot270.getRow(1, null));
+ LuminanceSource rot360 = rot270.rotateCounterClockwise();
+ assertArrayEquals(new byte[] { 0x7F, 0x3F}, rot360.getRow(0, null));
+ assertArrayEquals(new byte[] { 0x7F, 0x3F}, rot360.getRow(1, null));
+ assertArrayEquals(cropped.getMatrix(), rot360.getMatrix());
+ }
+
+ @Test
+ public void testMatrix() {
+ assertArrayEquals(new byte[] { 0x00, 0x7F, (byte) 0xFF, 0x3F, 0x7F, 0x3F, 0x3F, 0x7F, 0x3F },
+ SOURCE.getMatrix());
+ LuminanceSource croppedFullWidth = SOURCE.crop(0, 1, 3, 2);
+ assertArrayEquals(new byte[] { 0x3F, 0x7F, 0x3F, 0x3F, 0x7F, 0x3F },
+ croppedFullWidth.getMatrix());
+ LuminanceSource croppedCorner = SOURCE.crop(1, 1, 2, 2);
+ assertArrayEquals(new byte[] { 0x7F, 0x3F, 0x7F, 0x3F },
+ croppedCorner.getMatrix());
+ }
+
+ @Test
+ public void testGetRow() {
+ assertArrayEquals(new byte[] { 0x3F, 0x7F, 0x3F }, SOURCE.getRow(2, new byte[3]));
+ }
+
+ @Test
+ public void testToString() {
+ assertEquals("#+ \n#+#\n#+#\n", SOURCE.toString());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testNullPixelArray() {
+ // Test regression: null pixel array should throw IllegalArgumentException
+ new RGBLuminanceSource(3, 3, null);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testPixelArrayTooSmall() {
+ // Test regression: pixel array smaller than width * height should throw IllegalArgumentException
+ int width = 3;
+ int height = 3;
+ int[] pixels = new int[width * height - 1]; // One pixel short
+ new RGBLuminanceSource(width, height, pixels);
+ }
+
+}