diff --git a/README.md b/README.md index 9ca6189..98bcbbe 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ A Flutter plugin enabling the user to detect edges of a given image. It returns ## Demo

- +

## Try out diff --git a/example/lib/edge_detection_preview.dart b/example/lib/edge_detection_preview.dart deleted file mode 100644 index 86a89bd..0000000 --- a/example/lib/edge_detection_preview.dart +++ /dev/null @@ -1,149 +0,0 @@ -import 'dart:async'; -import 'dart:io'; -import 'dart:math'; -import 'dart:typed_data'; -import 'dart:ui' as ui; -import 'package:simple_edge_detection/edge_detection.dart'; -import 'package:flutter/material.dart'; - -class EdgeDetectionPreview extends StatefulWidget { - EdgeDetectionPreview({ - this.imagePath, - this.edgeDetectionResult - }); - - final String imagePath; - final EdgeDetectionResult edgeDetectionResult; - - @override - _EdgeDetectionPreviewState createState() => _EdgeDetectionPreviewState(); -} - -class _EdgeDetectionPreviewState extends State { - GlobalKey imageWidgetKey = GlobalKey(); - - @override - Widget build(BuildContext mainContext) { - return Center( - child: Stack( - fit: StackFit.expand, - children: [ - Center( - child: Text('Loading ...') - ), - Image.file( - File(widget.imagePath), - fit: BoxFit.contain, - key: imageWidgetKey - ), - FutureBuilder( - future: loadUiImage(widget.imagePath), - builder: (BuildContext context, AsyncSnapshot snapshot) { - return _getEdgePaint(snapshot, context); - } - ), - ], - ), - ); - } - - Widget _getEdgePaint(AsyncSnapshot imageSnapshot, BuildContext context) { - if (imageSnapshot.connectionState == ConnectionState.waiting) - return Container(); - - if (imageSnapshot.hasError) - return Text('Error: ${imageSnapshot.error}'); - - if (widget.edgeDetectionResult == null) - return Container(); - - final keyContext = imageWidgetKey.currentContext; - - if (keyContext == null) { - return Container(); - } - - final box = keyContext.findRenderObject() as RenderBox; - - return CustomPaint( - size: Size(box.size.width, box.size.height), - painter: EdgePainter( - topLeft: widget.edgeDetectionResult.topLeft, - topRight: widget.edgeDetectionResult.topRight, - bottomLeft: widget.edgeDetectionResult.bottomLeft, - bottomRight: widget.edgeDetectionResult.bottomRight, - image: imageSnapshot.data, - color: Theme.of(context).accentColor - ) - ); - } - - Future loadUiImage(String imageAssetPath) async { - final Uint8List data = await File(imageAssetPath).readAsBytes(); - final Completer completer = Completer(); - ui.decodeImageFromList(Uint8List.view(data.buffer), (ui.Image image) { - return completer.complete(image); - }); - return completer.future; - } -} - -class EdgePainter extends CustomPainter { - EdgePainter({ - this.topLeft, - this.topRight, - this.bottomLeft, - this.bottomRight, - this.image, - this.color - }); - - Offset topLeft; - Offset topRight; - Offset bottomLeft; - Offset bottomRight; - - ui.Image image; - Color color; - - @override - void paint(Canvas canvas, Size size) { - double top = 0.0; - double left = 0.0; - - double widthFactor = size.width / image.width; - double heightFactor = size.height / image.height; - double sizeFactor = min(widthFactor, heightFactor); - - double renderedImageHeight = image.height * sizeFactor; - top = ((size.height - renderedImageHeight) / 2); - - double renderedImageWidth = image.width * sizeFactor; - left = ((size.width - renderedImageWidth) / 2); - - - final points = [ - Offset(left + topLeft.dx * renderedImageWidth, top + topLeft.dy * renderedImageHeight), - Offset(left + topRight.dx * renderedImageWidth, top + topRight.dy * renderedImageHeight), - Offset(left + bottomRight.dx * renderedImageWidth, top + (bottomRight.dy * renderedImageHeight)), - Offset(left + bottomLeft.dx * renderedImageWidth, top + bottomLeft.dy * renderedImageHeight), - Offset(left + topLeft.dx * renderedImageWidth, top + topLeft.dy * renderedImageHeight), - ]; - - final paint = Paint() - ..color = color.withOpacity(0.5) - ..strokeWidth = 2 - ..strokeCap = StrokeCap.round; - - canvas.drawPoints(ui.PointMode.polygon, points, paint); - - for (Offset point in points) { - canvas.drawCircle(point, 10, paint); - } - } - - @override - bool shouldRepaint(CustomPainter old) { - return true; - } -} \ No newline at end of file diff --git a/example/lib/edge_detection_shape/animated_touch_bubble_part.dart b/example/lib/edge_detection_shape/animated_touch_bubble_part.dart new file mode 100644 index 0000000..a587aaf --- /dev/null +++ b/example/lib/edge_detection_shape/animated_touch_bubble_part.dart @@ -0,0 +1,85 @@ +import 'package:flutter/material.dart'; + +class AnimatedTouchBubblePart extends StatefulWidget { + AnimatedTouchBubblePart({this.dragging, this.size}); + + final bool dragging; + final double size; + + @override + _AnimatedTouchBubblePartState createState() => _AnimatedTouchBubblePartState(); +} + +class _AnimatedTouchBubblePartState extends State with SingleTickerProviderStateMixin { + AnimationController _controller; + Animation _colorAnimation; + Animation _sizeAnimation; + + @override + void didChangeDependencies() { + _controller = new AnimationController( + duration: const Duration(milliseconds: 1000), + vsync: this, + ); + + _sizeAnimation = Tween( + begin: 0.5, + end: 1.0 + ).animate(_controller); + + _colorAnimation = ColorTween( + begin: Theme.of(context).accentColor.withOpacity(0.5), + end: Theme.of(context).accentColor.withOpacity(0.0) + ).animate( + CurvedAnimation( + parent: _controller, + curve: Interval(0.5, 1.0) + ) + ); + + _controller.repeat(); + super.didChangeDependencies(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Center( + child: Container( + width: widget.dragging ? widget.size : widget.size / 2, + height: widget.dragging ? widget.size : widget.size / 2, + decoration: BoxDecoration( + color: Theme.of(context).accentColor.withOpacity(0.5), + borderRadius: widget.dragging ? BorderRadius.circular(widget.size) : BorderRadius.circular(widget.size / 4) + ) + ) + ), + AnimatedBuilder( + builder: (BuildContext context, Widget child) { + return Center( + child: Container( + width: widget.dragging ? 0 : widget.size * _sizeAnimation.value, + height: widget.dragging ? 0 : widget.size * _sizeAnimation.value, + decoration: BoxDecoration( + border: Border.all( + color: _colorAnimation.value, + width: widget.size / 20 + ), + borderRadius: widget.dragging ? BorderRadius.zero : BorderRadius.circular(widget.size * _sizeAnimation.value / 2) + ) + ) + ); + }, + animation: _controller + ) + ], + ); + } +} \ No newline at end of file diff --git a/example/lib/edge_detection_shape/edge_detection_shape.dart b/example/lib/edge_detection_shape/edge_detection_shape.dart new file mode 100644 index 0000000..c48e3ef --- /dev/null +++ b/example/lib/edge_detection_shape/edge_detection_shape.dart @@ -0,0 +1,170 @@ +import 'dart:math'; +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:simple_edge_detection/edge_detection.dart'; + +import 'edge_painter.dart'; +import 'touch_bubble.dart'; + +class EdgeDetectionShape extends StatefulWidget { + EdgeDetectionShape({ + @required this.renderedImageSize, + @required this.originalImageSize, + @required this.edgeDetectionResult + }); + + final Size renderedImageSize; + final Size originalImageSize; + final EdgeDetectionResult edgeDetectionResult; + + @override + _EdgeDetectionShapeState createState() => _EdgeDetectionShapeState(); +} + +class _EdgeDetectionShapeState extends State { + double edgeDraggerSize; + + EdgeDetectionResult edgeDetectionResult; + List points; + + double renderedImageWidth; + double renderedImageHeight; + double top; + double left; + + @override + void didChangeDependencies() { + double shortestSide = min(MediaQuery.of(context).size.width, MediaQuery.of(context).size.height); + edgeDraggerSize = shortestSide / 8; + super.didChangeDependencies(); + } + + @override + void initState() { + edgeDetectionResult = widget.edgeDetectionResult; + _calculateDimensionValues(); + + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + _getTouchBubbles(), + CustomPaint( + painter: EdgePainter( + points: points, + color: Theme.of(context).accentColor.withOpacity(0.5) + ), + ) + ], + ); + } + + void _calculateDimensionValues() { + top = 0.0; + left = 0.0; + + double widthFactor = widget.renderedImageSize.width / widget.originalImageSize.width; + double heightFactor = widget.renderedImageSize.height / widget.originalImageSize.height; + double sizeFactor = min(widthFactor, heightFactor); + + renderedImageHeight = widget.originalImageSize.height * sizeFactor; + top = ((widget.renderedImageSize.height - renderedImageHeight) / 2); + + renderedImageWidth = widget.originalImageSize.width * sizeFactor; + left = ((widget.renderedImageSize.width - renderedImageWidth) / 2); + } + + Offset _getNewPositionAfterDrag(Offset position, double renderedImageWidth, double renderedImageHeight) { + return Offset( + position.dx / renderedImageWidth, + position.dy / renderedImageHeight + ); + } + + Widget _getTouchBubbles() { + points = [ + Offset(left + edgeDetectionResult.topLeft.dx * renderedImageWidth, top + edgeDetectionResult.topLeft.dy * renderedImageHeight), + Offset(left + edgeDetectionResult.topRight.dx * renderedImageWidth, top + edgeDetectionResult.topRight.dy * renderedImageHeight), + Offset(left + edgeDetectionResult.bottomRight.dx * renderedImageWidth, top + (edgeDetectionResult.bottomRight.dy * renderedImageHeight)), + Offset(left + edgeDetectionResult.bottomLeft.dx * renderedImageWidth, top + edgeDetectionResult.bottomLeft.dy * renderedImageHeight), + Offset(left + edgeDetectionResult.topLeft.dx * renderedImageWidth, top + edgeDetectionResult.topLeft.dy * renderedImageHeight), + ]; + + final Function onDragFinished = () { + setState(() {}); + }; + + return Container( + width: widget.renderedImageSize.width, + height: widget.renderedImageSize.height, + child: Stack( + children: [ + Positioned( + child: TouchBubble( + size: edgeDraggerSize, + onDrag: (position) { + setState(() { + edgeDetectionResult.topLeft += _getNewPositionAfterDrag( + position, renderedImageWidth, renderedImageHeight + ); + }); + }, + onDragFinished: onDragFinished + ), + left: points[0].dx - (edgeDraggerSize / 2), + top: points[0].dy - (edgeDraggerSize / 2) + ), + Positioned( + child: TouchBubble( + size: edgeDraggerSize, + onDrag: (position) { + setState(() { + edgeDetectionResult.topRight += _getNewPositionAfterDrag( + position, renderedImageWidth, renderedImageHeight + ); + }); + }, + onDragFinished: onDragFinished + ), + left: points[1].dx - (edgeDraggerSize / 2), + top: points[1].dy - (edgeDraggerSize / 2) + ), + Positioned( + child: TouchBubble( + size: edgeDraggerSize, + onDrag: (position) { + setState(() { + edgeDetectionResult.bottomRight += _getNewPositionAfterDrag( + position, renderedImageWidth, renderedImageHeight + ); + }); + }, + onDragFinished: onDragFinished + ), + left: points[2].dx - (edgeDraggerSize / 2), + top: points[2].dy - (edgeDraggerSize / 2) + ), + Positioned( + child: TouchBubble( + size: edgeDraggerSize, + onDrag: (position) { + setState(() { + edgeDetectionResult.bottomLeft += _getNewPositionAfterDrag( + position, renderedImageWidth, renderedImageHeight + ); + }); + }, + onDragFinished: onDragFinished + ), + left: points[3].dx - (edgeDraggerSize / 2), + top: points[3].dy - (edgeDraggerSize / 2) + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/example/lib/edge_detection_shape/edge_painter.dart b/example/lib/edge_detection_shape/edge_painter.dart new file mode 100644 index 0000000..782a08e --- /dev/null +++ b/example/lib/edge_detection_shape/edge_painter.dart @@ -0,0 +1,28 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; + +class EdgePainter extends CustomPainter { + EdgePainter({ + @required this.points, + @required this.color + }); + + final List points; + final Color color; + + @override + void paint(Canvas canvas, Size size) { + final paint = Paint() + ..color = color.withOpacity(0.5) + ..strokeWidth = 2 + ..strokeCap = StrokeCap.round; + + canvas.drawPoints(PointMode.polygon, points, paint); + } + + @override + bool shouldRepaint(CustomPainter old) { + return true; + } +} \ No newline at end of file diff --git a/example/lib/edge_detection_shape/touch_bubble.dart b/example/lib/edge_detection_shape/touch_bubble.dart new file mode 100644 index 0000000..2d45933 --- /dev/null +++ b/example/lib/edge_detection_shape/touch_bubble.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; + +import 'animated_touch_bubble_part.dart'; + +class TouchBubble extends StatefulWidget { + TouchBubble({ + @required this.size, + @required this.onDrag, + @required this.onDragFinished, + }); + + final double size; + final Function onDrag; + final Function onDragFinished; + + @override + _TouchBubbleState createState() => _TouchBubbleState(); +} + +class _TouchBubbleState extends State { + bool dragging = false; + + @override + Widget build(BuildContext context) { + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTapDown: (_) => _startDragging(), + onPanStart: (_) => _startDragging(), + onTapUp: (_) => _cancelDragging(), + onTapCancel: _cancelDragging, + onPanUpdate: _drag, + onPanCancel: _cancelDragging, + onPanEnd: (_) => _cancelDragging(), + child: Container( + width: widget.size, + height: widget.size, + decoration: BoxDecoration( + color: Colors.transparent, + borderRadius: BorderRadius.circular(widget.size / 2) + ), + child: AnimatedTouchBubblePart( + dragging: dragging, + size: widget.size, + ) + ) + ); + } + + void _startDragging() { + setState(() { + dragging = true; + }); + } + + void _cancelDragging() { + setState(() { + dragging = false; + }); + widget.onDragFinished(); + } + + void _drag(DragUpdateDetails data) { + if (!dragging) { + return; + } + widget.onDrag(data.delta); + } +} \ No newline at end of file diff --git a/example/lib/image_preview.dart b/example/lib/image_preview.dart new file mode 100644 index 0000000..e4ebf82 --- /dev/null +++ b/example/lib/image_preview.dart @@ -0,0 +1,87 @@ +import 'dart:async'; +import 'dart:io'; +import 'dart:typed_data'; +import 'dart:ui' as ui; +import 'package:simple_edge_detection/edge_detection.dart'; +import 'package:flutter/material.dart'; + +import 'edge_detection_shape/edge_detection_shape.dart'; + +class ImagePreview extends StatefulWidget { + ImagePreview({ + this.imagePath, + this.edgeDetectionResult + }); + + final String imagePath; + final EdgeDetectionResult edgeDetectionResult; + + @override + _ImagePreviewState createState() => _ImagePreviewState(); +} + +class _ImagePreviewState extends State { + GlobalKey imageWidgetKey = GlobalKey(); + + @override + Widget build(BuildContext mainContext) { + return Center( + child: Stack( + fit: StackFit.expand, + children: [ + Center( + child: Text('Loading ...') + ), + Image.file( + File(widget.imagePath), + fit: BoxFit.contain, + key: imageWidgetKey + ), + FutureBuilder( + future: loadUiImage(widget.imagePath), + builder: (BuildContext context, AsyncSnapshot snapshot) { + return _getEdgePaint(snapshot, context); + } + ), + ], + ), + ); + } + + Widget _getEdgePaint(AsyncSnapshot imageSnapshot, BuildContext context) { + if (imageSnapshot.connectionState == ConnectionState.waiting) + return Container(); + + if (imageSnapshot.hasError) + return Text('Error: ${imageSnapshot.error}'); + + if (widget.edgeDetectionResult == null) + return Container(); + + final keyContext = imageWidgetKey.currentContext; + + if (keyContext == null) { + return Container(); + } + + final box = keyContext.findRenderObject() as RenderBox; + + return EdgeDetectionShape( + originalImageSize: Size( + imageSnapshot.data.width.toDouble(), + imageSnapshot.data.height.toDouble() + ), + renderedImageSize: Size(box.size.width, box.size.height), + edgeDetectionResult: widget.edgeDetectionResult, + ); + } + + Future loadUiImage(String imageAssetPath) async { + final Uint8List data = await File(imageAssetPath).readAsBytes(); + final Completer completer = Completer(); + ui.decodeImageFromList(Uint8List.view(data.buffer), (ui.Image image) { + return completer.complete(image); + }); + return completer.future; + } +} \ No newline at end of file diff --git a/example/lib/scan.dart b/example/lib/scan.dart index 9debfa5..51e9c46 100644 --- a/example/lib/scan.dart +++ b/example/lib/scan.dart @@ -7,9 +7,9 @@ import 'package:simple_edge_detection/edge_detection.dart'; import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; import 'package:path_provider/path_provider.dart'; +import 'package:simple_edge_detection_example/image_preview.dart'; import 'camera_view.dart'; -import 'edge_detection_preview.dart'; import 'edge_detector.dart'; class Scan extends StatefulWidget { @@ -50,7 +50,7 @@ class _ScanState extends State { ); } - return EdgeDetectionPreview( + return ImagePreview( imagePath: imagePath, edgeDetectionResult: edgeDetectionResult, );