From 84384d81f74d9f8093ab1000ecfec4226fa283b3 Mon Sep 17 00:00:00 2001 From: Marco Schuster Date: Sat, 27 Jan 2024 13:22:41 +0100 Subject: [PATCH 1/5] Fix Maven build (issue #2176) --- pom.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pom.xml b/pom.xml index 938c0933..e11b2cb4 100644 --- a/pom.xml +++ b/pom.xml @@ -301,6 +301,10 @@ https://jogamp.org/deployment/v2.3.2/javadoc/jogl/javadoc http://junit.org/junit4/javadoc/4.13.2 + + org/bytedeco/javacv/FlyCaptureFrameGrabber.java + org/bytedeco/javacv/FFmpegLockCallback.java + From aadc5fbb13224311547521834a0294cf1639d9ce Mon Sep 17 00:00:00 2001 From: Marco Schuster Date: Sat, 27 Jan 2024 13:38:55 +0100 Subject: [PATCH 2/5] Clean up more Javadoc warnings (issue #2176) --- .../java/org/bytedeco/javacv/FFmpegLogCallback.java | 2 +- src/main/java/org/bytedeco/javacv/Frame.java | 2 +- .../java/org/bytedeco/javacv/PS3EyeFrameGrabber.java | 12 +++++------- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/bytedeco/javacv/FFmpegLogCallback.java b/src/main/java/org/bytedeco/javacv/FFmpegLogCallback.java index 10947e95..593869ee 100644 --- a/src/main/java/org/bytedeco/javacv/FFmpegLogCallback.java +++ b/src/main/java/org/bytedeco/javacv/FFmpegLogCallback.java @@ -41,7 +41,7 @@ public class FFmpegLogCallback extends LogCallback { static final FFmpegLogCallback instance = new FFmpegLogCallback().retainReference(); - /** Returns an instance that can be used with {@link #setLogCallback(LogCallback)}. */ + /** Returns an instance that can be used with {@link org.bytedeco.ffmpeg.global.avutil#setLogCallback(LogCallback)}. */ public static FFmpegLogCallback getInstance() { return instance; } diff --git a/src/main/java/org/bytedeco/javacv/Frame.java b/src/main/java/org/bytedeco/javacv/Frame.java index ec506037..496d9022 100644 --- a/src/main/java/org/bytedeco/javacv/Frame.java +++ b/src/main/java/org/bytedeco/javacv/Frame.java @@ -213,7 +213,7 @@ public I createIndexer(boolean direct, int i) { * @return A deep copy of this frame. * @see {@link #cloneBufferArray} * - * @author Extension proposed by Dragos Dutu + * Extension proposed by Dragos Dutu * */ @Override public Frame clone() { diff --git a/src/main/java/org/bytedeco/javacv/PS3EyeFrameGrabber.java b/src/main/java/org/bytedeco/javacv/PS3EyeFrameGrabber.java index 24995a45..5d593012 100644 --- a/src/main/java/org/bytedeco/javacv/PS3EyeFrameGrabber.java +++ b/src/main/java/org/bytedeco/javacv/PS3EyeFrameGrabber.java @@ -118,7 +118,7 @@ public PS3EyeFrameGrabber() throws Exception { } /** Color mode, VGA resolution, 60 FPS frame rate. - * @param system wide camera index + * @param cameraIndex system wide camera index */ public PS3EyeFrameGrabber(int cameraIndex) throws Exception { this(cameraIndex, 640, 480, 60); @@ -274,9 +274,8 @@ public Frame grab() throws Exception { } - /** Start camera first (before grabbing). - * - * @return success/failure (true/false) + /** + * Start camera first (before grabbing). */ public void start() throws Exception { boolean b; @@ -301,9 +300,8 @@ public void start() throws Exception { } - /** Stop camera. It can be re-started if needed. - * - * @return success/failure (true/false) + /** + * Stop camera. It can be re-started if needed. */ public void stop() throws Exception { boolean b = camera.stopCamera(); From dac0b8a16c932682cef7b8cc0c15748c11023d9e Mon Sep 17 00:00:00 2001 From: Marco Schuster Date: Sat, 27 Jan 2024 15:44:06 +0100 Subject: [PATCH 3/5] add JUnit support --- pom.xml | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/pom.xml b/pom.xml index e11b2cb4..a731dcd7 100644 --- a/pom.xml +++ b/pom.xml @@ -58,6 +58,8 @@ UTF-8 yyyyMMddhhmm ${project.version} + 5.9.1 + 3.2.3 @@ -72,11 +74,23 @@ openblas 0.3.26-${javacpp.version} + + org.bytedeco + openblas-platform + 0.3.26-${javacpp.version} + test + org.bytedeco opencv 4.9.0-${javacpp.version} + + org.bytedeco + opencv-platform + 4.9.0-${javacpp.version} + test + org.bytedeco ffmpeg @@ -132,6 +146,12 @@ leptonica 1.84.1-${javacpp.version} + + org.bytedeco + leptonica-platform + 1.84.1-${javacpp.version} + test + org.bytedeco tesseract @@ -169,6 +189,19 @@ 2.3.2 true + + + org.junit.jupiter + junit-jupiter-engine + ${junit-jupiter-engine.version} + test + + + org.junit.jupiter + junit-jupiter-params + ${junit-jupiter-engine.version} + test + @@ -320,6 +353,14 @@ true + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + true + + From 2bf5cc9e88dfa47fef750c601f25a61f86156146 Mon Sep 17 00:00:00 2001 From: Marco Schuster Date: Sat, 27 Jan 2024 15:45:39 +0100 Subject: [PATCH 4/5] rework LeptonicaFrameConverter#convert(Frame) to handle non-aligned stride sizes, see issue #1115) --- .../javacv/LeptonicaFrameConverter.java | 128 +++++++++++++----- 1 file changed, 96 insertions(+), 32 deletions(-) diff --git a/src/main/java/org/bytedeco/javacv/LeptonicaFrameConverter.java b/src/main/java/org/bytedeco/javacv/LeptonicaFrameConverter.java index a3a7fb88..0e3421b4 100644 --- a/src/main/java/org/bytedeco/javacv/LeptonicaFrameConverter.java +++ b/src/main/java/org/bytedeco/javacv/LeptonicaFrameConverter.java @@ -22,14 +22,16 @@ package org.bytedeco.javacv; -import java.nio.Buffer; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; import org.bytedeco.javacpp.BytePointer; import org.bytedeco.javacpp.Loader; import org.bytedeco.javacpp.Pointer; +import org.bytedeco.leptonica.PIX; + +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.IntBuffer; -import org.bytedeco.leptonica.*; import static org.bytedeco.leptonica.global.leptonica.*; /** @@ -40,7 +42,9 @@ * @author Samuel Audet */ public class LeptonicaFrameConverter extends FrameConverter { - static { Loader.load(org.bytedeco.leptonica.global.leptonica.class); } + static { + Loader.load(org.bytedeco.leptonica.global.leptonica.class); + } PIX pix; BytePointer frameData, pixData; @@ -51,7 +55,7 @@ static boolean isEqual(Frame frame, PIX pix) { && frame.imageWidth == pix.w() && frame.imageHeight == pix.h() && frame.imageChannels == pix.d() / 8 && frame.imageDepth == Frame.DEPTH_UBYTE && (ByteOrder.nativeOrder().equals(ByteOrder.LITTLE_ENDIAN) - || new Pointer(frame.image[0]).address() == pix.data().address()) + || new Pointer(frame.image[0]).address() == pix.data().address()) && frame.imageStride * Math.abs(frame.imageDepth) / 8 == pix.wpl() * 4; } @@ -59,32 +63,91 @@ public PIX convert(Frame frame) { if (frame == null || frame.image == null) { return null; } else if (frame.opaque instanceof PIX) { - return (PIX)frame.opaque; + return (PIX) frame.opaque; } else if (!isEqual(frame, pix)) { - Pointer data; - if (ByteOrder.nativeOrder().equals(ByteOrder.LITTLE_ENDIAN)) { - if (pixData == null || pixData.capacity() < frame.imageHeight * frame.imageStride) { - if (pixData != null) { - pixData.releaseReference(); + //I simply lack a machine to test this. + if (ByteOrder.nativeOrder().equals(ByteOrder.BIG_ENDIAN)) { + System.err.println("This converter does not support running on big-endian machines"); + return null; + } + //PIX data should be packed as tightly as possible, see https://github.com/DanBloomberg/leptonica/blob/0d4477653691a8cb4f63fa751d43574c757ccc9f/src/pix.h#L133 + //For anything not greyscale or RGB @ 8 bit per pixel, this involves more bit-shift logic than I'm willing to write (and I lack test cases) + if (frame.imageChannels != 3 && frame.imageChannels != 1) { + System.out.println(String.format("Image has %d channels, converter only supports 3 (RGB) or 1 (grayscale) for input", frame.imageChannels)); + return null; + } + if (frame.imageDepth != 8 || !(frame.image[0] instanceof ByteBuffer)) { + System.out.println(String.format("Image has bit depth %d, converter only supports 8 (1 byte/px) for input", frame.imageDepth)); + return null; + } + // Leptonica frame scan lines must be padded to 32 bit / 4 bytes of stride (line) length, otherwise one gets nasty scan effects + // See http://www.leptonica.org/library-notes.html#PIX + int srcChannelDepthBytes = frame.imageDepth / 8; + int srcBytesPerPixel = srcChannelDepthBytes * frame.imageChannels; + + // Leptonica counts RGB images as 24 bits per pixel, while the data actually is 32 bit per pixel + int destBytesPerPixel = srcBytesPerPixel; + if (frame.imageChannels == 3) { + // RGB pixels are stored as RGBA, so they take up 4 bytes! + // See https://github.com/DanBloomberg/leptonica/blob/master/src/pix.h#L157 + destBytesPerPixel = 4; + } + int currentStrideLength = frame.imageWidth * destBytesPerPixel; + int targetStridePad = 4 - (currentStrideLength % 4); + if (targetStridePad == 4) + targetStridePad = 0; + int targetStrideLength = (currentStrideLength) + targetStridePad; + ByteBuffer src = ((ByteBuffer) frame.image[0]).order(ByteOrder.LITTLE_ENDIAN); + int newSize = targetStrideLength * frame.imageHeight; + ByteBuffer dst = ByteBuffer.allocate(newSize).order(ByteOrder.LITTLE_ENDIAN); + /* + System.out.println(String.format( + "src: %d bytes total, %d channels @ %d bytes per pixel, stride length %d", + frame.image[0].capacity(), + frame.imageChannels, + srcBytesPerPixel, + currentStrideLength + )); + System.out.println(String.format( + "dst: %d bytes total, stride length %d, stride pad %d", + newSize, + targetStrideLength, + targetStridePad + )); + */ + //The source bytes will be RGB, which means it will have to be copied byte-by-byte to match Leptonica RGBA + //todo: use qword copy ops? + byte[] rowData = new byte[targetStrideLength]; + for (int row = 0; row < frame.imageHeight; row++) { + for (int col = 0; col < frame.imageWidth; col++) { + int srcIndex = (frame.imageStride * row) + (col * frame.imageChannels); + if (frame.imageChannels == 1) { + byte v = src.get(srcIndex); + rowData[col] = v; + //System.out.println(String.format("row %03d col %03d idx src %03d val %02x", row, col, srcIndex,v)); + } else if (frame.imageChannels == 3) { + int dstIndex = col * destBytesPerPixel; + byte[] pixelData = new byte[3]; + src.position(srcIndex); + src.get(pixelData, 0, pixelData.length); + // Convert BGR (OpenCV's standard ordering) to RGB (Leptonica) + // See https://learnopencv.com/why-does-opencv-use-bgr-color-format/ and https://github.com/DanBloomberg/leptonica/blob/master/src/pix.h#L157 + rowData[dstIndex] = pixelData[2]; //dst: r + rowData[dstIndex + 1] = pixelData[1]; //dst: g + rowData[dstIndex + 2] = pixelData[0]; // dst: b + rowData[dstIndex + 3] = 0; + //System.out.println(String.format("row %03d col %03d idx src %03d dst %03d val r %02x g %02x b %02x", row, col, srcIndex,dstIndex, pixelData[2], pixelData[1], pixelData[1])); } - pixData = new BytePointer(frame.imageHeight * frame.imageStride).retainReference(); } - data = pixData; - pixBuffer = data.asByteBuffer().order(ByteOrder.BIG_ENDIAN); - } else { - data = new Pointer(frame.image[0].position(0)); - } - if (pix != null) { - pix.releaseReference(); + //And since pixel data in source is little-endian, but Leptonica is big-endian on 32-bit level, now invert accordingly... + ByteBuffer rowBuffer = ByteBuffer.wrap(rowData); + IntBuffer inverted = rowBuffer.order(ByteOrder.BIG_ENDIAN).asIntBuffer(); + //System.out.println(Arrays.toString(rowBuffer.array())); + dst.position(row * targetStrideLength).asIntBuffer().put(inverted); } - pix = PIX.create(frame.imageWidth, frame.imageHeight, frame.imageChannels * 8, data) - .wpl(frame.imageStride / 4 * Math.abs(frame.imageDepth) / 8).retainReference(); + pix = PIX.create(frame.imageWidth, frame.imageHeight, destBytesPerPixel * 8, new BytePointer(dst.position(0))); } - if (ByteOrder.nativeOrder().equals(ByteOrder.LITTLE_ENDIAN)) { - ((ByteBuffer)pixBuffer.position(0)).asIntBuffer() - .put(((ByteBuffer)frame.image[0].position(0)).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer()); - } return pix; } @@ -99,10 +162,10 @@ public Frame convert(PIX pix) { } else if (pix.d() < 8) { switch (pix.d()) { case 1: - tempPix = pix = pixConvert1To8(null, pix, (byte)0, (byte)255); + tempPix = pix = pixConvert1To8(null, pix, (byte) 0, (byte) 255); break; case 2: - tempPix = pix = pixConvert2To8(pix, (byte)0, (byte)85, (byte)170, (byte)255, 0); + tempPix = pix = pixConvert2To8(pix, (byte) 0, (byte) 85, (byte) 170, (byte) 255, 0); break; case 4: tempPix = pix = pixConvert4To8(pix, 0); @@ -128,7 +191,7 @@ public Frame convert(PIX pix) { } frameBuffer = frameData.asByteBuffer().order(ByteOrder.LITTLE_ENDIAN); frame.opaque = frameData; - frame.image = new Buffer[] { frameBuffer }; + frame.image = new Buffer[]{frameBuffer}; } else { if (tempPix != null) { if (this.pix != null) { @@ -137,12 +200,12 @@ public Frame convert(PIX pix) { this.pix = pix = pix.clone(); } frame.opaque = pix; - frame.image = new Buffer[] { pix.createBuffer() }; + frame.image = new Buffer[]{pix.createBuffer()}; } } if (ByteOrder.nativeOrder().equals(ByteOrder.LITTLE_ENDIAN)) { - ((ByteBuffer)frameBuffer.position(0)).asIntBuffer() + ((ByteBuffer) frameBuffer.position(0)).asIntBuffer() .put(pix.createBuffer().order(ByteOrder.BIG_ENDIAN).asIntBuffer()); } @@ -152,7 +215,8 @@ public Frame convert(PIX pix) { return frame; } - @Override public void close() { + @Override + public void close() { super.close(); if (pix != null) { pix.releaseReference(); From c0f53c5d429c4ce3d11606800b54fabcc5405fc6 Mon Sep 17 00:00:00 2001 From: Marco Schuster Date: Sat, 27 Jan 2024 15:47:27 +0100 Subject: [PATCH 5/5] add unit tests for LeptonicaFrameConverter#convert(Frame), see issue #1115 --- .../java/LeptonicaFrameConverterTest.java | 130 ++++++++++++++++++ .../LeptonicaFrameConverter/mat-bw-10x10.bmp | Bin 0 -> 1198 bytes .../LeptonicaFrameConverter/mat-bw-10x11.bmp | Bin 0 -> 1198 bytes .../LeptonicaFrameConverter/mat-bw-10x12.bmp | Bin 0 -> 1198 bytes .../LeptonicaFrameConverter/mat-bw-10x8.bmp | Bin 0 -> 1158 bytes .../LeptonicaFrameConverter/mat-bw-10x9.bmp | Bin 0 -> 1198 bytes .../LeptonicaFrameConverter/mat-rgb-10x10.bmp | Bin 0 -> 374 bytes .../LeptonicaFrameConverter/mat-rgb-10x11.bmp | Bin 0 -> 414 bytes .../LeptonicaFrameConverter/mat-rgb-10x12.bmp | Bin 0 -> 414 bytes .../LeptonicaFrameConverter/mat-rgb-10x8.bmp | Bin 0 -> 294 bytes .../LeptonicaFrameConverter/mat-rgb-10x9.bmp | Bin 0 -> 334 bytes .../LeptonicaFrameConverter/pix-bw-10x10.bmp | Bin 0 -> 1198 bytes .../LeptonicaFrameConverter/pix-bw-10x11.bmp | Bin 0 -> 1198 bytes .../LeptonicaFrameConverter/pix-bw-10x12.bmp | Bin 0 -> 1198 bytes .../LeptonicaFrameConverter/pix-bw-10x8.bmp | Bin 0 -> 1158 bytes .../LeptonicaFrameConverter/pix-bw-10x9.bmp | Bin 0 -> 1198 bytes .../LeptonicaFrameConverter/pix-rgb-10x10.bmp | Bin 0 -> 374 bytes .../LeptonicaFrameConverter/pix-rgb-10x11.bmp | Bin 0 -> 414 bytes .../LeptonicaFrameConverter/pix-rgb-10x12.bmp | Bin 0 -> 414 bytes .../LeptonicaFrameConverter/pix-rgb-10x8.bmp | Bin 0 -> 294 bytes .../LeptonicaFrameConverter/pix-rgb-10x9.bmp | Bin 0 -> 334 bytes 21 files changed, 130 insertions(+) create mode 100644 src/test/java/LeptonicaFrameConverterTest.java create mode 100644 src/test/resources/LeptonicaFrameConverter/mat-bw-10x10.bmp create mode 100644 src/test/resources/LeptonicaFrameConverter/mat-bw-10x11.bmp create mode 100644 src/test/resources/LeptonicaFrameConverter/mat-bw-10x12.bmp create mode 100644 src/test/resources/LeptonicaFrameConverter/mat-bw-10x8.bmp create mode 100644 src/test/resources/LeptonicaFrameConverter/mat-bw-10x9.bmp create mode 100644 src/test/resources/LeptonicaFrameConverter/mat-rgb-10x10.bmp create mode 100644 src/test/resources/LeptonicaFrameConverter/mat-rgb-10x11.bmp create mode 100644 src/test/resources/LeptonicaFrameConverter/mat-rgb-10x12.bmp create mode 100644 src/test/resources/LeptonicaFrameConverter/mat-rgb-10x8.bmp create mode 100644 src/test/resources/LeptonicaFrameConverter/mat-rgb-10x9.bmp create mode 100644 src/test/resources/LeptonicaFrameConverter/pix-bw-10x10.bmp create mode 100644 src/test/resources/LeptonicaFrameConverter/pix-bw-10x11.bmp create mode 100644 src/test/resources/LeptonicaFrameConverter/pix-bw-10x12.bmp create mode 100644 src/test/resources/LeptonicaFrameConverter/pix-bw-10x8.bmp create mode 100644 src/test/resources/LeptonicaFrameConverter/pix-bw-10x9.bmp create mode 100644 src/test/resources/LeptonicaFrameConverter/pix-rgb-10x10.bmp create mode 100644 src/test/resources/LeptonicaFrameConverter/pix-rgb-10x11.bmp create mode 100644 src/test/resources/LeptonicaFrameConverter/pix-rgb-10x12.bmp create mode 100644 src/test/resources/LeptonicaFrameConverter/pix-rgb-10x8.bmp create mode 100644 src/test/resources/LeptonicaFrameConverter/pix-rgb-10x9.bmp diff --git a/src/test/java/LeptonicaFrameConverterTest.java b/src/test/java/LeptonicaFrameConverterTest.java new file mode 100644 index 00000000..d888d0b4 --- /dev/null +++ b/src/test/java/LeptonicaFrameConverterTest.java @@ -0,0 +1,130 @@ +import org.bytedeco.javacv.Frame; +import org.bytedeco.javacv.LeptonicaFrameConverter; +import org.bytedeco.javacv.OpenCVFrameConverter; +import org.bytedeco.leptonica.PIX; +import org.bytedeco.leptonica.global.leptonica; +import org.bytedeco.opencv.global.opencv_imgcodecs; +import org.bytedeco.opencv.opencv_core.Mat; +import org.bytedeco.opencv.opencv_core.Point; +import org.bytedeco.opencv.opencv_core.Scalar; +import org.junit.jupiter.api.function.Executable; +import org.junit.jupiter.api.io.CleanupMode; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import static org.bytedeco.opencv.global.opencv_core.CV_8UC1; +import static org.bytedeco.opencv.global.opencv_core.CV_8UC3; +import static org.bytedeco.opencv.global.opencv_imgproc.line; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +/** + * These tests compare that the Frame to Leptonica PIX converter works correctly against known-good + * files that come in different stride lengths. + * + * @link Source bug + */ +public class LeptonicaFrameConverterTest { + @TempDir + static Path tempDir; + + @ParameterizedTest + @ValueSource(ints = {8, 9, 10, 11, 12}) + public void testBw(final int cols) { + LeptonicaFrameConverter lfcFixed = new LeptonicaFrameConverter(); + OpenCVFrameConverter.ToMat matConverter = new OpenCVFrameConverter.ToMat(); + + final int rows = 10; + + Mat originalImage = new Mat(rows, cols, CV_8UC1); + int stepX = 255 / cols; + int stepY = 255 / rows; + int stepTotal = Math.min(stepX, stepY); + for (int i = 0; i < originalImage.rows(); i++) { + for (int j = 0; j < originalImage.cols(); j++) { + line(originalImage, new Point(j, i), new Point(j, i), new Scalar(stepTotal * j)); + } + } + + //System.out.println(String.format("orig\n capacity %d\n w %d\n h %d\n ch %d\n dpt %d\n str %d", originalImage.asByteBuffer().capacity(), originalImage.cols(), originalImage.rows(), originalImage.channels(), originalImage.depth(), originalImage.step())); + opencv_imgcodecs.imwrite(tempDir + "/mat-bw-" + rows + "x" + cols + ".bmp", originalImage); + assertDoesNotThrow(new Executable() { + @Override + public void execute() throws Throwable { + assertArrayEquals( + Files.readAllBytes(Paths.get(getClass().getClassLoader().getResource("LeptonicaFrameConverter/mat-bw-" + rows + "x" + cols + ".bmp").getPath())), + Files.readAllBytes(Paths.get(tempDir + "/mat-bw-" + rows + "x" + cols + ".bmp")), + "Mat file differs"); + } + }); + + Frame ocrFrame = matConverter.convert(originalImage); + //System.out.println(String.format("frame\n capacity %d\n w %d\n h %d\n ch %d\n dpt %d\n str %d", ocrFrame.image[0].capacity(), ocrFrame.imageWidth, ocrFrame.imageHeight, ocrFrame.imageChannels, ocrFrame.imageDepth, ocrFrame.imageStride)); + + PIX converted = lfcFixed.convert(ocrFrame); + //System.out.println(String.format("fixconverted pix\n capacity %d\n w %d\n h %d\n ch %d\n dpt %d\n wpl %d", converted.createBuffer().capacity(), converted.w(), converted.h(), -1, converted.d(), converted.wpl())); + leptonica.pixWrite(tempDir + "/pix-bw-" + rows + "x" + cols + ".bmp", converted, leptonica.IFF_BMP); + assertDoesNotThrow(new Executable() { + @Override + public void execute() throws Throwable { + assertArrayEquals( + Files.readAllBytes(Paths.get(getClass().getClassLoader().getResource("LeptonicaFrameConverter/pix-bw-" + rows + "x" + cols + ".bmp").getPath())), + Files.readAllBytes(Paths.get(tempDir + "/pix-bw-" + rows + "x" + cols + ".bmp")), + "Pix file differs"); + } + }); + } + + @ParameterizedTest + @ValueSource(ints = {8, 9, 10, 11, 12}) + public void testRgb(final int cols) { + LeptonicaFrameConverter lfcFixed = new LeptonicaFrameConverter(); + OpenCVFrameConverter.ToMat matConverter = new OpenCVFrameConverter.ToMat(); + + final int rows = 10; + + Mat originalImage = new Mat(rows, cols, CV_8UC3); + int stepX = 255 / cols; + int stepY = 255 / rows; + for (int i = 0; i < originalImage.rows(); i++) { + for (int j = 0; j < originalImage.cols(); j++) { + // Warning: OpenCV uses BGR ordering under the hood! + // See https://learnopencv.com/why-does-opencv-use-bgr-color-format/ + line(originalImage, new Point(j, i), new Point(j, i), new Scalar(i * stepY, j * stepX, 0, 0)); + } + } + + //System.out.println(String.format("orig\n capacity %d\n w %d\n h %d\n ch %d\n dpt %d\n str %d", originalImage.asByteBuffer().capacity(), originalImage.cols(), originalImage.rows(), originalImage.channels(), originalImage.depth(), originalImage.step())); + opencv_imgcodecs.imwrite(tempDir + "/mat-rgb-" + rows + "x" + cols + ".bmp", originalImage); + assertDoesNotThrow(new Executable() { + @Override + public void execute() throws Throwable { + assertArrayEquals( + Files.readAllBytes(Paths.get(getClass().getClassLoader().getResource("LeptonicaFrameConverter/mat-rgb-" + rows + "x" + cols + ".bmp").getPath())), + Files.readAllBytes(Paths.get(tempDir + "/mat-rgb-" + rows + "x" + cols + ".bmp")), + "Mat file differs"); + } + }); + + Frame ocrFrame = matConverter.convert(originalImage); + //System.out.println(String.format("frame\n capacity %d\n w %d\n h %d\n ch %d\n dpt %d\n str %d", ocrFrame.image[0].capacity(), ocrFrame.imageWidth, ocrFrame.imageHeight, ocrFrame.imageChannels, ocrFrame.imageDepth, ocrFrame.imageStride)); + + PIX converted = lfcFixed.convert(ocrFrame); + //System.out.println(String.format("fixconverted pix\n capacity %d\n w %d\n h %d\n ch %d\n dpt %d\n wpl %d", converted.createBuffer().capacity(), converted.w(), converted.h(), -1, converted.d(), converted.wpl())); + leptonica.pixWrite(tempDir + "/pix-rgb-" + rows + "x" + cols + ".bmp", converted, leptonica.IFF_BMP); + assertDoesNotThrow(new Executable() { + @Override + public void execute() throws Throwable { + assertArrayEquals( + Files.readAllBytes(Paths.get(getClass().getClassLoader().getResource("LeptonicaFrameConverter/pix-rgb-" + rows + "x" + cols + ".bmp").getPath())), + Files.readAllBytes(Paths.get(tempDir + "/pix-rgb-" + rows + "x" + cols + ".bmp")), + "Pix file differs"); + } + }); + } +} diff --git a/src/test/resources/LeptonicaFrameConverter/mat-bw-10x10.bmp b/src/test/resources/LeptonicaFrameConverter/mat-bw-10x10.bmp new file mode 100644 index 0000000000000000000000000000000000000000..06e89a843490e82e16a5d79b5a48d11a5befdd22 GIT binary patch literal 1198 zcmchXXEYEB0EIs#A<-bClD3&qlq6DVhzdzX*_G8oRHBHCl#!NEk+f(N+S#Km4NBWA zN)+lj@4dh8@4M&Rd%tt;|7&ELA;{m->1o9t68+Z#2>&zuM+yiC(5qK3diU;)pr9aq z`t(6aNQl0D`ywnXjEIN`qN1Yo>(>u4F){l0?~k~+I1&;P3>YwgfddCJXwV=A4<3x9 zq$E;OQVba~1Zin$WMpKJm6b(KPL82Nhcax~Foq8wj=a1)BSwrsK|z6$BS$i7)F?)e z9?h6BV;DPjEaS$FWBmB>C@LzVq@;whvN9?vDyXWeqNb+Cgb5Q+S64?vLjz4sO|-PM zm^g7FlO|0m_L6$CMG5+uU^fXHEUSAb}g>1uDH3m;qLB^hldBAo}R2* zw~qDe*Rx^61~zWo$fix3@bdD)+uIu-A0IYv-i)uWFMfW0Y}vAfty{OUZQC~d{r%a# zeLDdG0R#pHvSY^%cJACsP*4!T!NKg>wTqCD5JE#k*}Z!=VPRo}hldjp5kX{RBzyMk zAu1}0y?ghvZ{I$mqoaw5i6J&Nmbkb$_V3?Me0)3y4jka%!Gjz+bcn-;4|C+m5fTy- zNK8y5DJhBMJ<73T$2flcI4LP9q^72F;=~Ek($YA2@+9f$>6|)siqof0bLPw$ z&YnHXxpU{p$jBfwGn4b@&vW6z1ukB^$fZk{xP19CSFT*)>eZ`cWo40_oz1mt*T~7q zAvZUdyu3WFU%$?c8#lOl^Cq`$-QxD`+uXTxhr4(0a_`HZcl~q*stZ32@GK!)-(K1SuWTdDRX(BVL z?Ah`E`}@6f?tAw6FG{pjDnKN1oW3>YwgfddCJXwV=eB_)xPl0sTq zn!$qyBO@cjkRd}DI&>(*h7ChjR+izzhcjZt2u6+^$*57I7(IG4a&mHv88e2lW5*&d zFOPzP0^`PwWBmB>C@LzVq@;whvN97UOkm=~iAK@2S65e7u3U+mn;Y)#?s#~3uxiyRR)~&f@b>n`$Hxa>UtczE+{mU)o7lX0Gh4Q7Ve8hdY}>Yt?c2ArW5*75?%c_) zUAx%5dpCRb?7`2^4}X7u0s;cqyLT`9_U$7uFp!|2AcBK~2?+@yG&Gd3urT)T-_L;q z2M7-jCn6$($jC^dqM|r>@F3CA(HuH-h{J~ubL7YoVq#)Adh{sAjveFp@#CC0ae~;` zSmNU1IC=6Ur%s(BK0cm=gal5XKFygkXGly;Bq=G04GOl00PI-AbH*VaZqN0MEH*a$5)-7(| zzRjIGcc`qa_`U0uz+d-u41|2{P}HPqJDQdd{Ug9i_&udk<}p@D}FAM)tY zBN`hUdHnb>O-)TSH#gJL(n4!%D{XCUw70kOTOh?Cj+2+qb-X_m21P-}B+a2R?rMNLN=E-QC@M`t*s;OEbu{QC8a-@kwJ=g%LIQPH#W4v8iA#%FNut?y8r+H literal 0 HcmV?d00001 diff --git a/src/test/resources/LeptonicaFrameConverter/mat-bw-10x12.bmp b/src/test/resources/LeptonicaFrameConverter/mat-bw-10x12.bmp new file mode 100644 index 0000000000000000000000000000000000000000..ac9810875e4fab1b7ef6ad39770440bd1a4c0f6c GIT binary patch literal 1198 zcmchLXH*Cb07Y*q4HczmNk$|)ZKH&UQcia6D67&C4br59N-`Tp0~rlTMiGj3 zG;G-&|G&TAJLkT4&oeg96yjfWdRD#1Bzh3*Qz|9_Q$fB?OE^+Hfk5FsHUgoTCa z-Mcp;A|i;2iXtW^hPb#mefso4LP7#bNlB!nr0CnXFa7%ULt0uI85tS+_wSFatSoYJ zats(SfPn)CA}=qGf`S5yii#*HDWR;a%%DMoP*G7~@ZiCys;V+%$Pk7O9m=p_!x%n% zIBIHYj2JP3kt0WB#*ZJ*gb5RvIB_DHnwm_S zGzl#&Ewr_@(b3UCS63H3Jw5dG^)WCoVDjY2Oqnu;sZ*yiZQ3*p4Gl3eGGhAl=@=Uu zGh@aKX3m_+tXZ=#F)?BG?AgqjGY3;sQ|8W{%e;B>Ff%j5+}xb`^XFq>VS%NkC015e zELgCBg$oyAZEcN>jSY(yEn@NF#VlE}gr!TDvTWHhmM>qq$C@>3Si5#D>(;Gf{rdIT+uP&d;J}6r8*p@VWaGw-Y}&Mm&6_u~Wy=m*>(;Go+qRAE+qdK6}JJ`8%C%(SE z`1$#0s{ltzkfeLK|uru2NMz!LTG3x2M!z{ zEG&$J2M==S&>_OZ!-EPDtP$tA(fStJbLtq$B!TLgwYC`}chK@PUsXKl16*Cq94v%$F};`1
    Chzxe(8H-G;80XYprYe(P6)V#{(|I_~hfc>_1 literal 0 HcmV?d00001 diff --git a/src/test/resources/LeptonicaFrameConverter/mat-bw-10x8.bmp b/src/test/resources/LeptonicaFrameConverter/mat-bw-10x8.bmp new file mode 100644 index 0000000000000000000000000000000000000000..07a89b971e1af05e54616899eae5639cc03feddc GIT binary patch literal 1158 zcmb`6XH*Cb07Y+Xn4 z>^(bve?Px>&VBct^YknN1o;=O9xL}Oksd^Q5cs$LUH@4E0s{2v)r;P}dm|_)h>(yF zefso4SXdYl5fS?K?Te_WDE<2NLrhGJ{{8ziV88&x#l?}3kU&yWl7RyUGHB2s1`i(0 zkRd~ml9FQR(4h<)HVkQLX=G$%7(RSBva+&_7%_s8BS#`9C&#E!qZmDUH1hKDC@3hP zsHljNk`iOajA889u_!AmGj7~C#*ZJ5ii!%Vs;a1|siCf}&V&gQm^g7FlO|0>CBihgPAjDGHcc>X3w6@oH=vQ z)6+v=UmpVl0}KrfF)}j3*w`2o6BA5LO))bw!`$4QxpU`YVPS!#r6pEYR?M3>kNNZG zV{L7Xjg1Wp7A(Nl)|Q0}7h-2;$D&1xu(!9z!NGyWix=bQ=*W^KOK@^>V(HSQEL*mW z<;$0|V#Nwpu3U+;vokI(F1Wh7;^yYYs#U9ScXwy?>eZ}Svjz_j57w?-i>Ie2>(;Gf z{rdHId3mv6!v;2P+=#cgH=8zX!pFyl&6_u~Wy=<}Zr#eZZQIzseLFjL?7-L8mz_Iz zvTN5acJJPepPwK8{{HOQvxmKV_p)!_J^}&)2n-A)C@6^F;9x>RLfF55KcS(a95`@* zg9i^178XW$csLOe5ky8ta_G<@4j(>DR8$nv(b2@j#1I=BOI%zWM~)mJK0cm=gai^3 z6G=)+A~`vkl#~>X9zDvjW5-BMO(iWYjpN6Ulb)W=i4!Nt$jBfwGn1^WEV8q+$;rtf zH#e7)Cr^@>m&d77r^wIG=k)2*6ciLtSXjuJGiNw^_AKYlo#XuZ^Ar^oQCwV1Nl6K% zrKMcBaDlS2GA>@c$fZk{C@(LkqN0M!moIbW$`!6&y~?#~*SLQDIyY|I;O5Pn+`4s( z%F0Trs;a22uBN7@hT7U%>gwvKudnC!?b|doG|<@C$elZPxO?|5O-)TSH#c+d-aT4c zTDX7zJ`Wx|ptZG?wzf7NK77cdM~`^?_%TnOJfXe4osNzUo<4obvuDqE{`@&FUcBJt z%a^=*^@`5UPP)3fc>Ve{-QC^1dGm(1Z{PCn-8xANct3BcDEf;`8UveEIT) ruV26N?b|oLfB(*pA3yl{^C!Q4{o?oU-~9RW2c$L3oqeP8{xAOpfIgdk literal 0 HcmV?d00001 diff --git a/src/test/resources/LeptonicaFrameConverter/mat-bw-10x9.bmp b/src/test/resources/LeptonicaFrameConverter/mat-bw-10x9.bmp new file mode 100644 index 0000000000000000000000000000000000000000..da21367b323eb3e55092161bc18be9c140e380e3 GIT binary patch literal 1198 zcmchXXFCuI0EJ(n&=AUKP!ifGN=2m*GE)jIGomdGG$<;CvKqFQ3Pp-SR5mH2th5(} z_IBOA->>&P&wI{!&JQ?xrl~^wl~zxx_B7ER#Ci}w7(RSB^78U1C@3&u#0W-?9EqZ$BBMr) zLP<%9(W6JBtgMWRiVCW#s*D*khOuMEGH%>B#*ZJ*gb5RvIB_DACQU+3O$~K*bu=_I zm^^thQ>IKoQ&STyEiI-_oyxRn)6mw|X8QE$%$PBQnKNgiqoaeat}e4?&0_ZK+02U2&n3%9&!2(Q8O))bw!`$2)3kwS@ zEiJLKvSQ)Fg;-l#vuM#G7B60mjg1XUmMmfE(xuqi+G1yChrPW$%a$!;`SRtgSh0eY zD_62=)hZku9B_1W#L3ACXJ=f*tl^cn>KA?^XAQXd3mvA%ND%7z1g~TE8DhhWBc~)?AWmbA0HoleSPus z^JC}Eo$T7Ri`~0-vuDp9{Qdn22nb;B-o5PGw~xTUK!SpT*uQ^22M!$I;K73g2M2TL z&>;>VK1@hR2%({&goTB1eMMtpFYi*GiNw^_AKYlog*bBh4bgn zbK$}TE?&GyYHBKJX=$XVr*rAjB`#mSOh!fqnVFefxpIZ9tSqixy~?#~*T~M!CMPF{ z>({Sy@PLO8AJWj! zKx1PgO-)TSH#gJL(!!%hk7#Xe~SdwV;OEbu{QC8a-@kwJ=g%LyySqV7!^FuqGOgs_bNCwSy2mtrnCo=3Ss69X7Cm4Q${f@bhJuh2r*xgZp{@K6u6V*00Tv zcRu)ho&Q$`Mi^y`aVD5#ifLvYy$G$e(M|`QbkR+puSF=OjB+Zdq>5_ld@e#NX{3`u zCRt>Y=VK8<2_u{cB8eiJIPZ(#iW}~D;E5OB_<36dQ_L{O0!ys0#?I>^sG^2C8fc=0 kHhNwbK@>5>kw6kDq>=L%duQA1OFPWYyDRP1`!VPL00bOab^rhX literal 0 HcmV?d00001 diff --git a/src/test/resources/LeptonicaFrameConverter/mat-rgb-10x11.bmp b/src/test/resources/LeptonicaFrameConverter/mat-rgb-10x11.bmp new file mode 100644 index 0000000000000000000000000000000000000000..5ec5602510d14a1f40a5258dba47128263b30e01 GIT binary patch literal 414 zcmZwDp>YHt5QX9YY*GNBgD3UGB)Fy2F=hMEzlAz(<-ggCT-KMDNqc>Q354VGNn>FWl}cf8UZng zLjsbJj8vo}6WPc$5uzbFVjw1BCRSo6PU0qB$4>0VUi18s0S5sRWT?<#!iH=90mPDHd;kCd literal 0 HcmV?d00001 diff --git a/src/test/resources/LeptonicaFrameConverter/mat-rgb-10x12.bmp b/src/test/resources/LeptonicaFrameConverter/mat-rgb-10x12.bmp new file mode 100644 index 0000000000000000000000000000000000000000..f3535727f03ea4e737c370a5feced1aee0ce882f GIT binary patch literal 414 zcmZvYp>04R6acTbsU_*#pg}%SOHxbf7y-i¨OR8pi~9G#j8;qd(QXTNWIaXjLR$*0EXHC{-T?P!p zunfltjKnC6%IJ*A*o;eqW@wh?Xn~e!g;r^uHffu7$&d}%k{vma6SVkLIsByQrxgEx4Kcldx$_=2zaj-U9AzbL3dE$YyKCbXax?dU`|dJzzV gSi~U#Nk~B|(vgX5oB#j- literal 0 HcmV?d00001 diff --git a/src/test/resources/LeptonicaFrameConverter/mat-rgb-10x8.bmp b/src/test/resources/LeptonicaFrameConverter/mat-rgb-10x8.bmp new file mode 100644 index 0000000000000000000000000000000000000000..e6d49db5a74793a2dcd4269b37bcb101af81be81 GIT binary patch literal 294 zcmZwCp-liW06@`)BTPV59YI$hE3g+vsOuQ*8fQ90eJ8jTDsK}mXnyj)|KC3!FaG#? z`uX_!`1bhX{ptVQ+|ixg#ogS)z1-Wy6i|rSHH%3qZ literal 0 HcmV?d00001 diff --git a/src/test/resources/LeptonicaFrameConverter/mat-rgb-10x9.bmp b/src/test/resources/LeptonicaFrameConverter/mat-rgb-10x9.bmp new file mode 100644 index 0000000000000000000000000000000000000000..f7643f18dd79e0c5d160a1f1bfcaa5c4b4de248a GIT binary patch literal 334 zcmZvRu~7ve6a@E&T7k?^fH8sdVpB#VN}vE5po14v0~t*aP{yYdPh)rXF4@no?+1T; z=-;&$?Nxj5rvIM{H*$?zxQ*-F$=%#192q&rDV)Y}&g5*4+LDoFtioz6XHC{-sSOz! z#wd)&aK>b8hFX`AZuCNLbf-`HrmIyMsYWf-Ms@0>ZmL?Ak!<8bZe%A<@+PY#8A(Pe jq(*YmByEyfkda`7LTCghOu{Cp{Sv{TKm&&f8=mk3nif!2 literal 0 HcmV?d00001 diff --git a/src/test/resources/LeptonicaFrameConverter/pix-bw-10x10.bmp b/src/test/resources/LeptonicaFrameConverter/pix-bw-10x10.bmp new file mode 100644 index 0000000000000000000000000000000000000000..2dc2ddd2ce3a5ffd3da148d5a65d6560ca251df1 GIT binary patch literal 1198 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 CkhT8+ literal 0 HcmV?d00001 diff --git a/src/test/resources/LeptonicaFrameConverter/pix-bw-10x8.bmp b/src/test/resources/LeptonicaFrameConverter/pix-bw-10x8.bmp new file mode 100644 index 0000000000000000000000000000000000000000..b6a6aed73172f067fec9514b51b4f73f4338c1ae GIT binary patch literal 1158 zcmb`6hc^%o0E8)(G>l3i6(S`KiH4RCNeE@6A__^G8nhHb$tILagN&AyRT&LLLZwZ} zsI*JtOWyBK_}zPV-`zWHBOf7wZfSH;xvL3xS)|K?-AzEir2G9Z1iO!a13^JSdi3Z) z&z?OI5)z_UuU_=--J3pr`XDSUOy9nJ5fKrgU%!6z@86#R0|qc~;6Ox0MG+GdLtI=O z2?+@X4I0GY!GjqxWC)Uyk_;U>lwre$Atfb+w6rubGBOMwJ{(zDSw@T)!N`##88vDY za&mIW%gdvnpup(Sqft~;L`g}BF=NJ{tgOt~v11uGZX7BqDvTdL9#vIUCQO*X#EBD` zG-(o(Cr?IAO^qp2rl78_&eW+>(a_L9Q&W>^)21I?c2xx{rmCu_U6EW0~|bf5FZ~Oe0_cK^Yi1-p+orl z`x6ilKww}XhYug-$dMxi1qE^R=uv`$g9!-g(u5k70RnpVb$;ikcGc%K{ ztSqv#v$=Nd8aX*R9p78YPQ=UD0#`EXTdGX=}FJHdo)vH(3*Vog~(7@~0uW4*- zq^YTi=H_PJym>=QOAD>7t+chZ(ca$9+qZ9d_wF6<-@m7$qk|70KJfA5M?QV}#OKeS z`SRrpU%!6k+qZ9g|NfmHKYq~J*~!nJKl%0R7r%f1=Fgu${F8u`nxTVNMB4wwzq`7c A-v9sr literal 0 HcmV?d00001 diff --git a/src/test/resources/LeptonicaFrameConverter/pix-bw-10x9.bmp b/src/test/resources/LeptonicaFrameConverter/pix-bw-10x9.bmp new file mode 100644 index 0000000000000000000000000000000000000000..666648e3ed24ae10db0ad3eb954e3d1b15f040c4 GIT binary patch literal 1198 zcmchX=T`^}7{#U1BpFSlgp!g{*)1Z;N~zFJW|_%|QX!#`jE33LQk0QZqEaDgkRl-s zQMTeIo%e5e?>YBAcYJ?LEK`MgbVI-Es&zTBE{bV=4i2%@5*h>3|IE-sFQgane3lJxG~n?8N|ASES5-@bj3mX@Yp zzkc-Z-=6^k1~72oKxAZOkd>81PEHPad3h8R6i`%DWYC~N3?4j~Aw!0sq@={qp+iwt zRz^idg<->np{lCN@ZrN5F=7NGM~*~IO$~K*bw-UEg@%R(qeqWMQ&STyEiJ~38H2XA zHe<(*MMp;mU0q$qjT?uao*w%8`WP4(Fn;`aCQO*X#EBD`G-(o(Cr@U|lqpP|Iu%1h zLyU}!Fg7;E#KZ(sQ&Xl*o5u9%(=jtMW5$db%$zwBb8~ZM&6gP z&6+iMdV1pJ<;B{yYgxB$9qZSxXTydKY}~k!O`A61?d{Fx&71M@@nOrBEo|Mom2KO$ zv3>h?e0_b{v1122ckaZ`&kuipe|GKKML<9Rfq{Vp1qHEt_ipy=*~8wwdkGE>X5YSj zgoK2!fB$|C95}$ig9ix>4J9lrj6;VGarp3IjvP5ccz8HRj~*o=B7$Sbj&c0>aZa2# z!O4>+iHwXSDk_TT=xAbMVmNi`6sJ$0=FFKhoIQJ%bLY-+{``4jV`GVniz7Zho`i%1 z5)%`-aNz<;Nl7FpCzFzrLTYL%X=!O(ym*oH^mH;ZGPrc<5|=MuCNndWtgI}qT)D#4 zt5>;p?HbpwUne^|n;SQ7aP#I(Zr!@Y?c2AxbLS2@IXUF!=5qJ$UGnnsxOeX!_wU~) zKR=&>f&vN)3wiM10Yybc6c-m$Qc}XhhYxx5=n;<}Kc=*_lqXM~P*zsP)2B}+5;*<_&M( zzUAG!cQiCK@c#XK8XFsFYHFgnxtW%h7CwCVKx=C&A3uKN)2C0gwYAaS-p=RGpZW6T z3tzu}<=eM!eEQAdGM#h$M<=Vu&S9q=Oso rc;JZ_-uU2)pLhc^%(1`{E3C1>7Q25*CQutq(lXjtC+TWEM!(|^T}NVp literal 0 HcmV?d00001 diff --git a/src/test/resources/LeptonicaFrameConverter/pix-rgb-10x11.bmp b/src/test/resources/LeptonicaFrameConverter/pix-rgb-10x11.bmp new file mode 100644 index 0000000000000000000000000000000000000000..dc24a3fcb8fe584a2720c3831bedd6743406ea3e GIT binary patch literal 414 zcmXYnp=|{q6h+Us?EsVwst||{&;&)g0TNM3~(fsq)6(HNaE8H;fkV+e>r91@U(6r>>?naDy8B2u9ms-p&Kq84hScIu=q z>Y>(}AsezI2XZ18awB*0BrozI=Nus#q9X=kA{JsJcH$&1;vvT9!5h582YkX8e8YGA q#4r58dvC!StiuLu!WL}9cI?D1?7`Z50Rs*KBq-3J!-NIrZ~p~3YH<_* literal 0 HcmV?d00001 diff --git a/src/test/resources/LeptonicaFrameConverter/pix-rgb-10x12.bmp b/src/test/resources/LeptonicaFrameConverter/pix-rgb-10x12.bmp new file mode 100644 index 0000000000000000000000000000000000000000..357292f5574207403735a4c01d82a841ebf6b622 GIT binary patch literal 414 zcmXYtF=~V%7)0N;A*~Q^5G*aW71ByNk6;lGp_dRWx3=>H_FwG{th`2kesSt$60OiGtS5o34$#HI`QgsUz(A==Fi`M zAHMec^{Z3?_^J$3Ilm literal 0 HcmV?d00001