One way suggested by the official doc of Flutter, is to:
To display a subpart of an image, consider using a CustomPainter and Canvas.drawImageRect.
ref: https://api.flutter.dev/flutter/painting/DecorationImage/alignment.html
Thus here is my full code. Use PartImage
to show what you want.
class PartImage extends StatefulWidget {
const PartImage({
Key key,
@required this.imageProvider,
@required this.transform,
}) : assert(imageProvider != null),
super(key: key);
final ImageProvider imageProvider;
final Matrix4 transform;
@override
_PartImageState createState() => _PartImageState();
}
class _PartImageState extends State<PartImage> {
ImageStream _imageStream;
ImageInfo _imageInfo;
@override
void didChangeDependencies() {
super.didChangeDependencies();
_getImage();
}
@override
void didUpdateWidget(PartImage oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.imageProvider != oldWidget.imageProvider) _getImage();
}
void _getImage() {
final oldImageStream = _imageStream;
_imageStream = widget.imageProvider.resolve(createLocalImageConfiguration(context));
if (_imageStream.key != oldImageStream?.key) {
final listener = ImageStreamListener(_updateImage);
oldImageStream?.removeListener(listener);
_imageStream.addListener(listener);
}
}
void _updateImage(ImageInfo imageInfo, bool synchronousCall) {
setState(() {
_imageInfo = imageInfo;
});
}
@override
void dispose() {
_imageStream.removeListener(ImageStreamListener(_updateImage));
super.dispose();
}
@override
Widget build(BuildContext context) {
return RawPartImage(
image: _imageInfo?.image, // this is a dart:ui Image object
scale: _imageInfo?.scale ?? 1.0,
transform: widget.transform,
);
}
}
/// ref: [RawImage]
class RawPartImage extends StatelessWidget {
final ui.Image image;
final double scale;
final Matrix4 transform;
const RawPartImage({Key key, this.image, this.scale, this.transform}) : super(key: key);
@override
Widget build(BuildContext context) {
return CustomPaint(
painter: _RawPartImagePainter(
image: image,
scale: scale,
transform: transform,
),
);
}
}
class _RawPartImagePainter extends CustomPainter {
final ui.Image image;
final double scale;
final Matrix4 transform;
final painter = Paint();
_RawPartImagePainter({this.image, this.scale, this.transform});
@override
void paint(Canvas canvas, Size size) {
if (image == null) {
return;
}
final transformInv = Matrix4.inverted(transform);
final dst = Offset.zero & size;
final src = Rect.fromPoints(
transformOffset(transformInv, dst.topLeft),
transformOffset(transformInv, dst.bottomRight),
);
// print('src=$src dst=$dst');
canvas.drawImageRect(image, src, dst, painter);
}
@override
bool shouldRepaint(covariant _RawPartImagePainter oldDelegate) {
return oldDelegate.image != image || //
oldDelegate.scale != scale ||
oldDelegate.transform != transform;
}
}
Offset transformOffset(Matrix4 transform, Offset offset) {
Vector4 vecOut = transform * Vector4(offset.dx, offset.dy, 0, 1);
return Offset(vecOut.x, vecOut.y);
}
By the way, if you are interested in knowing what happens behind drawImageRect
:
- Have a search https://github.com/flutter/engine/search?q=drawImageRect
- This seems the C++ code that
drawImageRect
(i.e. _drawImageRect
actually calls: https://github.com/flutter/engine/blob/6bc70e4a114ff4c01b60c77bae754bace5683f6d/lib/ui/painting/canvas.cc#L330
- It calls
canvas_->drawImageRect
. What is canvas_
? From the header file we see it is of type SkCanvas* canvas_;
.
- Then we go into the world of
Skia
(not Flutter or Dart anymore). https://skia.org/user/api/skcanvas_overview for an overview of SkCanvas. And https://api.skia.org/classSkCanvas.html#a680ab85c3c7b5eab23b853b97f914334 for the actual SkCanvas.drawImageRect
documentation.