bharat8
bharat8

Reputation: 125

How to add images to any shapes in flutter?

I am new to flutter and bumped into this problem of adding images to shapes in flutter. I have created the custom shape but I am not able to insert image into it. Here is what I want to achieve : A picture inside a custom shape

I tried giving Image as the child of CustomPainter but still no good results.

Can anyone suggest a good approach?

Upvotes: 1

Views: 4091

Answers (3)

JRamos29
JRamos29

Reputation: 890

With CustomPaint you can use an image as a mask that overlays the original image, and show just the content from the image that it contained in the mask. Which in that case is a shape. In the code bellow shape.png acts like the mask, and just the part non-transparent from the png file will show the content from the image.jpeg. So you need to create a png file with the desired shape, and apply as overlay to the image.

import 'dart:typed_data';
import 'dart:ui' as ui;

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show rootBundle;


class MaskedImageWithPainter extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  ui.Image mask;
  ui.Image image;

  @override
  void initState() {
    super.initState();
    load('assets/shape.png').then((i) {
      setState(() {
        mask = i;
      });
    });
    load('assets/image.jpeg').then((i) {
      setState(() {
        image = i;
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(backgroundColor: Colors.blue, title: Text('I am a title')),
      body: SafeArea(
        child: SizedBox(
          width: 200.0,
          height: 200.0,
          child: CustomPaint(painter: OverlayPainter(mask, image)),
        ),
      ),
    );
  }

  Future<ui.Image> load(String asset) async {
    ByteData data = await rootBundle.load(asset);
    ui.Codec codec = await ui.instantiateImageCodec(data.buffer.asUint8List());
    ui.FrameInfo fi = await codec.getNextFrame();
    return fi.image;
  }
}

class OverlayPainter extends CustomPainter {
  ui.Image mask;
  ui.Image image;

  OverlayPainter(this.mask, this.image);

  @override
  void paint(Canvas canvas, Size size) {
    if (image != null && mask != null) {
      var rect = Rect.fromLTRB(0, 0, 200, 200);
      Size outputSize = rect.size;
      Paint paint = new Paint();

      //Mask
      Size maskInputSize = Size(mask.width.toDouble(), mask.height.toDouble());
      final FittedSizes maskFittedSizes =
          applyBoxFit(BoxFit.cover, maskInputSize, outputSize);
      final Size maskSourceSize = maskFittedSizes.source;

      final Rect maskSourceRect = Alignment.center
          .inscribe(maskSourceSize, Offset.zero & maskInputSize);

      canvas.saveLayer(rect, paint);
      canvas.drawImageRect(mask, maskSourceRect, rect, paint);

      //Image
      Size inputSize = Size(image.width.toDouble(), image.height.toDouble());
      final FittedSizes fittedSizes =
          applyBoxFit(BoxFit.cover, inputSize, outputSize);
      final Size sourceSize = fittedSizes.source;
      final Rect sourceRect =
          Alignment.center.inscribe(sourceSize, Offset.zero & inputSize);

      canvas.drawImageRect(
          image, sourceRect, rect, paint..blendMode = BlendMode.srcIn);
      canvas.restore();
    }
  }

  @override
  bool shouldRepaint(OverlayPainter oldDelegate) {
    return mask != oldDelegate.mask || image != oldDelegate.image;
  }
}

Upvotes: 1

oziem
oziem

Reputation: 470

Like Mindhun MP wrote you need to use a custom clipper. Here is an example clipping the Container:

ClipPath(
      clipper: _CustomClipper(),
      child: Container(...),
);

Your case should be similar to mine so check out quadraticBezierTo function. My example:

class _CustomClipper extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    final double heightDelta = size.height / 2.2;

    return Path()
      ..addRect(
          Rect.fromLTWH(0, heightDelta, size.width, size.height - heightDelta))
      ..moveTo(0, heightDelta)
      ..quadraticBezierTo(
        size.width / 2,
        heightDelta - size.width / 2,
        size.width,
        heightDelta,
      );
  }

  @override
  bool shouldReclip(CustomClipper<Path> oldClipper) => true;
}

Also here is a great article that explains more about Paths in Flutter.

Upvotes: 1

Mizuki
Mizuki

Reputation: 2233

As Midhun MP mentioned, wrap your image with ClipPath

  @override
  Widget build(BuildContext context) {
    return ClipPath(
      child: image,
      clipper: AnyClipper(),
    );
  }

and use CustomClipper

class AnyClipper extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    var path = Path();
    //... do something
    return path;
  }

  @override
  bool shouldReclip(CustomClipper<Path> oldClipper) => false;
}

Upvotes: 0

Related Questions