Aim : I want to make an image marking functionality i.e drawing on an image along with zoom-in and zoom-out functions. For that I have used a canvas with an image on it and flutter Transform widget to achieve zoom and translation. I have succeeded in making the image zoom and translate. Also I can perform drawing on that image.
Problem : The problem is I can perform drawing only on the area which was set when Image was in original size, i.e when Image expands then I can draw on only that area(original image area) which comes under the new expanded(zoomed) image, same problem occurs when I move the image.
NOTE - I have set the size of canvas to the size of my image.
On the below screen-shots 1.before_zoom displays my image when I load it from assets. On that I had drawn a rectangle which shows drawing can be done on any part of image. In after_zoom image I had again drawn a rectangle which show the available screen for drawing. If I try to perform drawing outside that rectangle, it doesn't works same problem occurs when I move the image. The problem I think is that canvas drawing space gets fixed when image first loads and when the image expands or moves it just lets drawing on the area which was first set on time of image loading. So anyone knows how to fix these problem or any other method to achieve these functionality.
Code :
/// DrawingScreen :
import 'dart:convert';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'dart:ui' as ui;
import 'package:flutter/services.dart';
class _DrawingScreenState extends State<DrawingScreen> {
ui.Image _image;
Future _future;
Color _selectedColor;
double _strokeWidth;
List<TouchPoints> points = [];
@override
void initState() {
super.initState();
_selectedColor = Colors.yellow;
_strokeWidth = 14.0;
_future = load();
}
String imageLocation = 'images/2.jpg';
/// FUNCTION TO LOAD THE IMAGE FROM THE ASSETS FOLDER
Future<void> load() async {
ByteData data = await rootBundle.load(imageLocation);
ui.Codec codec1 = await ui.instantiateImageCodec(data.buffer.asUint8List());
ui.FrameInfo fi = await codec1.getNextFrame();
_image = fi.image;
tempIW = _image.width.toDouble();
tempIH = _image.height.toDouble();
}
double translateX = 0;
double translateY = 0;
double scaleFactor = 1;
double tempIW;
double tempIH;
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: _future,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return Scaffold(
bottomNavigationBar: Container(
color: Color(0xffF8F8FF),
child: Row(children: [
Flexible(
child: IconButton(
padding: EdgeInsets.zero,
iconSize: 20,
onPressed: () {
setState(() {
if (scaleFactor > 0.11)
setState(() {
scaleFactor = scaleFactor - 0.1;
});
});
},
icon: Icon(Icons.zoom_in),
),
),
Flexible(
child: IconButton(
iconSize: 20,
padding: EdgeInsets.zero,
onPressed: () {
setState(() {
if (scaleFactor < 0.91) {
scaleFactor = scaleFactor + 0.1;
}
});
},
icon: Icon(Icons.zoom_out),
),
),
Flexible(
child: IconButton(
iconSize: 20,
padding: EdgeInsets.zero,
onPressed: () {
setState(() {
translateX = translateX - 100;
});
},
icon: Icon(Icons.arrow_back),
),
),
Flexible(
child: IconButton(
iconSize: 20,
padding: EdgeInsets.zero,
onPressed: () {
setState(() {
translateX = translateX + 100;
});
},
icon: Icon(Icons.arrow_forward),
),
),
Flexible(
child: IconButton(
iconSize: 20,
padding: EdgeInsets.zero,
onPressed: () {
setState(() {
translateY = translateY - 100;
});
},
icon: Icon(Icons.arrow_upward),
),
),
Flexible(
child: IconButton(
iconSize: 20,
padding: EdgeInsets.zero,
onPressed: () {
setState(() {
translateY = translateY + 100;
});
},
icon: Icon(Icons.arrow_downward),
),
),
Flexible(
child: IconButton(
iconSize: 20,
padding: EdgeInsets.zero,
onPressed: () {
this.setState(() {
points.clear();
});
},
icon: Icon(Icons.layers_clear),
),
),
Flexible(
child: IconButton(
iconSize: 20,
padding: EdgeInsets.zero,
onPressed: () {
undo();
setState(() {
points;
});
},
icon: Icon(Icons.wifi_protected_setup),
),
),
]),
),
body: SafeArea(
child: Center(
child: FittedBox(
fit: BoxFit.fill,
child: Transform(
transform: Matrix4(
1, 0, 0, 0, //
0, 1, 0, 0, //
0, 0, 1, 0, //
translateX, translateY, 0, scaleFactor,
),
child: GestureDetector(
onPanStart: (details) {
setState(() {
if (details.localPosition.dx < _image.width.toDouble() && details.localPosition.dy < _image.height.toDouble())
points.add(
TouchPoints(
points: Offset(
details.localPosition.dx,
details.localPosition.dy,
),
paint: Paint()
..color = _selectedColor
..strokeWidth = _strokeWidth
..strokeCap = StrokeCap.round,
),
);
});
},
onPanUpdate: (details) {
setState(() {
if (details.localPosition.dx < _image.width.toDouble() && details.localPosition.dy < _image.height.toDouble())
points.add(
TouchPoints(
points: Offset(
details.localPosition.dx,
details.localPosition.dy,
),
paint: Paint()
..color = _selectedColor
..strokeWidth = _strokeWidth
..strokeCap = StrokeCap.round,
),
);
});
},
onPanEnd: (details) {
setState(() {
points.add(null);
});
print(details.velocity);
},
child: RepaintBoundary(
child: SizedBox(
width: _image.width.toDouble(),
height: _image.height.toDouble(),
child: ClipRect(
child: CustomPaint(
painter: FacePainter(
_image,
points,
),
),
),
),
),
),
),
),
),
),
);
} else {
return CircularProgressIndicator();
}
},
);
}
}
class FacePainter extends CustomPainter {
final ui.Image _image;
List<TouchPoints> pointsList = [];
FacePainter(
this._image,
this.pointsList,
) : super();
@override
void paint(Canvas canvas, Size size) async {
canvas.drawImage(_image, Offset(0.0, 0.0), Paint());
for (int i = 0; i < pointsList.length - 1; i++) {
if (pointsList[i] != null && pointsList[i + 1] != null) {
if (pointsList[i].points.dx < size.width && pointsList[i].points.dy < size.height)
canvas.drawLine(pointsList[i].points, pointsList[i + 1].points, pointsList[i].paint);
}
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}
class TouchPoints {
Paint paint;
Offset points;
TouchPoints({this.paint, this.points});
}
Images for reference. Before Zoom