From cb01ae522612a35a9a3de1d8455d5afbf484de62 Mon Sep 17 00:00:00 2001 From: Gibah Joseph Date: Fri, 1 Jan 2021 01:40:01 +0100 Subject: [PATCH 1/3] added custom styling for eye --- lib/src/qr_image.dart | 13 ++++++- lib/src/qr_painter.dart | 83 +++++++++++++++++++++++++++++++++++++++++ lib/src/types.dart | 60 ++++++++++++++++++++++++++++- 3 files changed, 153 insertions(+), 3 deletions(-) diff --git a/lib/src/qr_image.dart b/lib/src/qr_image.dart index e762f59..a7b6dd5 100644 --- a/lib/src/qr_image.dart +++ b/lib/src/qr_image.dart @@ -45,7 +45,7 @@ class QrImage extends StatefulWidget { dataModuleShape: QrDataModuleShape.square, color: Colors.black, ), - this.embeddedImageEmitsError = false, + this.embeddedImageEmitsError = false, this.blendEmbeddedImage=false, }) : assert(QrVersions.isSupportedVersion(version)), _data = data, _qrCode = null, @@ -76,7 +76,7 @@ class QrImage extends StatefulWidget { dataModuleShape: QrDataModuleShape.square, color: Colors.black, ), - this.embeddedImageEmitsError = false, + this.embeddedImageEmitsError = false, this.blendEmbeddedImage=false, }) : assert(QrVersions.isSupportedVersion(version)), _data = null, _qrCode = qr, @@ -84,6 +84,7 @@ class QrImage extends StatefulWidget { // The data passed to the widget final String _data; + // The QR code data passed to the widget final QrCode _qrCode; @@ -100,6 +101,12 @@ class QrImage extends StatefulWidget { /// The QR code error correction level to use. final int errorCorrectionLevel; + /// Embedded image will be blended into the qr code + /// + /// In layman terms, qr code will not be drawn behind embedded image so it + /// looks like the image is part of the qr code. + final bool blendEmbeddedImage; + /// The external padding between the edge of the widget and the content. final EdgeInsets padding; @@ -220,6 +227,7 @@ class _QrImageState extends State { gapless: widget.gapless, embeddedImageStyle: widget.embeddedImageStyle, embeddedImage: image, + blendEmbeddedImage: widget.blendEmbeddedImage, eyeStyle: widget.eyeStyle, dataModuleStyle: widget.dataModuleStyle, ); @@ -250,6 +258,7 @@ class _QrImageState extends State { } ImageStreamListener streamListener; + Future _loadQrImage( BuildContext buildContext, QrEmbeddedImageStyle style) async { if (style != null) {} diff --git a/lib/src/qr_painter.dart b/lib/src/qr_painter.dart index cb42c5d..86799d0 100644 --- a/lib/src/qr_painter.dart +++ b/lib/src/qr_painter.dart @@ -17,6 +17,7 @@ import 'errors.dart'; import 'paint_cache.dart'; import 'qr_versions.dart'; import 'types.dart'; +import 'types.dart'; import 'validator.dart'; // ignore_for_file: deprecated_member_use_from_same_package @@ -38,6 +39,7 @@ class QrPainter extends CustomPainter { this.gapless = false, this.embeddedImage, this.embeddedImageStyle, + this.blendEmbeddedImage = false, this.eyeStyle = const QrEyeStyle( eyeShape: QrEyeShape.square, color: Color(0xFF000000), @@ -60,6 +62,7 @@ class QrPainter extends CustomPainter { this.gapless = false, this.embeddedImage, this.embeddedImageStyle, + this.blendEmbeddedImage = false, this.eyeStyle = const QrEyeStyle( eyeShape: QrEyeShape.square, color: Color(0xFF000000), @@ -97,6 +100,12 @@ class QrPainter extends CustomPainter { /// be added to the center of the QR code. final ui.Image embeddedImage; + /// Embedded image will be blended into the qr code + /// + /// In layman terms, qr code will not be drawn behind embedded image so it + /// looks like the image is part of the qr code. + final bool blendEmbeddedImage; + /// Styling options for the image overlay. final QrEmbeddedImageStyle embeddedImageStyle; @@ -211,6 +220,8 @@ class QrPainter extends CustomPainter { for (var y = 0; y < _qr.moduleCount; y++) { // draw the finder patterns independently if (_isFinderPatternPosition(x, y)) continue; + // Exclude if pixel is in image gap space + if (blendEmbeddedImage && _isLogoArea(x, y)) continue; final paint = _qr.isDark(y, x) ? pixelPaint : emptyPixelPaint; if (paint == null) continue; // paint a pixel @@ -267,6 +278,18 @@ class QrPainter extends CustomPainter { return _qr.isDark(y, x + 1); } + bool _isLogoArea(int x, int y) { + //Find center of module count and portion to cut out of QR + var center = _qr.moduleCount / 2; + var canvasPortion = _qr.moduleCount * 0.15; + + if (x > center - canvasPortion && + x < center + canvasPortion && + y > center - canvasPortion && + y < center + canvasPortion) return true; + return false; + } + bool _isFinderPatternPosition(int x, int y) { final isTopLeft = (y < _finderPatternLimit && x < _finderPatternLimit); final isBottomLeft = (y < _finderPatternLimit && @@ -336,6 +359,10 @@ class QrPainter extends CustomPainter { canvas.drawRect(outerRect, outerPaint); canvas.drawRect(innerRect, innerPaint); canvas.drawRect(dotRect, dotPaint); + } + if (eyeStyle.eyeShape == QrEyeShape.custom) { + _drawCustomEyeStyle( + position, outerRect, outerPaint, dotRect, dotPaint, canvas); } else { final roundedOuterStrokeRect = RRect.fromRectAndRadius(outerRect, Radius.circular(radius)); @@ -351,6 +378,59 @@ class QrPainter extends CustomPainter { } } + void _drawCustomEyeStyle( + FinderPatternPosition position, + ui.Rect outerRect, + ui.Paint outerPaint, + ui.Rect dotRect, + ui.Paint dotPaint, + ui.Canvas canvas) { + BorderRadius eyeBorderRadius; + BorderRadius eyeballBorderRadius; + switch (position) { + case FinderPatternPosition.topLeft: + eyeBorderRadius = eyeStyle.shape.topLeft.eyeShape; + eyeballBorderRadius = eyeStyle.shape.topLeft.eyeballShape; + break; + case FinderPatternPosition.topRight: + eyeBorderRadius = eyeStyle.shape.topRight.eyeShape; + eyeballBorderRadius = eyeStyle.shape.topRight.eyeballShape; + break; + case FinderPatternPosition.bottomLeft: + eyeBorderRadius = eyeStyle.shape.bottomLeft.eyeShape; + eyeballBorderRadius = eyeStyle.shape.bottomLeft.eyeballShape; + break; + } + var eyePath = drawRRect(outerRect.left, outerRect.top, outerRect.width, + outerRect.height, eyeBorderRadius, outerPaint.strokeWidth); + var eyeBallPath = drawRRect(dotRect.left, dotRect.top, dotRect.width, + dotRect.height, eyeballBorderRadius, dotPaint.strokeWidth); + canvas.drawPath(eyePath..close(), outerPaint); + canvas.drawPath(eyeBallPath..close(), dotPaint); + } + + /// Draws a rounded rectangle using the current state of the canvas. + /// + /// [Canvas.drawRRect] wasn't used because there was a bug causing the + /// rect to not close properly if radius is 0 + Path drawRRect(double left, double top, double width, double height, + BorderRadius radius, double strokeWidth) { + var ctx = Path(); + ctx.moveTo(left + radius.topLeft.x, top); + ctx.lineTo(left + width - radius.topRight.x, top); + ctx.quadraticBezierTo( + left + width, top, left + width, top + radius.topRight.y); + ctx.lineTo(left + width, top + height - radius.bottomRight.y); + ctx.quadraticBezierTo(left + width, top + height, + left + width - radius.bottomRight.x, top + height); + ctx.lineTo(left + radius.bottomLeft.x, top + height); + ctx.quadraticBezierTo( + left, top + height, left, top + height - radius.bottomLeft.y); + ctx.lineTo(left, top + radius.topLeft.y); + ctx.quadraticBezierTo(left, top, left + radius.topLeft.x, top); + return ctx; + } + bool _hasOneNonZeroSide(Size size) => size.longestSide > 0; Size _scaledAspectSize( @@ -435,12 +515,15 @@ class _PaintMetrics { final double gapSize; double _pixelSize; + double get pixelSize => _pixelSize; double _innerContentSize; + double get innerContentSize => _innerContentSize; double _inset; + double get inset => _inset; void _calculateMetrics() { diff --git a/lib/src/types.dart b/lib/src/types.dart index 155eec8..209a427 100644 --- a/lib/src/types.dart +++ b/lib/src/types.dart @@ -47,6 +47,9 @@ enum QrEyeShape { /// Use circular eye frame. circle, + + /// Use custom frame + custom } /// Enumeration representing the shape of Data modules inside QR. @@ -58,10 +61,62 @@ enum QrDataModuleShape { circle, } +/// A holder for customizing the eye of a QRCode +class QrEyeShapeStyle { + /// Set the border radius of the eye to give a shape + final BorderRadius eyeShape; + + /// Set the border radius of the eyeball to give it a custom shape + final BorderRadius eyeballShape; + + /// Create A new Shape style + const QrEyeShapeStyle( + {this.eyeShape = BorderRadius.zero, this.eyeballShape = BorderRadius + .zero}); + + /// Sets the style to none + static const none = QrEyeShapeStyle( + eyeShape: BorderRadius.zero, eyeballShape: BorderRadius.zero); +} + +/// Customize the shape of the finders on a QRCode +class QrEyeShapeStyles { + /// Customize the bottom left eye + final QrEyeShapeStyle bottomLeft; + + /// Customize the top right eye + final QrEyeShapeStyle topRight; + + /// Customize the top left eye + final QrEyeShapeStyle topLeft; + + /// Sets the style to none + static const none = QrEyeShapeStyles.all(QrEyeShapeStyle.none); + + const QrEyeShapeStyles._(this.bottomLeft, this.topLeft, this.topRight) + :assert(bottomLeft != null && topRight != null && topLeft != null); + + const QrEyeShapeStyles.only( + {QrEyeShapeStyle bottomLeft = QrEyeShapeStyle.none, + QrEyeShapeStyle topRight = QrEyeShapeStyle.none, + QrEyeShapeStyle topLeft = QrEyeShapeStyle.none, + }) + :this._(bottomLeft, topLeft, topRight); + + /// Call this to create a uniform style + const QrEyeShapeStyles.all(QrEyeShapeStyle style) : + this._(style, style, style); + +} + /// Styling options for finder pattern eye. class QrEyeStyle { /// Create a new set of styling options for QR Eye. - const QrEyeStyle({this.eyeShape, this.color}); + const QrEyeStyle({ + this.eyeShape, + this.color, + this.shape = QrEyeShapeStyles.none, + }); /// Eye shape. final QrEyeShape eyeShape; @@ -69,6 +124,9 @@ class QrEyeStyle { /// Color to tint the eye. final Color color; + /// The custom shape of the eye + final QrEyeShapeStyles shape; + @override int get hashCode => eyeShape.hashCode ^ color.hashCode; From b467cced731d492c6a32739ac7c2a6bb6ccf1d7f Mon Sep 17 00:00:00 2001 From: Gibah Joseph Date: Wed, 3 Mar 2021 01:05:38 +0100 Subject: [PATCH 2/3] added dashed border --- lib/src/qr_painter.dart | 27 ++++++++++++++++++++++++++- lib/src/types.dart | 28 ++++++++++++++++------------ pubspec.yaml | 6 +++--- 3 files changed, 45 insertions(+), 16 deletions(-) diff --git a/lib/src/qr_painter.dart b/lib/src/qr_painter.dart index 86799d0..6ff3113 100644 --- a/lib/src/qr_painter.dart +++ b/lib/src/qr_painter.dart @@ -387,25 +387,50 @@ class QrPainter extends CustomPainter { ui.Canvas canvas) { BorderRadius eyeBorderRadius; BorderRadius eyeballBorderRadius; + bool dashedBorder = false; switch (position) { case FinderPatternPosition.topLeft: eyeBorderRadius = eyeStyle.shape.topLeft.eyeShape; eyeballBorderRadius = eyeStyle.shape.topLeft.eyeballShape; + dashedBorder = eyeStyle.shape.topLeft.dashedBorder; break; case FinderPatternPosition.topRight: eyeBorderRadius = eyeStyle.shape.topRight.eyeShape; eyeballBorderRadius = eyeStyle.shape.topRight.eyeballShape; + dashedBorder = eyeStyle.shape.topRight.dashedBorder; break; case FinderPatternPosition.bottomLeft: eyeBorderRadius = eyeStyle.shape.bottomLeft.eyeShape; eyeballBorderRadius = eyeStyle.shape.bottomLeft.eyeballShape; + dashedBorder = eyeStyle.shape.bottomLeft.dashedBorder; break; } var eyePath = drawRRect(outerRect.left, outerRect.top, outerRect.width, outerRect.height, eyeBorderRadius, outerPaint.strokeWidth); var eyeBallPath = drawRRect(dotRect.left, dotRect.top, dotRect.width, dotRect.height, eyeballBorderRadius, dotPaint.strokeWidth); - canvas.drawPath(eyePath..close(), outerPaint); + + if (dashedBorder) { + var eyeDashPath = Path(); + + var dashWidth = 10.0; + var dashSpace = 5.0; + var distance = 0.0; + eyePath.close(); + for (ui.PathMetric pathMetric in eyePath.computeMetrics()) { + while (distance < pathMetric.length) { + eyeDashPath.addPath( + pathMetric.extractPath(distance, distance + dashWidth), + Offset.zero, + ); + distance += dashWidth; + distance += dashSpace; + } + } + canvas.drawPath(eyeDashPath..close(), outerPaint); + } else { + canvas.drawPath(eyePath..close(), outerPaint); + } canvas.drawPath(eyeBallPath..close(), dotPaint); } diff --git a/lib/src/types.dart b/lib/src/types.dart index 209a427..4c56b84 100644 --- a/lib/src/types.dart +++ b/lib/src/types.dart @@ -69,10 +69,16 @@ class QrEyeShapeStyle { /// Set the border radius of the eyeball to give it a custom shape final BorderRadius eyeballShape; + /// if to use a dashed eye + /// + /// This field is ignored for the eyeball + final bool dashedBorder; + /// Create A new Shape style const QrEyeShapeStyle( - {this.eyeShape = BorderRadius.zero, this.eyeballShape = BorderRadius - .zero}); + {this.dashedBorder = false, + this.eyeShape = BorderRadius.zero, + this.eyeballShape = BorderRadius.zero}); /// Sets the style to none static const none = QrEyeShapeStyle( @@ -94,19 +100,17 @@ class QrEyeShapeStyles { static const none = QrEyeShapeStyles.all(QrEyeShapeStyle.none); const QrEyeShapeStyles._(this.bottomLeft, this.topLeft, this.topRight) - :assert(bottomLeft != null && topRight != null && topLeft != null); + : assert(bottomLeft != null && topRight != null && topLeft != null); - const QrEyeShapeStyles.only( - {QrEyeShapeStyle bottomLeft = QrEyeShapeStyle.none, - QrEyeShapeStyle topRight = QrEyeShapeStyle.none, - QrEyeShapeStyle topLeft = QrEyeShapeStyle.none, - }) - :this._(bottomLeft, topLeft, topRight); + const QrEyeShapeStyles.only({ + QrEyeShapeStyle bottomLeft = QrEyeShapeStyle.none, + QrEyeShapeStyle topRight = QrEyeShapeStyle.none, + QrEyeShapeStyle topLeft = QrEyeShapeStyle.none, + }) : this._(bottomLeft, topLeft, topRight); /// Call this to create a uniform style - const QrEyeShapeStyles.all(QrEyeShapeStyle style) : - this._(style, style, style); - + const QrEyeShapeStyles.all(QrEyeShapeStyle style) + : this._(style, style, style); } /// Styling options for finder pattern eye. diff --git a/pubspec.yaml b/pubspec.yaml index dffd4ee..0cd3234 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: qr_flutter description: > QR.Flutter is a Flutter library for simple and fast QR code rendering via a Widget or custom painter. -version: 3.2.0 +version: 3.2.2 homepage: https://github.com/lukef/qr.flutter environment: @@ -12,12 +12,12 @@ environment: dependencies: flutter: sdk: flutter - qr: ^1.2.0 + qr: ">=1.2.0 <=2.0.0" dev_dependencies: flutter_test: sdk: flutter - test: ^1.6.0 + test: ^1.16.4 flutter: uses-material-design: false From 96683da9a45de9db5dcedcc27fe8c6d4ea67f7d1 Mon Sep 17 00:00:00 2001 From: Gibah Joseph Date: Sun, 14 Mar 2021 21:12:31 +0100 Subject: [PATCH 3/3] fixed merge conflicts --- lib/src/qr_painter.dart | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/src/qr_painter.dart b/lib/src/qr_painter.dart index d6cad41..2ef5d99 100644 --- a/lib/src/qr_painter.dart +++ b/lib/src/qr_painter.dart @@ -16,7 +16,6 @@ import 'errors.dart'; import 'paint_cache.dart'; import 'qr_versions.dart'; import 'types.dart'; -import 'types.dart'; import 'validator.dart'; // ignore_for_file: deprecated_member_use_from_same_package @@ -279,8 +278,8 @@ class QrPainter extends CustomPainter { bool _isLogoArea(int x, int y) { //Find center of module count and portion to cut out of QR - var center = _qr.moduleCount / 2; - var canvasPortion = _qr.moduleCount * 0.15; + var center = _qr!.moduleCount / 2; + var canvasPortion = _qr!.moduleCount * 0.15; if (x > center - canvasPortion && x < center + canvasPortion && @@ -386,7 +385,7 @@ class QrPainter extends CustomPainter { ui.Canvas canvas) { BorderRadius eyeBorderRadius; BorderRadius eyeballBorderRadius; - bool dashedBorder = false; + var dashedBorder = false; switch (position) { case FinderPatternPosition.topLeft: eyeBorderRadius = eyeStyle.shape.topLeft.eyeShape; @@ -416,7 +415,7 @@ class QrPainter extends CustomPainter { var dashSpace = 5.0; var distance = 0.0; eyePath.close(); - for (ui.PathMetric pathMetric in eyePath.computeMetrics()) { + for (var pathMetric in eyePath.computeMetrics()) { while (distance < pathMetric.length) { eyeDashPath.addPath( pathMetric.extractPath(distance, distance + dashWidth),