0

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

vvvvv
  • 25,404
  • 19
  • 49
  • 81
Toxic Axes
  • 13
  • 4
  • shape of a dvd disk is round so why do you want to use `quadraticBezierTo`? – pskink Aug 05 '22 at 14:45
  • for the hole in the midlle , wich i achived by using it – Toxic Axes Aug 05 '22 at 15:40
  • simply add two circles and use `PathFillType.evenOdd` – pskink Aug 05 '22 at 15:53
  • that seems more logic ,I didn't know about it , my main problem is how do i go about making an image masked on the shape i tried clippath + canvas.drawimage but no result – Toxic Axes Aug 05 '22 at 16:09
  • yes, clipPath + drawImage is what you should follow, what does "no result" mean? – pskink Aug 05 '22 at 16:35
  • as shown in the imag above when i use canvas.clipPath(path); nothing changes image stays the same as it was before – Toxic Axes Aug 05 '22 at 17:44
  • thanks for your time, i manged to solve the issue after a long documentation readings and trail error , it turned out as you said thats the correct way to do it , one thing to keep in mind is the order of wihere you write the draw image matters , in my case it neded to be , path.close(); canvas.clipPath(path); canvas.drawImage(paintimage, Offset.zero, paint); so if you draw the image before it wont work , but if you draw it after i will work – Toxic Axes Aug 05 '22 at 18:07
  • also if you are using the drawimage dont use the canvas.drawPath – Toxic Axes Aug 05 '22 at 18:26
  • but actually, why don't you use `ClipPath` instead? – pskink Aug 05 '22 at 18:39
  • @ToxicAxes instead of editing the question you can post an answer to yourself (You can even accept it). This way it's going to be easier for other users to see the actual answer. – lepsch Aug 05 '22 at 18:42
  • i didnt know that , i will post and accpet it – Toxic Axes Aug 05 '22 at 21:24

2 Answers2

0

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

Toxic Axes
  • 13
  • 4
  • but actually, why don't you use instead `ClipPath` with `Image` widget as a child? – pskink Aug 06 '22 at 05:46
  • i did try that too but didnt work for me as mentioned above I'm still new to drawing in general so i might have made a mistake when implementing it , the code i uploaded got me the result i wanted with satisfaction – Toxic Axes Aug 06 '22 at 12:14
  • seems like a better solution ill take a look on it and comment back – Toxic Axes Aug 06 '22 at 14:44
  • how can i not think of that , your solution is way more better than the previous, it is faster better and easier to understand , im grateful for that – Toxic Axes Aug 06 '22 at 14:54
  • can you post it as answer ? – Toxic Axes Aug 06 '22 at 14:58
0

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;
}
pskink
  • 23,874
  • 6
  • 66
  • 77