Home > Software design >  Solved, ui.image drawn on canvas as bckground overflow after drawing with a quadraticBezierTo
Solved, ui.image drawn on canvas as bckground overflow after drawing with a quadraticBezierTo

Time:08-07

I'm trying to draw an image on a canvas while making a custom shape of a dvd disk on it.

I'm still new to drawing in general and trying to learn it so I managed to draw the custom shape I wanted by combining quadraticBezierTo, lineTo. I tried searching for a way to apply the image to the custom shape I drew but the result I get is as it follows

as you can see in this picture it overflows

my code is as it follows

late ui.Image sBackground;

which will be initialized before calling the paint class

code For the paint class is:

class Painter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    Paint paint = Paint()
    ..style = PaintingStyle.fill;
    canvas.drawImage(sBackground, Offset.zero, paint); // drawing image to canvas in here
    Path path = Path()..moveTo(0, 0);
    path.quadraticBezierTo(0, 0, size.width * 0.5, 0);
    path.quadraticBezierTo(size.width, 0, size.width, size.height * 0.5);
    path.lineTo(size.width * 0.55, size.height * 0.5);
    path.quadraticBezierTo(size.width * 0.55, size.height * 0.45,
        size.width * 0.5, size.height * 0.45);
    path.quadraticBezierTo(size.width * 0.45, size.height * 0.45,
        size.width * 0.45, size.height * 0.5);
    path.quadraticBezierTo(size.width * 0.45, size.height * 0.55,
        size.width * 0.5, size.height * 0.55);
    path.quadraticBezierTo(size.width * 0.55, size.height * 0.55,
        size.width * 0.55, size.height * 0.5);
    path.lineTo(size.width, size.height * 0.5);
    path.quadraticBezierTo(
        size.width, size.height, size.width * 0.5, size.height);
    path.quadraticBezierTo(0, size.height, 0, size.height * 0.5);
    path.quadraticBezierTo(0, 0, size.width * 0.5, 0);
    path.close();
    canvas.drawShadow(path, Colors.white30, 2.0, true);
    canvas.drawPath(path, paint);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return false;
  }
}

reference: Clip objects drawn outside of canvas which I read and tried but didn't get a result

NOTE: I'm still searching for a way to solve my problem as I post this question in here. any help or docs will really be helpful and thanks in advance

EDIT : for anyone else having same problem please take a look at the comments below for the solution

CodePudding user response:

instead of CustomPainter i would use a custom ShapeBorder, note that the "elevation" shadow is hardcoded in BoxShadow constructor - you can change that by adding some extra parameters to Cover widget, also you can add some custom painting by overriding CoverShape.paint method:

class Cover extends StatelessWidget {
  const Cover({
    required this.image,
    Key? key,
  }) : super(key: key);

  Cover.asset(
    String name,
    Key? key,
  ) : this(key: key, image: AssetImage(name));

  Cover.file(
    File file,
    Key? key,
  ) : this(key: key, image: FileImage(file));

  Cover.network(
    String src,
    Key? key,
  ) : this(key: key, image: NetworkImage(src));

  final ImageProvider image;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: AspectRatio(
        aspectRatio: 1,
        child: Container(
          clipBehavior: Clip.antiAlias,
          decoration: ShapeDecoration(
            shape: CoverShape(),
            shadows: const [BoxShadow(color: Colors.black, blurRadius: 5, spreadRadius: 1, offset: Offset(3, 3))],
          ),
          child: Image(image: image, fit: BoxFit.cover),
        ),
      ),
    );
  }
}

class CoverShape extends ShapeBorder {
  @override
  EdgeInsetsGeometry get dimensions => EdgeInsets.zero;

  @override
  ui.Path getInnerPath(ui.Rect rect, {ui.TextDirection? textDirection}) => getOuterPath(rect);

  @override
  ui.Path getOuterPath(ui.Rect rect, {ui.TextDirection? textDirection}) {
    final center = rect.center;
    final radius = rect.shortestSide / 2;
    return Path()
      ..fillType = PathFillType.evenOdd
      ..addOval(Rect.fromCircle(center: center, radius: radius))
      ..addOval(Rect.fromCircle(center: center, radius: 0.1 * radius));
  }

  @override
  void paint(ui.Canvas canvas, ui.Rect rect, {ui.TextDirection? textDirection}) {
  }

  @override
  ShapeBorder scale(double t) => this;
}

alternatively you can use ClipPath widget but with that solution the shadows cannot be used:

class Cover extends StatelessWidget {
  const Cover({
    required this.image,
    Key? key,
  }) : super(key: key);

  Cover.asset(
    String name,
    Key? key,
  ) : this(key: key, image: AssetImage(name));

  Cover.file(
    File file,
    Key? key,
  ) : this(key: key, image: FileImage(file));

  Cover.network(
    String src,
    Key? key,
  ) : this(key: key, image: NetworkImage(src));
 
  final ImageProvider image;
 
  @override
  Widget build(BuildContext context) {
    return Center(
      child: AspectRatio(
        aspectRatio: 1,
        child: ClipPath(
          clipper: CoverClipper(),
          child: Image(image: image, fit: BoxFit.cover),
        ),
      ),
    );
  }
}
 
class CoverClipper extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    final center = size.center(Offset.zero);
    final radius = size.shortestSide / 2;
    return Path()
      ..fillType = PathFillType.evenOdd
      ..addOval(Rect.fromCircle(center: center, radius: radius))
      ..addOval(Rect.fromCircle(center: center, radius: 0.1 * radius));
  }
 
  @override
  bool shouldReclip(CustomClipper<Path> oldClipper) => false;
}

CodePudding user response:

for anyone else having same problem here is my final code

class PainterV2 extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    Paint paint = Paint()
      ..color = const Color.fromARGB(255, 0, 0, 0)
      ..style = PaintingStyle.fill;
    Path path = Path()
      ..fillType = PathFillType.evenOdd
      ..moveTo(0, 0);
    Path path2 = Path()
      ..fillType = PathFillType.evenOdd
      ..moveTo(0, 0);
    var rect = Rect.fromLTRB(0, 0, size.width, size.height);
    var rect2 = Rect.fromLTRB(size.width * 0.47, size.height * 0.47,
        size.width * 0.53, size.height * 0.53);
    path.addOval(rect);
    path.addOval(rect2);
    path2.addOval(rect.deflate(69));
    path2.addOval(rect2);
    // canvas.drawPath(path, paint);
    path.close();
    path2.close();
    canvas.clipPath(path);
    canvas.drawImage(paintimage, Offset.zero, paint);
    canvas.drawPath(path2, paint);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return true;
  }
}

instead of using quadraticBezier i switched to addOval , also if i use canvas.drawimage alongside canvas.drawPath it wont work, and keep in mind that order wich you place it matters in my case it neded to be ,

path.close();
canvas.clipPath(path);
canvas.drawImage(paintimage, Offset.zero, paint);

long story short the script above will make a circle in shape of cd with a hole in the middle alongside another shape in black to cover the hole in the middle

  • Related