diff --git a/pom.xml b/pom.xml index 938c0933..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 + @@ -301,6 +334,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 + @@ -316,6 +353,14 @@ true + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + true + + 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/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(); 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(); 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 00000000..06e89a84 Binary files /dev/null and b/src/test/resources/LeptonicaFrameConverter/mat-bw-10x10.bmp differ diff --git a/src/test/resources/LeptonicaFrameConverter/mat-bw-10x11.bmp b/src/test/resources/LeptonicaFrameConverter/mat-bw-10x11.bmp new file mode 100644 index 00000000..091f7ceb Binary files /dev/null and b/src/test/resources/LeptonicaFrameConverter/mat-bw-10x11.bmp differ 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 00000000..ac981087 Binary files /dev/null and b/src/test/resources/LeptonicaFrameConverter/mat-bw-10x12.bmp differ 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 00000000..07a89b97 Binary files /dev/null and b/src/test/resources/LeptonicaFrameConverter/mat-bw-10x8.bmp differ 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 00000000..da21367b Binary files /dev/null and b/src/test/resources/LeptonicaFrameConverter/mat-bw-10x9.bmp differ diff --git a/src/test/resources/LeptonicaFrameConverter/mat-rgb-10x10.bmp b/src/test/resources/LeptonicaFrameConverter/mat-rgb-10x10.bmp new file mode 100644 index 00000000..837f5336 Binary files /dev/null and b/src/test/resources/LeptonicaFrameConverter/mat-rgb-10x10.bmp differ 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 00000000..5ec56025 Binary files /dev/null and b/src/test/resources/LeptonicaFrameConverter/mat-rgb-10x11.bmp differ 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 00000000..f3535727 Binary files /dev/null and b/src/test/resources/LeptonicaFrameConverter/mat-rgb-10x12.bmp differ 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 00000000..e6d49db5 Binary files /dev/null and b/src/test/resources/LeptonicaFrameConverter/mat-rgb-10x8.bmp differ 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 00000000..f7643f18 Binary files /dev/null and b/src/test/resources/LeptonicaFrameConverter/mat-rgb-10x9.bmp differ 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 00000000..2dc2ddd2 Binary files /dev/null and b/src/test/resources/LeptonicaFrameConverter/pix-bw-10x10.bmp differ diff --git a/src/test/resources/LeptonicaFrameConverter/pix-bw-10x11.bmp b/src/test/resources/LeptonicaFrameConverter/pix-bw-10x11.bmp new file mode 100644 index 00000000..5284fc17 Binary files /dev/null and b/src/test/resources/LeptonicaFrameConverter/pix-bw-10x11.bmp differ diff --git a/src/test/resources/LeptonicaFrameConverter/pix-bw-10x12.bmp b/src/test/resources/LeptonicaFrameConverter/pix-bw-10x12.bmp new file mode 100644 index 00000000..89db6ce4 Binary files /dev/null and b/src/test/resources/LeptonicaFrameConverter/pix-bw-10x12.bmp differ 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 00000000..b6a6aed7 Binary files /dev/null and b/src/test/resources/LeptonicaFrameConverter/pix-bw-10x8.bmp differ 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 00000000..666648e3 Binary files /dev/null and b/src/test/resources/LeptonicaFrameConverter/pix-bw-10x9.bmp differ diff --git a/src/test/resources/LeptonicaFrameConverter/pix-rgb-10x10.bmp b/src/test/resources/LeptonicaFrameConverter/pix-rgb-10x10.bmp new file mode 100644 index 00000000..4cdf370f Binary files /dev/null and b/src/test/resources/LeptonicaFrameConverter/pix-rgb-10x10.bmp differ 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 00000000..dc24a3fc Binary files /dev/null and b/src/test/resources/LeptonicaFrameConverter/pix-rgb-10x11.bmp differ 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 00000000..357292f5 Binary files /dev/null and b/src/test/resources/LeptonicaFrameConverter/pix-rgb-10x12.bmp differ diff --git a/src/test/resources/LeptonicaFrameConverter/pix-rgb-10x8.bmp b/src/test/resources/LeptonicaFrameConverter/pix-rgb-10x8.bmp new file mode 100644 index 00000000..5ee5131b Binary files /dev/null and b/src/test/resources/LeptonicaFrameConverter/pix-rgb-10x8.bmp differ diff --git a/src/test/resources/LeptonicaFrameConverter/pix-rgb-10x9.bmp b/src/test/resources/LeptonicaFrameConverter/pix-rgb-10x9.bmp new file mode 100644 index 00000000..b1e5b363 Binary files /dev/null and b/src/test/resources/LeptonicaFrameConverter/pix-rgb-10x9.bmp differ