Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions lib/src/qr_image.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -76,14 +76,15 @@ 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,
super(key: key);

// The data passed to the widget
final String _data;

// The QR code data passed to the widget
final QrCode _qrCode;

Expand All @@ -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;

Expand Down Expand Up @@ -220,6 +227,7 @@ class _QrImageState extends State<QrImage> {
gapless: widget.gapless,
embeddedImageStyle: widget.embeddedImageStyle,
embeddedImage: image,
blendEmbeddedImage: widget.blendEmbeddedImage,
eyeStyle: widget.eyeStyle,
dataModuleStyle: widget.dataModuleStyle,
);
Expand Down Expand Up @@ -250,6 +258,7 @@ class _QrImageState extends State<QrImage> {
}

ImageStreamListener streamListener;

Future<ui.Image> _loadQrImage(
BuildContext buildContext, QrEmbeddedImageStyle style) async {
if (style != null) {}
Expand Down
108 changes: 108 additions & 0 deletions lib/src/qr_painter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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),
Expand All @@ -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),
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 &&
Expand Down Expand Up @@ -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));
Expand All @@ -351,6 +378,84 @@ 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;
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);

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);
}

/// 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(
Expand Down Expand Up @@ -435,12 +540,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() {
Expand Down
64 changes: 63 additions & 1 deletion lib/src/types.dart
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ enum QrEyeShape {

/// Use circular eye frame.
circle,

/// Use custom frame
custom
}

/// Enumeration representing the shape of Data modules inside QR.
Expand All @@ -58,17 +61,76 @@ 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;

/// if to use a dashed eye
///
/// This field is ignored for the eyeball
final bool dashedBorder;

/// Create A new Shape style
const QrEyeShapeStyle(
{this.dashedBorder = false,
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;

/// 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;

Expand Down
6 changes: 3 additions & 3 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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