diff --git a/packages/dartcv/lib/src/imgcodecs/imgcodecs.dart b/packages/dartcv/lib/src/imgcodecs/imgcodecs.dart index e7b6e836..d0113ded 100644 --- a/packages/dartcv/lib/src/imgcodecs/imgcodecs.dart +++ b/packages/dartcv/lib/src/imgcodecs/imgcodecs.dart @@ -17,6 +17,38 @@ import '../g/constants.g.dart'; import '../g/imgcodecs.g.dart' as cvg; import '../native_lib.dart' show cimgcodecs; +/// Returns true if the specified image can be decoded by OpenCV. +/// +/// https://docs.opencv.org/4.10.0/d4/da8/group__imgcodecs.html#ga65d3569d8845d1210e1aeab8c199031c +bool haveImageReader(String filename) { + final cname = filename.toNativeUtf8().cast(); + final rval = cimgcodecs.cv_haveImageReader(cname); + calloc.free(cname); + return rval; +} + +/// Returns true if an image with the specified filename can be encoded by OpenCV. +/// +/// https://docs.opencv.org/4.10.0/d4/da8/group__imgcodecs.html#gac4fa9c4c32b58c55059752c0490d3f20 +bool haveImageWriter(String filename) { + final cname = filename.toNativeUtf8().cast(); + final rval = cimgcodecs.cv_haveImageWriter(cname); + calloc.free(cname); + return rval; +} + +/// Returns the number of images inside the give file. +/// +/// The function imcount will return the number of pages in a multi-page image, or 1 for single-page images +/// +/// https://docs.opencv.org/4.10.0/d4/da8/group__imgcodecs.html#ga02237b2aad2d4ae41c9489a83781f202 +int imcount(String filename, {int flags = IMREAD_ANYCOLOR}) { + final cname = filename.toNativeUtf8().cast(); + final rval = cimgcodecs.cv_imcount(cname, flags); + calloc.free(cname); + return rval; +} + /// read an image from a file into a Mat. /// The flags param is one of the IMReadFlag flags. /// If the image cannot be read (because of missing file, improper permissions, @@ -32,6 +64,7 @@ Mat imread(String filename, {int flags = IMREAD_COLOR}) { return dst; } +/// async version of [imread] Future imreadAsync(String filename, {int flags = IMREAD_COLOR}) async { final dst = Mat.empty(); final cname = filename.toNativeUtf8().cast(); @@ -61,6 +94,7 @@ bool imwrite(String filename, InputArray img, {VecI32? params}) { return rval; } +/// async version of [imwrite] Future imwriteAsync(String filename, InputArray img, {VecI32? params}) async { final fname = filename.toNativeUtf8().cast(); final p = calloc(); @@ -82,7 +116,7 @@ Future imwriteAsync(String filename, InputArray img, {VecI32? params}) asy ); } -/// IMEncode encodes an image Mat into a memory buffer. +/// imencode encodes an image Mat into a memory buffer. /// This function compresses the image and stores it in the returned memory buffer, /// using the image format passed in in the form of a file extension string. /// @@ -92,6 +126,18 @@ Future imwriteAsync(String filename, InputArray img, {VecI32? params}) asy String ext, InputArray img, { VecI32? params, +}) { + final (success, vec) = imencodeVec(ext, img, params: params); + final u8List = vec.toU8List(); // will copy data + vec.dispose(); + return (success, u8List); +} + +/// Same as [imencode] but returns [VecUChar] +(bool, VecUChar) imencodeVec( + String ext, + InputArray img, { + VecI32? params, }) { final buffer = calloc(); final pSuccess = calloc(); @@ -105,11 +151,9 @@ Future imwriteAsync(String filename, InputArray img, {VecI32? params}) asy calloc.free(pSuccess); final vec = VecUChar.fromPointer(buffer); - final u8List = vec.toU8List(); // will copy data - vec.dispose(); - return (success, u8List); + return (success, vec); } - +/// async version of [imencode] Future<(bool, Uint8List)> imencodeAsync( String ext, InputArray img, { @@ -142,23 +186,66 @@ Future<(bool, Uint8List)> imencodeAsync( ); } +/// Same as [imencodeAsync] but returns [VecUChar] +Future<(bool, VecUChar)> imencodeVecAsync( + String ext, + InputArray img, { + VecI32? params, +}) async { + final buffer = calloc(); + final pSuccess = calloc(); + final cExt = ext.toNativeUtf8().cast(); + + void completeFunc(Completer<(bool, VecUChar)> c) { + final success = pSuccess.value; + calloc.free(cExt); + calloc.free(pSuccess); + + final vec = VecUChar.fromPointer(buffer); + return c.complete((success, vec)); + } + + if (params == null) { + return cvRunAsync0( + (callback) => cimgcodecs.cv_imencode(cExt, img.ref, pSuccess, buffer, callback), + completeFunc, + ); + } + return cvRunAsync0( + (callback) => cimgcodecs.cv_imencode_1(cExt, img.ref, params.ref, pSuccess, buffer, callback), + completeFunc, + ); +} + /// imdecode reads an image from a buffer in memory. /// The function imdecode reads an image from the specified buffer in memory. /// If the buffer is too short or contains invalid data, the function /// returns an empty matrix. /// @param buf Input array or vector of bytes. /// @param flags The same flags as in cv::imread, see cv::ImreadModes. +/// /// For further details, please see: /// https://docs.opencv.org/master/d4/da8/group__imgcodecs.html#ga26a67788faa58ade337f8d28ba0eb19e Mat imdecode(Uint8List buf, int flags, {Mat? dst}) { final vec = VecUChar.fromList(buf); + return imdecodeVec(vec, flags, dst: dst); +} + +/// Same as [imdecode] but accepts [VecUChar] +Mat imdecodeVec(VecUChar buf, int flags, {Mat? dst}) { dst ??= Mat.empty(); - cvRun(() => cimgcodecs.cv_imdecode(vec.ref, flags, dst!.ptr, ffi.nullptr)); + cvRun(() => cimgcodecs.cv_imdecode(buf.ref, flags, dst!.ptr, ffi.nullptr)); return dst; } +/// async version of [imdecode] Future imdecodeAsync(Uint8List buf, int flags, {Mat? dst}) async { final vec = VecUChar.fromList(buf); + return imdecodeVecAsync(vec, flags, dst: dst); +} + +/// Same as [imdecodeAsync] but accepts [VecUChar] +Future imdecodeVecAsync(VecUChar vec, int flags, {Mat? dst}) async { dst ??= Mat.empty(); return cvRunAsync0( (callback) => cimgcodecs.cv_imdecode(vec.ref, flags, dst!.ptr, callback), diff --git a/packages/dartcv/test/images/avif_test.avif b/packages/dartcv/test/images/avif_test.avif new file mode 100644 index 00000000..9b96484f Binary files /dev/null and b/packages/dartcv/test/images/avif_test.avif differ diff --git a/packages/dartcv/test/imgcodecs_test.dart b/packages/dartcv/test/imgcodecs_test.dart index 2afd0085..b1557b04 100644 --- a/packages/dartcv/test/imgcodecs_test.dart +++ b/packages/dartcv/test/imgcodecs_test.dart @@ -4,6 +4,16 @@ import 'package:dartcv4/dartcv.dart' as cv; import 'package:test/test.dart'; void main() async { + test('cv.haveImageReader, cv.haveImageWriter', () { + expect(cv.haveImageReader("test/images/circles.jpg"), true); + expect(cv.haveImageReader("test/images/avif_test.avif"), false); + expect(cv.haveImageWriter("test/images/circles.jpg"), true); + }); + + test('cv.imcount', () { + expect(cv.imcount("test/images/circles.jpg"), 1); + }); + test("cv2.imread, cv2.imwrite", () async { { final cvImage = cv.imread("test/images/circles.jpg", flags: cv.IMREAD_COLOR); @@ -45,6 +55,30 @@ void main() async { expect(dst.isEmpty, false); expect((dst.rows, dst.cols, dst.channels), (cvImage.rows, cvImage.cols, cvImage.channels)); }); + + test("cv2.imencodeVec, cv2.imdecodeVec", () async { + final cvImage = cv.imread("test/images/circles.jpg", flags: cv.IMREAD_COLOR); + expect((cvImage.width, cvImage.height), (512, 512)); + final (success, buf) = cv.imencodeVec(".png", cvImage); + expect(success, true); + expect(buf.length, greaterThan(0)); + await File("test/images_out/test_imencode.png").writeAsBytes(buf.data); + final params = [cv.IMWRITE_PNG_COMPRESSION, 9].i32; + final (success1, buf1) = cv.imencodeVec(".png", cvImage, params: params); + expect(success1, true); + expect(buf1.length, greaterThan(0)); + + final cvimgDecode = cv.imdecodeVec(buf, cv.IMREAD_COLOR); + expect(cvimgDecode.height, equals(cvImage.height)); + expect(cvimgDecode.width, equals(cvImage.width)); + expect(cvimgDecode.channels, equals(cvImage.channels)); + + final dst = cv.Mat.empty(); + cv.imdecodeVec(buf, cv.IMREAD_COLOR, dst: dst); + expect(dst.isEmpty, false); + expect((dst.rows, dst.cols, dst.channels), (cvImage.rows, cvImage.cols, cvImage.channels)); + }); + test("cv2.imencodeAsync, cv2.imdecodeAsync", () async { final cvImage = await cv.imreadAsync("test/images/circles.jpg", flags: cv.IMREAD_COLOR); expect((cvImage.width, cvImage.height), (512, 512)); @@ -63,4 +97,23 @@ void main() async { expect(cvimgDecode.width, equals(cvImage.width)); expect(cvimgDecode.channels, equals(cvImage.channels)); }); + + test("cv2.imencodeVecAsync, cv2.imdecodeVecAsync", () async { + final cvImage = await cv.imreadAsync("test/images/circles.jpg", flags: cv.IMREAD_COLOR); + expect((cvImage.width, cvImage.height), (512, 512)); + final (success, buf) = await cv.imencodeVecAsync(".png", cvImage); + expect(success, true); + expect(buf.length, greaterThan(0)); + + await File("test/images_out/test_imencode.png").writeAsBytes(buf.data); + final params = [cv.IMWRITE_PNG_COMPRESSION, 9].i32; + final (success1, buf1) = await cv.imencodeVecAsync(".png", cvImage, params: params); + expect(success1, true); + expect(buf1.length, greaterThan(0)); + + final cvimgDecode = await cv.imdecodeVecAsync(buf, cv.IMREAD_COLOR); + expect(cvimgDecode.height, equals(cvImage.height)); + expect(cvimgDecode.width, equals(cvImage.width)); + expect(cvimgDecode.channels, equals(cvImage.channels)); + }); }