Home > Mobile >  Cropping And Swapping A Part Of Image With Another Image with Gradual Transparency in Flutter
Cropping And Swapping A Part Of Image With Another Image with Gradual Transparency in Flutter

Time:09-17

  1. How do we crop a circular part of an bitmap image and put it on another image (bitmap)?
  2. How do we make a gradient transparency of the circular image?

Any thoughts?

Below is the illustration of what i meant.

enter image description here

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.

  • Related