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