From 0328d3b8bfeb45ba6de842b138ce79dfff270125 Mon Sep 17 00:00:00 2001 From: Zino Hofmann Date: Wed, 12 Aug 2020 16:49:59 +0000 Subject: [PATCH] feat: initial commit --- .gitignore | 75 ++++++ .metadata | 10 + CHANGELOG.md | 3 + LICENSE | 21 ++ README.md | 181 ++++++++++++++ analysis_options.yaml | 1 + example/README.md | 171 +++++++++++++ lib/rounded_qr.dart | 1 + lib/src/rounded_qr.dart | 127 ++++++++++ lib/src/rounded_qr_painter.dart | 422 ++++++++++++++++++++++++++++++++ pubspec.lock | 161 ++++++++++++ pubspec.yaml | 57 +++++ test/rounded_qr_test.dart | 1 + 13 files changed, 1231 insertions(+) create mode 100644 .gitignore create mode 100644 .metadata create mode 100644 CHANGELOG.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 analysis_options.yaml create mode 100644 example/README.md create mode 100644 lib/rounded_qr.dart create mode 100644 lib/src/rounded_qr.dart create mode 100644 lib/src/rounded_qr_painter.dart create mode 100644 pubspec.lock create mode 100644 pubspec.yaml create mode 100644 test/rounded_qr_test.dart diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bb431f0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,75 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +build/ + +# Android related +**/android/**/gradle-wrapper.jar +**/android/.gradle +**/android/captures/ +**/android/gradlew +**/android/gradlew.bat +**/android/local.properties +**/android/**/GeneratedPluginRegistrant.java + +# iOS/XCode related +**/ios/**/*.mode1v3 +**/ios/**/*.mode2v3 +**/ios/**/*.moved-aside +**/ios/**/*.pbxuser +**/ios/**/*.perspectivev3 +**/ios/**/*sync/ +**/ios/**/.sconsign.dblite +**/ios/**/.tags* +**/ios/**/.vagrant/ +**/ios/**/DerivedData/ +**/ios/**/Icon? +**/ios/**/Pods/ +**/ios/**/.symlinks/ +**/ios/**/profile +**/ios/**/xcuserdata +**/ios/.generated/ +**/ios/Flutter/App.framework +**/ios/Flutter/Flutter.framework +**/ios/Flutter/Flutter.podspec +**/ios/Flutter/Generated.xcconfig +**/ios/Flutter/app.flx +**/ios/Flutter/app.zip +**/ios/Flutter/flutter_assets/ +**/ios/Flutter/flutter_export_environment.sh +**/ios/ServiceDefinitions.json +**/ios/Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!**/ios/**/default.mode1v3 +!**/ios/**/default.mode2v3 +!**/ios/**/default.pbxuser +!**/ios/**/default.perspectivev3 +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages diff --git a/.metadata b/.metadata new file mode 100644 index 0000000..7c84e6f --- /dev/null +++ b/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 2ae34518b87dd891355ed6c6ea8cb68c4d52bb9d + channel: stable + +project_type: package diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..c1a8aad --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +## [1.0.0] - 12-08-2020. + +- Initial release. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8d3ed57 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Zino Hofmann B.V. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..118f0b6 --- /dev/null +++ b/README.md @@ -0,0 +1,181 @@ +# rounded_qr + +An easy to use package for creating QR codes that can be rounded, and can have an image in the center. + +This package uses the actively maintained [qr](https://pub.dev/packages/qr) package under the hood. + +## Usage + +### Default + +By default the modules in the QR code will have rounded corners with a radius of `4.0`. + +```dart +import 'package:flutter/material.dart'; +import 'package:rounded_qr/rounded_qr.dart'; + +void main() => runApp(MyApp()); + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + body: Center( + child: RoundedQR( + data: 'https://flutter.dev', + ), + ), + ), + ); + } +} +``` + +### Squared + +The radius of the modules is variable and can be removed completely by setting the `moduleRadius` parameter to `0.0`. + +```dart +import 'package:flutter/material.dart'; +import 'package:rounded_qr/rounded_qr.dart'; + +void main() => runApp(MyApp()); + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + body: Center( + child: RoundedQR( + data: 'https://flutter.dev', + moduleRadius: 0.0, + ), + ), + ), + ); + } +} +``` + +### With image + +Adding an image to the center can be done by simply passing an `AssetImage` to the `image` parameter. + +```dart +import 'package:flutter/material.dart'; +import 'package:rounded_qr/rounded_qr.dart'; + +void main() => runApp(MyApp()); + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + body: Center( + child: RoundedQR( + data: 'https://flutter.dev', + image: AssetImage('assets/images/flutter.png'), + ), + ), + ), + ); + } +} +``` + +### Custom colors + +Both the module color and the background color can be changed by setting the `moduleColor` and `backgroundColor` parameters respectively. + +> Note that the module color needs to be darker then the background colors to work with most QR code scanners. + +```dart +import 'package:flutter/material.dart'; +import 'package:rounded_qr/rounded_qr.dart'; + +void main() => runApp(MyApp()); + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + body: Center( + child: RoundedQR( + data: 'https://flutter.dev', + moduleColor: Colors.blue, + backgroundColor: Colors.grey, + ), + ), + ), + ); + } +} +``` + +### QR version + +The version can be changed to allow for more storage just by setting the `typeNumber` to the desired version (1 to 40.) + +```dart +import 'package:flutter/material.dart'; +import 'package:rounded_qr/rounded_qr.dart'; + +void main() => runApp(MyApp()); + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + body: Center( + child: RoundedQR( + data: 'https://flutter.dev', + typeNumber: 17, + ), + ), + ), + ); + } +} +``` + +### Error correction + +The error correction level can be changed to allow more data bytes to be restored by setting the `errorCorrectLevel` to the desired level. + +- Level L (Low) - 7% of data bytes can be restored. +- Level M (Medium) - 15% of data bytes can be restored. +- Level Q (Quartile) - 25% of data bytes can be restored. +- Level H (High) - 30% of data bytes can be restored. + +```dart +import 'package:flutter/material.dart'; +import 'package:qr/qr.dart'; // we will import the [QrErrorCorrectLevel] from the qr package +import 'package:rounded_qr/rounded_qr.dart'; + +void main() => runApp(MyApp()); + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + body: Center( + child: RoundedQR( + data: 'https://flutter.dev', + errorCorrectLevel: QrErrorCorrectLevel.H, + ), + ), + ), + ); + } +} +``` + +## Licence + +This Flutter package is made available under a [MIT license](https://github.com/zino-hofmann/rounded-qr-flutter/blob/master/LICENSE). diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..108d105 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1 @@ +include: package:pedantic/analysis_options.yaml diff --git a/example/README.md b/example/README.md new file mode 100644 index 0000000..1af5f10 --- /dev/null +++ b/example/README.md @@ -0,0 +1,171 @@ +# Examples + +## Default + +By default the modules in the QR code will have rounded corners with a radius of `4.0`. + +```dart +import 'package:flutter/material.dart'; +import 'package:rounded_qr/rounded_qr.dart'; + +void main() => runApp(MyApp()); + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + body: Center( + child: RoundedQR( + data: 'https://flutter.dev', + ), + ), + ), + ); + } +} +``` + +## Squared + +The radius of the modules is variable and can be removed completely by setting the `moduleRadius` parameter to `0.0`. + +```dart +import 'package:flutter/material.dart'; +import 'package:rounded_qr/rounded_qr.dart'; + +void main() => runApp(MyApp()); + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + body: Center( + child: RoundedQR( + data: 'https://flutter.dev', + moduleRadius: 0.0, + ), + ), + ), + ); + } +} +``` + +## With image + +Adding an image to the center can be done by simply passing an `AssetImage` to the `image` parameter. + +```dart +import 'package:flutter/material.dart'; +import 'package:rounded_qr/rounded_qr.dart'; + +void main() => runApp(MyApp()); + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + body: Center( + child: RoundedQR( + data: 'https://flutter.dev', + image: AssetImage('assets/images/flutter.png'), + ), + ), + ), + ); + } +} +``` + +## Custom colors + +Both the module color and the background color can be changed by setting the `moduleColor` and `backgroundColor` parameters respectively. + +> Note that the module color needs to be darker then the background colors to work with most QR code scanners. + +```dart +import 'package:flutter/material.dart'; +import 'package:rounded_qr/rounded_qr.dart'; + +void main() => runApp(MyApp()); + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + body: Center( + child: RoundedQR( + data: 'https://flutter.dev', + moduleColor: Colors.blue, + backgroundColor: Colors.grey, + ), + ), + ), + ); + } +} +``` + +## QR version + +The version can be changed to allow for more storage just by setting the `typeNumber` to the desired version (1 to 40.) + +```dart +import 'package:flutter/material.dart'; +import 'package:rounded_qr/rounded_qr.dart'; + +void main() => runApp(MyApp()); + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + body: Center( + child: RoundedQR( + data: 'https://flutter.dev', + typeNumber: 17, + ), + ), + ), + ); + } +} +``` + +## Error correction + +The error correction level can be changed to allow more data bytes to be restored by setting the `errorCorrectLevel` to the desired level. + +- Level L (Low) - 7% of data bytes can be restored. +- Level M (Medium) - 15% of data bytes can be restored. +- Level Q (Quartile) - 25% of data bytes can be restored. +- Level H (High) - 30% of data bytes can be restored. + +```dart +import 'package:flutter/material.dart'; +import 'package:qr/qr.dart'; // we will import the [QrErrorCorrectLevel] from the qr package +import 'package:rounded_qr/rounded_qr.dart'; + +void main() => runApp(MyApp()); + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + body: Center( + child: RoundedQR( + data: 'https://flutter.dev', + errorCorrectLevel: QrErrorCorrectLevel.H, + ), + ), + ), + ); + } +} +``` diff --git a/lib/rounded_qr.dart b/lib/rounded_qr.dart new file mode 100644 index 0000000..8e1b094 --- /dev/null +++ b/lib/rounded_qr.dart @@ -0,0 +1 @@ +export 'package:rounded_qr/src/rounded_qr.dart'; diff --git a/lib/src/rounded_qr.dart b/lib/src/rounded_qr.dart new file mode 100644 index 0000000..90b5049 --- /dev/null +++ b/lib/src/rounded_qr.dart @@ -0,0 +1,127 @@ +library rounded_qr; + +import 'dart:async'; +import 'dart:ui' as ui; + +import 'package:flutter/material.dart'; +import 'package:qr/qr.dart'; + +import 'package:rounded_qr/src/rounded_qr_painter.dart'; + +class RoundedQR extends StatefulWidget { + /// The data to be encoded inside the QR code. + final String data; + + /// The size of the widget. + final double size; + + /// The QR code version (1 to 40). + final int typeNumber; + + /// The level or error correction. + final int errorCorrectLevel; + + /// The color of the modules. + /// NOTE: The modules need to be darker then the background to make sure all QR code scanners can read it correctly. + final Color moduleColor; + + /// The border radius of the modules. + final double moduleRadius; + + /// The color of the background. + /// NOTE: The modules need to be darker then the background to make sure all QR code scanners can read it correctly. + final Color backgroundColor; + + /// Image in the center of the QR code. + final ImageProvider image; + + RoundedQR({ + Key key, + @required this.data, + this.size = 100, + this.typeNumber = 1, + this.errorCorrectLevel = QrErrorCorrectLevel.M, + this.moduleColor = Colors.black, + this.moduleRadius = 4.0, + this.backgroundColor = Colors.white, + this.image, + }) : super(key: key); + + @override + _RoundedQRState createState() => _RoundedQRState(); +} + +class _RoundedQRState extends State { + Future _loadImage(BuildContext buildContext) async { + final completer = Completer(); + + final stream = widget.image.resolve( + ImageConfiguration( + devicePixelRatio: MediaQuery.of(buildContext).devicePixelRatio, + ), + ); + + stream.addListener( + ImageStreamListener( + (imageInfo, error) { + completer.complete(imageInfo.image); + }, + onError: (dynamic error, _) { + completer.completeError(error); + }, + ), + ); + + return completer.future; + } + + @override + Widget build(BuildContext context) { + if (widget.image == null) { + return CustomPaint( + size: Size( + widget.size, + widget.size, + ), + painter: RoundedQRPainter( + data: widget.data, + typeNumber: widget.typeNumber, + errorCorrectLevel: widget.errorCorrectLevel, + moduleColor: widget.moduleColor, + moduleRadius: widget.moduleRadius, + backgroundColor: widget.backgroundColor, + ), + ); + } + + return FutureBuilder( + future: _loadImage(context), + builder: ( + BuildContext context, + AsyncSnapshot snapshot, + ) { + if (snapshot.hasData) { + return Container( + child: CustomPaint( + size: Size( + widget.size, + widget.size, + ), + painter: RoundedQRPainter( + data: widget.data, + typeNumber: widget.typeNumber, + errorCorrectLevel: widget.errorCorrectLevel, + moduleColor: widget.moduleColor, + moduleRadius: widget.moduleRadius, + backgroundColor: widget.backgroundColor, + image: snapshot.data, + ), + ), + ); + } + + return Container(); + }, + ); + } +} diff --git a/lib/src/rounded_qr_painter.dart b/lib/src/rounded_qr_painter.dart new file mode 100644 index 0000000..b294411 --- /dev/null +++ b/lib/src/rounded_qr_painter.dart @@ -0,0 +1,422 @@ +import 'dart:ui' as ui; + +import 'package:flutter/material.dart'; +import 'package:qr/qr.dart'; + +class RoundedQRPainter extends CustomPainter { + final String data; + final int typeNumber; + final int errorCorrectLevel; + final Color moduleColor; + final double moduleRadius; + final Color backgroundColor; + + ui.Image image; + int deletePixelCount = 0; + + QrCode _qrCode; + + RoundedQRPainter({ + @required this.data, + @required this.typeNumber, + @required this.errorCorrectLevel, + @required this.moduleColor, + @required this.moduleRadius, + @required this.backgroundColor, + this.image, + }) { + _qrCode = QrCode(typeNumber, errorCorrectLevel) + ..addData(data) + ..make(); + } + + @override + void paint( + Canvas canvas, + Size size, + ) { + if (image != null) { + if (typeNumber <= 2) { + deletePixelCount = typeNumber + 7; + } else if (typeNumber <= 4) { + deletePixelCount = typeNumber + 8; + } else { + deletePixelCount = typeNumber + 9; + } + + var imageSize = Size( + image.width.toDouble(), + image.height.toDouble(), + ); + + var src = Alignment.center.inscribe( + imageSize, + Rect.fromLTWH( + 0, + 0, + image.width.toDouble(), + image.height.toDouble(), + ), + ); + + var dst = Alignment.center.inscribe( + Size( + size.height / 4, + size.height / 4, + ), + Rect.fromLTWH( + size.width / 3, + size.height / 3, + size.height / 3, + size.height / 3, + ), + ); + + canvas.drawImageRect( + image, + src, + dst, + Paint(), + ); + } + + moduleRadius > 0.0 + ? _paintRound(canvas, size) + : _paintDefault(canvas, size); + } + + void _paintRound( + Canvas canvas, + Size size, + ) { + var _paint = Paint() + ..style = PaintingStyle.fill + ..color = moduleColor + ..isAntiAlias = true; + + var _paintBackground = Paint() + ..style = PaintingStyle.fill + ..color = backgroundColor + ..isAntiAlias = true; + + var matrix = List(_qrCode.moduleCount + 2); + + for (var i = 0; i < _qrCode.moduleCount + 2; i++) { + matrix[i] = List(_qrCode.moduleCount + 2); + } + + for (var x = 0; x < _qrCode.moduleCount + 2; x++) { + for (var y = 0; y < _qrCode.moduleCount + 2; y++) { + matrix[x][y] = false; + } + } + + for (var x = 0; x < _qrCode.moduleCount; x++) { + for (var y = 0; y < _qrCode.moduleCount; y++) { + if (image != null && + x >= deletePixelCount && + y >= deletePixelCount && + x < _qrCode.moduleCount - deletePixelCount && + y < _qrCode.moduleCount - deletePixelCount) { + matrix[y + 1][x + 1] = false; + continue; + } + + if (_qrCode.isDark(y, x)) { + matrix[y + 1][x + 1] = true; + } else { + matrix[y + 1][x + 1] = false; + } + } + } + + var pixelSize = size.width / _qrCode.moduleCount; + + for (var x = 0; x < _qrCode.moduleCount; x++) { + for (var y = 0; y < _qrCode.moduleCount; y++) { + if (matrix[y + 1][x + 1]) { + final squareRect = Rect.fromLTWH( + x * pixelSize, + y * pixelSize, + pixelSize, + pixelSize, + ); + + _setShape( + x + 1, + y + 1, + squareRect, + _paint, + matrix, + canvas, + _qrCode.moduleCount, + ); + } else { + _setShapeInner( + x + 1, + y + 1, + _paintBackground, + matrix, + canvas, + pixelSize, + ); + } + } + } + } + + void _drawCurve( + Offset p1, + Offset p2, + Offset p3, + Canvas canvas, + ) { + var path = Path(); + + path.moveTo( + p1.dx, + p1.dy, + ); + path.quadraticBezierTo( + p2.dx, + p2.dy, + p3.dx, + p3.dy, + ); + path.lineTo( + p2.dx, + p2.dy, + ); + path.lineTo( + p1.dx, + p1.dy, + ); + path.close(); + + canvas.drawPath( + path, + Paint() + ..style = PaintingStyle.fill + ..color = moduleColor, + ); + } + + // rounding the inner corners (with the background color) + void _setShapeInner( + int x, + int y, + Paint paint, + List matrix, + Canvas canvas, + double pixelSize, + ) { + final widthY = pixelSize * (y - 1); + final heightX = pixelSize * (x - 1); + + // bottom right check + if (matrix[y + 1][x] && matrix[y][x + 1] && matrix[y + 1][x + 1]) { + final p1 = Offset( + heightX + pixelSize - (0.25 * pixelSize), + widthY + pixelSize, + ); + final p2 = Offset( + heightX + pixelSize, + widthY + pixelSize, + ); + final p3 = Offset( + heightX + pixelSize, + widthY + pixelSize - (0.25 * pixelSize), + ); + + _drawCurve( + p1, + p2, + p3, + canvas, + ); + } + + // top left check + if (matrix[y - 1][x] && matrix[y][x - 1] && matrix[y - 1][x - 1]) { + final p1 = Offset( + heightX, + widthY + (0.25 * pixelSize), + ); + final p2 = Offset( + heightX, + widthY, + ); + final p3 = Offset( + heightX + (0.25 * pixelSize), + widthY, + ); + + _drawCurve( + p1, + p2, + p3, + canvas, + ); + } + + // bottom left check + if (matrix[y + 1][x] && matrix[y][x - 1] && matrix[y + 1][x - 1]) { + final p1 = Offset( + heightX, + widthY + pixelSize - (0.25 * pixelSize), + ); + final p2 = Offset( + heightX, + widthY + pixelSize, + ); + final p3 = Offset( + heightX + (0.25 * pixelSize), + widthY + pixelSize, + ); + + _drawCurve( + p1, + p2, + p3, + canvas, + ); + } + + // top right check + if (matrix[y - 1][x] && matrix[y][x + 1] && matrix[y - 1][x + 1]) { + final p1 = Offset( + heightX + pixelSize - (0.25 * pixelSize), + widthY, + ); + final p2 = Offset( + heightX + pixelSize, + widthY, + ); + final p3 = Offset( + heightX + pixelSize, + widthY + (0.25 * pixelSize), + ); + + _drawCurve( + p1, + p2, + p3, + canvas, + ); + } + } + + // round the corners and paint it + void _setShape( + int x, + int y, + Rect squareRect, + Paint paint, + List matrix, + Canvas canvas, + int n, + ) { + var bottomRight = false; + var bottomLeft = false; + var topRight = false; + var topLeft = false; + + // if it is dot (arount an empty place) + if (!matrix[y + 1][x] && + !matrix[y][x + 1] && + !matrix[y - 1][x] && + !matrix[y][x - 1]) { + canvas.drawRRect( + RRect.fromRectAndCorners( + squareRect, + bottomRight: Radius.circular(moduleRadius / 2), + bottomLeft: Radius.circular(moduleRadius / 2), + topLeft: Radius.circular(moduleRadius / 2), + topRight: Radius.circular(moduleRadius / 2), + ), + paint, + ); + + return; + } + + // bottom right check + if (!matrix[y + 1][x] && !matrix[y][x + 1]) { + bottomRight = true; + } + + // top left check + if (!matrix[y - 1][x] && !matrix[y][x - 1]) { + topLeft = true; + } + + // bottom left check + if (!matrix[y + 1][x] && !matrix[y][x - 1]) { + bottomLeft = true; + } + + // top right check + if (!matrix[y - 1][x] && !matrix[y][x + 1]) { + topRight = true; + } + + canvas.drawRRect( + RRect.fromRectAndCorners( + squareRect, + bottomRight: bottomRight ? Radius.circular(moduleRadius) : Radius.zero, + bottomLeft: bottomLeft ? Radius.circular(moduleRadius) : Radius.zero, + topLeft: topLeft ? Radius.circular(moduleRadius) : Radius.zero, + topRight: topRight ? Radius.circular(moduleRadius) : Radius.zero, + ), + paint, + ); + + // if it is dot (arount an empty place) + if (!bottomLeft && !bottomRight && !topLeft && !topRight) { + canvas.drawRect( + squareRect, + paint, + ); + } + } + + void _paintDefault( + Canvas canvas, + Size size, + ) { + var _paint = Paint() + ..style = PaintingStyle.fill + ..color = moduleColor + ..isAntiAlias = true; + + // size of point + final pixelSize = size.width / _qrCode.moduleCount; + + for (var x = 0; x < _qrCode.moduleCount; x++) { + for (var y = 0; y < _qrCode.moduleCount; y++) { + if (image != null && + x >= deletePixelCount && + y >= deletePixelCount && + x < _qrCode.moduleCount - deletePixelCount && + y < _qrCode.moduleCount - deletePixelCount) { + continue; + } + + if (_qrCode.isDark(y, x)) { + canvas.drawRect( + Rect.fromLTWH( + x * pixelSize, + y * pixelSize, + pixelSize, + pixelSize, + ), + _paint, + ); + } + } + } + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => false; +} diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..3d6f1ac --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,161 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + url: "https://pub.dartlang.org" + source: hosted + version: "2.4.2" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + characters: + dependency: transitive + description: + name: characters + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + charcode: + dependency: transitive + description: + name: charcode + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.3" + clock: + dependency: transitive + description: + name: clock + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + collection: + dependency: transitive + description: + name: collection + url: "https://pub.dartlang.org" + source: hosted + version: "1.14.13" + fake_async: + dependency: transitive + description: + name: fake_async + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + matcher: + dependency: transitive + description: + name: matcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.8" + meta: + dependency: transitive + description: + name: meta + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.8" + path: + dependency: transitive + description: + name: path + url: "https://pub.dartlang.org" + source: hosted + version: "1.7.0" + pedantic: + dependency: "direct dev" + description: + name: pedantic + url: "https://pub.dartlang.org" + source: hosted + version: "1.9.0" + qr: + dependency: "direct main" + description: + name: qr + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + url: "https://pub.dartlang.org" + source: hosted + version: "1.7.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "1.9.5" + stream_channel: + dependency: transitive + description: + name: stream_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.5" + term_glyph: + dependency: transitive + description: + name: term_glyph + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + test_api: + dependency: transitive + description: + name: test_api + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.17" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + vector_math: + dependency: transitive + description: + name: vector_math + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.8" +sdks: + dart: ">=2.9.0-14.0.dev <3.0.0" + flutter: ">=1.17.0 <2.0.0" diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..1db9357 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,57 @@ +name: rounded_qr +description: An easy to use package for creating QR codes that can be rounded, and can have an image in the center. +version: 1.0.0 +homepage: https://github.com/zino-hofmann/rounded-qr-flutter + +environment: + sdk: ">=2.7.0 <3.0.0" + flutter: ">=1.17.0 <2.0.0" + +dependencies: + flutter: + sdk: flutter + + qr: ^1.3.0 + +dev_dependencies: + flutter_test: + sdk: flutter + + pedantic: ^1.9.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter. +flutter: + + # To add assets to your package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/assets-and-images/#from-packages + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware. + + # To add custom fonts to your package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.dev/custom-fonts/#from-packages diff --git a/test/rounded_qr_test.dart b/test/rounded_qr_test.dart new file mode 100644 index 0000000..ab73b3a --- /dev/null +++ b/test/rounded_qr_test.dart @@ -0,0 +1 @@ +void main() {}