- How do we crop a circular part of an bitmap image and put it on another image (bitmap)?
- How do we make a gradient transparency of the circular image?
Any thoughts?
Below is the illustration of what i meant.
CodePudding user response:
So the thing you want is not that complicated but quite complex.
For the first stage, you just need to crop a part of the image - depending on where you should do it - directly in memory, on UI, or using some combination of both you just need to pick a library(or method to do it). Here, here, here, just googling, or using an old regular
ClipRRect(
borderRadius: new BorderRadius.circular(50),
child: Image.asset('your_image_path', height: 100, width: 100),
)
you will be able to find a suitable tool or at least an approach.
For the second stage, you can either use a bitmap drawing using Canvas
or create a Stack
widget with the correct offsets and then rasterizing the resulting widget via RenderRepaintBoundry
. By the way this library can help you with this task also.
For the third task, I know only one relatively easy way - you should use ShaderMask
:
ShaderMask(
shaderCallback: (rect) {
return RadialGradient(
radius: 50,
colors: [Colors.black, Colors.transparent],
).createShader(Rect.fromLTRB(0, 0, rect.width,
rect.height)); // I'm not sure about the correct Rect creation so may need to experiment
},
blendMode: BlendMode.dstIn,
child: Image.asset(
'your_image_path',
height: 100,
fit: BoxFit.contain,
),
),
By combining the approaches listed in the answer, you will be able to achieve what you want. The easiest implementation will look like this:
class CaptureImage extends StatefulWidget {
const CaptureImage({super.key});
@override
State<CaptureImage> createState() => _CaptureImageState();
}
class _CaptureImageState extends State<CaptureImage> {
GlobalKey globalKey = GlobalKey();
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
_captureImage();
});
}
@override
Widget build(BuildContext context) => RepaintBoundary(
key: globalKey,
child: Stack(
alignment: Alignment.topCenter,
children: <Widget>[
AspectRatio(
aspectRatio: 1,
child: Image.asset('your_background_image', fit: BoxFit.cover),
),
Positioned(
top: 20,
left: 20,
child: ShaderMask(
shaderCallback: (rect) => const RadialGradient(
radius: 50,
colors: [Colors.black, Colors.transparent],
).createShader(
Rect.fromLTRB(0, 0, rect.width, rect.height),
),
blendMode: BlendMode.dstIn,
child: ClipRRect(
borderRadius: BorderRadius.circular(50.0),
child:
Image.asset('your_image_path', height: 100, width: 100),
),
),
)
],
),
);
Future<void> _captureImage() async {
final RenderRepaintBoundary boundary =
globalKey.currentContext!.findRenderObject()! as RenderRepaintBoundary;
final ui.Image image = await boundary.toImage();
final ByteData? byteData =
await image.toByteData(format: ui.ImageByteFormat.png);
final Uint8List pngBytes = byteData!.buffer.asUint8List();
print(pngBytes);
}
}
Edit
The widget-less approach will look like this:
Picture draw() {
final recorder = PictureRecorder();
final canvas = Canvas(recorder);
canvas
..drawImage(background_image, Offset.zero, Paint()) //or
..drawImage(
image_that_should_be_circle,
const Offset(x_offset_based_on_you_backgroud_image,
y_offset_based_on_you_backgroud_image),
// the offset from the corner of the canvas
Paint()
..shader = const RadialGradient(
radius: needed_radius,
// the radius of the result gradient - it should depend on the circling image dimens
colors: [Colors.black, Colors.transparent],
).createShader(
Rect.fromLTRB(0, 0, your_image_width,
your_image_height), // the portion of your image that should be influenced by the shader - in this case I use the whole image.
)
..blendMode = BlendMode
.dstIn); // for the black color of the gradient to be masking one
return recorder.endRecording();
}
Edit2
How to draw multiple images on one canvas - paintImage approach
Picture draw() {
final recorder = PictureRecorder();
final canvas = Canvas(recorder);
paintImage(
canvas: canvas,
image: backgroundImage,
fit: BoxFit.fill,
rect: Rect.fromLTWH(x, y, neededBackgroundWidth, neededBackgroundHeight),
);
paintImage(
canvas: canvas,
image: otherImage,
fit: BoxFit.fill,
rect: Rect.fromLTWH(x, y, neededOtherImageWidth, neededOtherImageHeight),
);
return recorder.endRecording();
}
I haven't tried it in action, so may need to adjust some stuff.
Hope it helps.