diff --git a/.gitignore b/.gitignore index 03de607c..ca5beea3 100644 --- a/.gitignore +++ b/.gitignore @@ -58,3 +58,4 @@ doc/api/ .flutter-plugins .flutter-plugins-dependencies +Podfile.lock diff --git a/packages/dartcv/lib/src/imgproc/imgproc.dart b/packages/dartcv/lib/src/imgproc/imgproc.dart index 2b6b10d5..9bf9af03 100644 --- a/packages/dartcv/lib/src/imgproc/imgproc.dart +++ b/packages/dartcv/lib/src/imgproc/imgproc.dart @@ -1758,3 +1758,29 @@ Mat accumulateWeighted(InputArray src, InputOutputArray dst, double alpha, {Inpu } return dst; } + +/// Tests a contour convexity. +/// +/// The function tests whether the input contour is convex or not. +/// The contour must be simple, that is, without self-intersections. +/// Otherwise, the function output is undefined. +/// +/// https://docs.opencv.org/4.x/d3/dc0/group__imgproc__shape.html#ga8abf8010377b58cbc16db6734d92941b +bool isContourConvex(VecPoint contour) => cimgproc.cv_isContourConvex(contour.ref); + +/// Finds intersection of two convex polygons. +/// +/// https://docs.opencv.org/4.x/d3/dc0/group__imgproc__shape.html#ga06eed475945f155030f2135a7f25f11d +(double rval, VecPoint p12) intersectConvexConvex( + VecPoint p1, + VecPoint p2, { + VecPoint? p12, + bool handleNested = true, +}) { + final r = calloc(); + final pP12 = p12?.ptr ?? calloc(); + cvRun(() => cimgproc.cv_intersectConvexConvex(p1.ref, p2.ref, pP12, handleNested, r, ffi.nullptr)); + final rval = (r.value, p12 ?? VecPoint.fromPointer(pP12)); + calloc.free(r); + return rval; +} diff --git a/packages/dartcv/lib/src/imgproc/imgproc_async.dart b/packages/dartcv/lib/src/imgproc/imgproc_async.dart index 064986d8..db494772 100644 --- a/packages/dartcv/lib/src/imgproc/imgproc_async.dart +++ b/packages/dartcv/lib/src/imgproc/imgproc_async.dart @@ -2109,3 +2109,24 @@ Future accumulateWeightedAsync( ); } } + +/// Finds intersection of two convex polygons. +/// +/// https://docs.opencv.org/4.x/d3/dc0/group__imgproc__shape.html#ga06eed475945f155030f2135a7f25f11d +Future<(double rval, VecPoint p12)> intersectConvexConvexAsync( + VecPoint p1, + VecPoint p2, { + VecPoint? p12, + bool handleNested = true, +}) { + final r = calloc(); + final pP12 = p12?.ptr ?? calloc(); + return cvRunAsync0( + (callback) => cimgproc.cv_intersectConvexConvex(p1.ref, p2.ref, pP12, handleNested, r, callback), + (c) { + final rval = (r.value, p12 ?? VecPoint.fromPointer(pP12)); + calloc.free(r); + return c.complete(rval); + }, + ); +} diff --git a/packages/dartcv/lib/src/native_lib.dart b/packages/dartcv/lib/src/native_lib.dart index 9ffb431e..ae7914c5 100644 --- a/packages/dartcv/lib/src/native_lib.dart +++ b/packages/dartcv/lib/src/native_lib.dart @@ -6,8 +6,6 @@ import 'dart:ffi' as ffi; import 'dart:io'; -import 'package:logging/logging.dart'; - import 'g/calib3d.g.dart' as calib3d; import 'g/contrib.g.dart' as contrib; import 'g/core.g.dart' as core; @@ -42,7 +40,7 @@ ffi.DynamicLibrary loadNativeLibrary(String libName) { try { return ffi.DynamicLibrary.open(libPath); } catch (e) { - Logger("dartcv").warning("$e"); + print("Error loading $libPath, error: $e fallback to process."); return ffi.DynamicLibrary.process(); } } diff --git a/packages/dartcv/pubspec.yaml b/packages/dartcv/pubspec.yaml index 8147efbe..927ee646 100644 --- a/packages/dartcv/pubspec.yaml +++ b/packages/dartcv/pubspec.yaml @@ -11,7 +11,6 @@ environment: dependencies: ffi: ^2.1.3 - logging: ^1.3.0 dev_dependencies: ffigen: ">=13.0.0 <15.0.0" diff --git a/packages/dartcv/src b/packages/dartcv/src index 75b1be38..7bd81ecc 160000 --- a/packages/dartcv/src +++ b/packages/dartcv/src @@ -1 +1 @@ -Subproject commit 75b1be38ac50c58a5c7e18d80a4826a0124c513f +Subproject commit 7bd81ecc6273087926761a0e6e64791e3fecb1d7 diff --git a/packages/dartcv/test/images_out/intersections.png b/packages/dartcv/test/images_out/intersections.png new file mode 100644 index 00000000..4ccd00b1 Binary files /dev/null and b/packages/dartcv/test/images_out/intersections.png differ diff --git a/packages/dartcv/test/imgproc/imgproc_async_test.dart b/packages/dartcv/test/imgproc/imgproc_async_test.dart index 548b539b..d79c06ed 100644 --- a/packages/dartcv/test/imgproc/imgproc_async_test.dart +++ b/packages/dartcv/test/imgproc/imgproc_async_test.dart @@ -948,4 +948,121 @@ void main() async { expect(p[0], isIn([0, 1])); }); }); + + test('cv.intersectConvexConvexAsync', () async { + // helper functions + cv.VecPoint makeRectangle(cv.Point topLeft, cv.Point bottomRiht) => + [topLeft, cv.Point(bottomRiht.x, topLeft.y), bottomRiht, cv.Point(topLeft.x, bottomRiht.y)].asVec(); + + Future drawIntersection(cv.Mat image, cv.VecPoint p1, cv.VecPoint p2, + {bool handleNested = true}) async { + final (intersectArea, intersectionPolygon) = + await cv.intersectConvexConvexAsync(p1, p2, handleNested: handleNested); + if (intersectArea > 0) { + final fillColor = + !cv.isContourConvex(p1) || !cv.isContourConvex(p2) ? cv.Scalar(0, 0, 255) : cv.Scalar.all(200); + await cv.fillPolyAsync(image, cv.VecVecPoint.fromVecPoint(intersectionPolygon), fillColor); + } + await cv.polylinesAsync(image, cv.VecVecPoint.fromVecPoint(intersectionPolygon), true, cv.Scalar.black); + return intersectArea; + } + + Future drawDescription( + cv.Mat image, int intersectionArea, String description, cv.Point origin) async { + final caption = "Intersection area: $intersectionArea$description"; + await cv.putTextAsync(image, caption, origin, cv.FONT_HERSHEY_SIMPLEX, 0.6, cv.Scalar.black); + } + + // start testing + final image = cv.Mat.fromScalar(610, 550, cv.MatType.CV_8UC3, cv.Scalar.white); + double intersectionArea = await drawIntersection( + image, + makeRectangle(cv.Point(10, 10), cv.Point(50, 50)), + makeRectangle(cv.Point(20, 20), cv.Point(60, 60)), + ); + await drawDescription(image, intersectionArea.toInt(), "", cv.Point(70, 40)); + + intersectionArea = await drawIntersection( + image, + makeRectangle(cv.Point(10, 70), cv.Point(35, 95)), + makeRectangle(cv.Point(35, 95), cv.Point(60, 120)), + ); + await drawDescription(image, intersectionArea.toInt(), "", cv.Point(70, 100)); + + intersectionArea = await drawIntersection( + image, + makeRectangle(cv.Point(10, 130), cv.Point(60, 180)), + makeRectangle(cv.Point(20, 140), cv.Point(50, 170)), + handleNested: true, + ); + await drawDescription(image, intersectionArea.toInt(), " (handleNested true)", cv.Point(70, 160)); + + intersectionArea = await drawIntersection( + image, + makeRectangle(cv.Point(10, 250), cv.Point(60, 300)), + makeRectangle(cv.Point(20, 250), cv.Point(50, 290)), + handleNested: true, + ); + + await drawDescription(image, intersectionArea.toInt(), " (handleNested true)", cv.Point(70, 280)); + + // These rectangles share an edge so handleNested can be false and an intersection is still found + intersectionArea = await drawIntersection( + image, + makeRectangle(cv.Point(10, 310), cv.Point(60, 360)), + makeRectangle(cv.Point(20, 310), cv.Point(50, 350)), + handleNested: false, + ); + + await drawDescription(image, intersectionArea.toInt(), " (handleNested false)", cv.Point(70, 340)); + + intersectionArea = await drawIntersection( + image, + makeRectangle(cv.Point(10, 370), cv.Point(60, 420)), + makeRectangle(cv.Point(20, 371), cv.Point(50, 410)), + handleNested: false, + ); + + await drawDescription(image, intersectionArea.toInt(), " (handleNested false)", cv.Point(70, 400)); + + // A vertex of the triangle lies on an edge of the rectangle so handleNested can be false and an intersection is still found + intersectionArea = await drawIntersection( + image, + makeRectangle(cv.Point(10, 430), cv.Point(60, 480)), + [cv.Point(35, 430), cv.Point(20, 470), cv.Point(50, 470)].asVec(), + handleNested: false, + ); + + await drawDescription(image, intersectionArea.toInt(), " (handleNested false)", cv.Point(70, 460)); + + // Show intersection of overlapping rectangle and triangle + intersectionArea = await drawIntersection( + image, + makeRectangle(cv.Point(10, 490), cv.Point(40, 540)), + [cv.Point(25, 500), cv.Point(25, 530), cv.Point(60, 515)].asVec(), + handleNested: false, + ); + + await drawDescription(image, intersectionArea.toInt(), "", cv.Point(70, 520)); + + // This concave polygon is invalid input to intersectConvexConvex so it returns an invalid intersection + final cv.VecPoint notConvex = [ + cv.Point(25, 560), + cv.Point(25, 590), + cv.Point(45, 580), + cv.Point(60, 600), + cv.Point(60, 550), + cv.Point(45, 570), + ].asVec(); + intersectionArea = await drawIntersection( + image, + makeRectangle(cv.Point(10, 550), cv.Point(50, 600)), + notConvex, + handleNested: false, + ); + + await drawDescription(image, intersectionArea.toInt(), " (invalid input: not convex)", cv.Point(70, 580)); + + await cv.imwriteAsync("test/images_out/intersections.png", image); + }); } diff --git a/packages/dartcv/test/imgproc/imgproc_test.dart b/packages/dartcv/test/imgproc/imgproc_test.dart index 891a50ed..bedcce1b 100644 --- a/packages/dartcv/test/imgproc/imgproc_test.dart +++ b/packages/dartcv/test/imgproc/imgproc_test.dart @@ -974,4 +974,136 @@ void main() async { expect(p[0], isIn([0, 1])); }); }); + + test('cv.isContourConvex', () { + final rectangle = [cv.Point(0, 0), cv.Point(100, 0), cv.Point(100, 100), cv.Point(0, 100)].asVec(); + final res = cv.isContourConvex(rectangle); + expect(res, true); + + final notConvex = [ + cv.Point(25, 560), + cv.Point(25, 590), + cv.Point(45, 580), + cv.Point(60, 600), + cv.Point(60, 550), + cv.Point(45, 570), + ].asVec(); + expect(cv.isContourConvex(notConvex), false); + }); + + // https://docs.opencv.org/4.x/df/da5/samples_2cpp_2intersectExample_8cpp-example.html + test('cv.intersectConvexConvex', () { + // helper functions + cv.VecPoint makeRectangle(cv.Point topLeft, cv.Point bottomRiht) => + [topLeft, cv.Point(bottomRiht.x, topLeft.y), bottomRiht, cv.Point(topLeft.x, bottomRiht.y)].asVec(); + + double drawIntersection(cv.Mat image, cv.VecPoint p1, cv.VecPoint p2, {bool handleNested = true}) { + final (intersectArea, intersectionPolygon) = + cv.intersectConvexConvex(p1, p2, handleNested: handleNested); + if (intersectArea > 0) { + final fillColor = + !cv.isContourConvex(p1) || !cv.isContourConvex(p2) ? cv.Scalar(0, 0, 255) : cv.Scalar.all(200); + cv.fillPoly(image, cv.VecVecPoint.fromVecPoint(intersectionPolygon), fillColor); + } + cv.polylines(image, cv.VecVecPoint.fromVecPoint(intersectionPolygon), true, cv.Scalar.black); + return intersectArea; + } + + void drawDescription(cv.Mat image, int intersectionArea, String description, cv.Point origin) { + final caption = "Intersection area: $intersectionArea$description"; + cv.putText(image, caption, origin, cv.FONT_HERSHEY_SIMPLEX, 0.6, cv.Scalar.black); + } + + // start testing + final image = cv.Mat.fromScalar(610, 550, cv.MatType.CV_8UC3, cv.Scalar.white); + double intersectionArea = drawIntersection( + image, + makeRectangle(cv.Point(10, 10), cv.Point(50, 50)), + makeRectangle(cv.Point(20, 20), cv.Point(60, 60)), + ); + drawDescription(image, intersectionArea.toInt(), "", cv.Point(70, 40)); + + intersectionArea = drawIntersection( + image, + makeRectangle(cv.Point(10, 70), cv.Point(35, 95)), + makeRectangle(cv.Point(35, 95), cv.Point(60, 120)), + ); + drawDescription(image, intersectionArea.toInt(), "", cv.Point(70, 100)); + + intersectionArea = drawIntersection( + image, + makeRectangle(cv.Point(10, 130), cv.Point(60, 180)), + makeRectangle(cv.Point(20, 140), cv.Point(50, 170)), + handleNested: true, + ); + drawDescription(image, intersectionArea.toInt(), " (handleNested true)", cv.Point(70, 160)); + + intersectionArea = drawIntersection( + image, + makeRectangle(cv.Point(10, 250), cv.Point(60, 300)), + makeRectangle(cv.Point(20, 250), cv.Point(50, 290)), + handleNested: true, + ); + + drawDescription(image, intersectionArea.toInt(), " (handleNested true)", cv.Point(70, 280)); + + // These rectangles share an edge so handleNested can be false and an intersection is still found + intersectionArea = drawIntersection( + image, + makeRectangle(cv.Point(10, 310), cv.Point(60, 360)), + makeRectangle(cv.Point(20, 310), cv.Point(50, 350)), + handleNested: false, + ); + + drawDescription(image, intersectionArea.toInt(), " (handleNested false)", cv.Point(70, 340)); + + intersectionArea = drawIntersection( + image, + makeRectangle(cv.Point(10, 370), cv.Point(60, 420)), + makeRectangle(cv.Point(20, 371), cv.Point(50, 410)), + handleNested: false, + ); + + drawDescription(image, intersectionArea.toInt(), " (handleNested false)", cv.Point(70, 400)); + + // A vertex of the triangle lies on an edge of the rectangle so handleNested can be false and an intersection is still found + intersectionArea = drawIntersection( + image, + makeRectangle(cv.Point(10, 430), cv.Point(60, 480)), + [cv.Point(35, 430), cv.Point(20, 470), cv.Point(50, 470)].asVec(), + handleNested: false, + ); + + drawDescription(image, intersectionArea.toInt(), " (handleNested false)", cv.Point(70, 460)); + + // Show intersection of overlapping rectangle and triangle + intersectionArea = drawIntersection( + image, + makeRectangle(cv.Point(10, 490), cv.Point(40, 540)), + [cv.Point(25, 500), cv.Point(25, 530), cv.Point(60, 515)].asVec(), + handleNested: false, + ); + + drawDescription(image, intersectionArea.toInt(), "", cv.Point(70, 520)); + + // This concave polygon is invalid input to intersectConvexConvex so it returns an invalid intersection + final cv.VecPoint notConvex = [ + cv.Point(25, 560), + cv.Point(25, 590), + cv.Point(45, 580), + cv.Point(60, 600), + cv.Point(60, 550), + cv.Point(45, 570), + ].asVec(); + intersectionArea = drawIntersection( + image, + makeRectangle(cv.Point(10, 550), cv.Point(50, 600)), + notConvex, + handleNested: false, + ); + + drawDescription(image, intersectionArea.toInt(), " (invalid input: not convex)", cv.Point(70, 580)); + + cv.imwrite("test/images_out/intersections.png", image); + }); } diff --git a/packages/opencv_core/images/opencv_core_size_report.svg b/packages/opencv_core/images/opencv_core_size_report.svg index 4cb8d6fc..378be0e2 100644 --- a/packages/opencv_core/images/opencv_core_size_report.svg +++ b/packages/opencv_core/images/opencv_core_size_report.svg @@ -68,9 +68,9 @@ Total: - 70.61 MB + 70.62 MB Package: - 47.41 MB + 47.42 MB diff --git a/packages/opencv_core/pubspec.yaml b/packages/opencv_core/pubspec.yaml index dfb3961c..b3b14fa8 100644 --- a/packages/opencv_core/pubspec.yaml +++ b/packages/opencv_core/pubspec.yaml @@ -5,7 +5,7 @@ description: | if you need them, please use `opencv_dart` instead. version: 1.3.3 opencv_version: 4.10.0+10 -dartcv_version: 4.10.0.5 +dartcv_version: 4.10.0.6 repository: https://github.com/rainyl/opencv_dart homepage: https://github.com/rainyl/opencv_dart/tree/main/packages/opencv_core diff --git a/packages/opencv_dart/images/opencv_dart_size_report.svg b/packages/opencv_dart/images/opencv_dart_size_report.svg index 40e033fb..61400062 100644 --- a/packages/opencv_dart/images/opencv_dart_size_report.svg +++ b/packages/opencv_dart/images/opencv_dart_size_report.svg @@ -68,9 +68,9 @@ Total: - 176.22 MB + 176.23 MB Package: - 153.02 MB + 153.03 MB diff --git a/packages/opencv_dart/pubspec.yaml b/packages/opencv_dart/pubspec.yaml index 7e569117..aa12317c 100644 --- a/packages/opencv_dart/pubspec.yaml +++ b/packages/opencv_dart/pubspec.yaml @@ -5,7 +5,7 @@ description: | please use `opencv_core` instead. version: 1.3.3 opencv_version: 4.10.0+10 -dartcv_version: 4.10.0.5 +dartcv_version: 4.10.0.6 repository: https://github.com/rainyl/opencv_dart homepage: https://github.com/rainyl/opencv_dart/tree/main/packages/opencv_dart