-
-
Save Hecatoncheir/08173a09710febef2811c12e48f4b69b to your computer and use it in GitHub Desktop.
Flutter crop circles, background image, draggable, transparent hole
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:convert'; | |
import 'dart:io'; | |
import 'dart:typed_data'; | |
import 'dart:ui' as ui; | |
import 'package:flutter/material.dart'; | |
import 'package:flutter/rendering.dart'; | |
import 'package:nft/services/safety/base_stateful.dart'; | |
import 'package:nft/widgets/p_appbar_transparency.dart'; | |
import 'package:path_provider/path_provider.dart'; | |
class WCircleCrop extends StatefulWidget { | |
@override | |
_WCircleCropState createState() => _WCircleCropState(); | |
} | |
class _WCircleCropState extends BaseStateful<WCircleCrop> { | |
final GlobalKey _backgroundImgKey = GlobalKey(); | |
final GlobalKey _circleViewKey = GlobalKey(); | |
Uint8List imgBytes; | |
ByteData _readFromFile(File file) { | |
final Uint8List bytes = file.readAsBytesSync(); | |
return ByteData.view(bytes.buffer); | |
} | |
Future<File> _writeToFile(ByteData data) async { | |
// https://pub.dev/packages/path_provider | |
final String dir = (await getTemporaryDirectory()).path; | |
final String filePath = '$dir/tempImage.jpg'; | |
final ByteBuffer buffer = data.buffer; | |
return File(filePath).writeAsBytes( | |
buffer.asUint8List(data.offsetInBytes, data.lengthInBytes)); | |
} | |
Future<ui.Image> _loadImageSource(File imageSource) async { | |
// ByteData data = await rootBundle.load(asset); | |
final ByteData data = _readFromFile(imageSource); | |
final ui.Codec codec = | |
await ui.instantiateImageCodec(data.buffer.asUint8List()); | |
final ui.FrameInfo fi = await codec.getNextFrame(); | |
return fi.image; | |
} | |
Future<Uint8List> _capturePng() async { | |
try { | |
final RenderBox circleBoundary = | |
_circleViewKey.currentContext.findRenderObject() as RenderBox; | |
final Size circleBoundarySize = circleBoundary.size; | |
final Offset globalTopLeft = circleBoundary.localToGlobal(Offset.zero); | |
final RenderRepaintBoundary backgroundBoundary = | |
_backgroundImgKey.currentContext.findRenderObject() | |
as RenderRepaintBoundary; | |
// Background to image | |
const double scale = 2.0; | |
final double outputW = circleBoundarySize.width * scale; | |
final double outputH = circleBoundarySize.height * scale; | |
final double srcLeft = globalTopLeft.dx * scale; | |
final double srcTop = globalTopLeft.dy * scale; | |
final ui.Image image = | |
await backgroundBoundary.toImage(pixelRatio: scale); | |
// Edit background image | |
final ui.PictureRecorder recorder = ui.PictureRecorder(); | |
final ui.Rect rect = Rect.fromLTWH(0, 0, outputW, outputH); | |
final Canvas canvas = Canvas(recorder, rect); | |
final Paint paint = Paint(); | |
// Clip circle | |
final Path path = Path()..addOval(Rect.fromLTWH(0, 0, outputW, outputH)); | |
// Requires save and restore canvas to local clipPath | |
canvas.save(); | |
canvas.clipPath(path); | |
canvas.drawImageRect( | |
image, | |
Rect.fromLTWH(srcLeft, srcTop, outputW, outputH), | |
Rect.fromLTWH(0, 0, outputW, outputH), | |
paint); | |
canvas.restore(); | |
// Finish edit, convert to image render | |
final ui.Picture picture = recorder.endRecording(); | |
final ui.Image img = | |
await picture.toImage(rect.width.toInt(), rect.height.toInt()); | |
final ByteData byteData2 = | |
await img.toByteData(format: ui.ImageByteFormat.png); | |
final Uint8List pngBytes2 = byteData2.buffer.asUint8List(); | |
final String bs642 = base64Encode(pngBytes2); | |
print(pngBytes2); | |
print(bs642); | |
setState(() { | |
imgBytes = pngBytes2; | |
}); | |
return pngBytes2; | |
} catch (e) { | |
print(e); | |
} | |
return null; | |
} | |
@override | |
Widget build(BuildContext context) { | |
super.build(context); | |
return PAppBarTransparency( | |
child: Stack( | |
fit: StackFit.expand, | |
children: <Widget>[ | |
RepaintBoundary( | |
key: _backgroundImgKey, | |
child: Image.network( | |
'https://wallpaperplay.com/walls/full/e/5/3/13586.jpg', | |
fit: BoxFit.cover, | |
), | |
), | |
ColorFiltered( | |
// This one will create the magic | |
colorFilter: ColorFilter.mode( | |
Colors.black.withOpacity(0.8), BlendMode.srcOut), | |
child: Stack( | |
fit: StackFit.expand, | |
children: <Widget>[ | |
Container( | |
decoration: const BoxDecoration( | |
color: Colors.black, | |
// This one will handle background + difference out | |
backgroundBlendMode: BlendMode.dstOut), | |
), | |
_WDraggable( | |
child: Container( | |
key: _circleViewKey, | |
height: 200, | |
width: 200, | |
decoration: BoxDecoration( | |
color: Colors.white, | |
borderRadius: BorderRadius.circular(100), | |
), | |
), | |
), | |
const Center( | |
child: Text( | |
'Hello World', | |
style: TextStyle(fontSize: 70, fontWeight: FontWeight.w600), | |
), | |
) | |
], | |
), | |
), | |
if (imgBytes != null) | |
IgnorePointer( | |
child: Container( | |
alignment: Alignment.bottomCenter, | |
child: Image.memory( | |
imgBytes, | |
height: 200, | |
width: 200, | |
fit: BoxFit.contain, | |
), | |
), | |
), | |
Center( | |
child: RaisedButton( | |
onPressed: () { | |
_capturePng(); | |
}, | |
child: const Text('Capture'), | |
), | |
) | |
], | |
), | |
); | |
} | |
} | |
/// Draggable widget | |
class _WDraggable extends StatefulWidget { | |
const _WDraggable({Key key, this.child, this.valueChanged}) : super(key: key); | |
final Widget child; | |
final ValueChanged<Offset> valueChanged; | |
@override | |
_WDraggableState createState() => _WDraggableState(); | |
} | |
class _WDraggableState extends State<_WDraggable> { | |
ValueNotifier<Offset> valueListener = ValueNotifier<Offset>(const Offset(1, 1)); | |
@override | |
void initState() { | |
valueListener.addListener(_notifyParent); | |
super.initState(); | |
} | |
@override | |
Widget build(BuildContext context) { | |
return Builder( | |
builder: (BuildContext context) { | |
final GestureDetector handle = GestureDetector( | |
onPanUpdate: (DragUpdateDetails details) { | |
final double dx = (valueListener.value.dx + | |
(details.delta.dx / context.size.width)) | |
.clamp(0.0, 1.0) | |
.toDouble(); | |
final double dy = (valueListener.value.dy - | |
(details.delta.dy / context.size.height)) | |
.clamp(0.0, 1.0) | |
.toDouble(); | |
valueListener.value = Offset(dx, dy); | |
}, | |
child: widget.child, | |
); | |
return AnimatedBuilder( | |
animation: valueListener, | |
builder: (_, Widget child) { | |
return Align( | |
alignment: Alignment(valueListener.value.dx * 2 - 1, | |
1 - valueListener.value.dy * 2), | |
child: child, | |
); | |
}, | |
child: handle, | |
); | |
}, | |
); | |
} | |
// Notify change to parent | |
void _notifyParent() { | |
if (widget.valueChanged != null) { | |
widget.valueChanged(valueListener.value); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment