Canvas drawImage scale height and width in CustomPainter

4,995

I had a very similar requirement and the comment about using paintImage was exactly what I was looking for, so I figured I'd share what I ended up with.

I needed to scale down an image and draw overlays on top of that image. image is my original (unscaled) Image object.

var recorder = ui.PictureRecorder();
var imageCanvas = new Canvas(recorder);
var painter = _MarkupPainter(_overlays);

//Paint the image into a rectangle that matches the requested width/height.
//This will handle rescaling the image into the rectangle so that it will not be clipped.
paintImage(
  canvas: imageCanvas, 
  rect: Rect.fromLTWH(0, 0, scaledWidth, scaledHeight),
  image: image,
  fit: BoxFit.scaleDown,
  repeat: ImageRepeat.noRepeat,
  scale: 1.0,
  alignment: Alignment.center,
  flipHorizontally: false,
  filterQuality: FilterQuality.high
  );

//Add the markup overlays.
painter.paint(imageCanvas, Size(scaledWidth, scaledHeight));

var picture = recorder.endRecording();

return picture.toImage(scaledWidth.toInt(), scaledHeight.toInt());
Share:
4,995
Boris
Author by

Boris

Updated on December 12, 2022

Comments

  • Boris
    Boris over 1 year

    I am developing an application in Flutter where I am using CustomPainter to draw an image which the user picks from gallery/camera. In addition to this the use can draw lines as well as change the stroke value, opacity colour and colour on it its own. For this I have created 2 classes DrawEditor and DrawingPainter the code for those two classes can be found below. Once the user picks an image the image is passed to the DrawingPainter class where paint() is called and I draw my lines and image. The issue is in _paintBackgroundImage() in this method I draw the image by using canvas.drawImage(paintedImage, Offset.zero, Paint()); which does not scale the image.

    Earlier I tried a different approach instead of drawing the image with canvas.drawImage(paintedImage, Offset.zero, Paint()) I used canvas.drawImageRect(paintedImage, inputSubRect, outputSubRect, Paint()); as can be seen below. However with this approach the draw picture Is pixelated so I prefer canvas.drawImage(paintedImage, Offset.zero, Paint()) as this does not damage the picture.

    Any help with scaling the image will be greatly appreciated.

      //Example 1 : Code with canvas.drawImageRect but image pixelated
       final UI.Rect rect = UI.Offset.zero & _canvasSize;
           final Size imageSize =Size(paintedImage.width.toDouble(),      paintedImage.height.toDouble());
        FittedSizes sizes = applyBoxFit(BoxFit.contain, imageSize, _canvasSize);
        final Rect inputSubRect =
        Alignment.center.inscribe(sizes.source, Offset.zero & imageSize);
    final Rect outputSubRect =
        Alignment.center.inscribe(sizes.destination, rect);
    
    canvas.drawImageRect(paintedImage, inputSubRect, outputSubRect, Paint());
    
       //Example 2 : Code with canvas.drawImageRect but image pixelated
       canvas.drawRect(Rect.fromPoints(blurStartOffset, blurIndicatorOffset),
          blurPaintSettings)
    
    class DrawingPainter extends CustomPainter {
       static int blurColor = 0xFFB3E5FC;
       UI.Image paintedImage;
       List<DrawingPoints> pointsList;
       List<DrawingPoints> blurPointsList;
       List<Offset> offsetPoints = List();
       Size _canvasSize;
       Offset blurIndicatorOffset;
       Offset blurStartOffset;
       bool isBlur;
       List<BlurIndicatorOffsetWrapper> wrapperList = new List();
    
       /// To blur an image we need a [MaskFilter]
       Paint blurPaintSettings = new Paint()
          ..style = PaintingStyle.fill
          ..color = Color(blurColor)
          ..maskFilter = MaskFilter.blur(BlurStyle.normal, 3.0);
    
       DrawingPainter(
          {this.pointsList,
          this.paintedImage,
          this.blurPointsList,
          this.blurIndicatorOffset,
          this.blurStartOffset}) {
          isBlur = blurIndicatorOffset != null;
     }
    
     @override
     void paint(Canvas canvas, Size size) {
          _canvasSize = size;
          _paintBackgroundImage(canvas);
          _drawPoints(canvas);
          _drawBlurIndicator(canvas);
     }
    
       /// Paints the image onto the canvas
       void _paintBackgroundImage(Canvas canvas) {
          if (paintedImage == null) {
          return;
         }
         final UI.Rect rect = UI.Offset.zero & _canvasSize;
         final Size imageSize =
        Size(paintedImage.width.toDouble(), paintedImage.height.toDouble());
         FittedSizes sizes = applyBoxFit(BoxFit.contain, imageSize, _canvasSize);
         final Rect inputSubRect =
        Alignment.center.inscribe(sizes.source, Offset.zero & imageSize);
         final Rect outputSubRect =
        Alignment.center.inscribe(sizes.destination, rect);
    
         canvas.drawImageRect(paintedImage, inputSubRect, outputSubRect, Paint());
    
        }
    
        /// Paints the lines onto the canvas
        void _drawPoints(Canvas canvas) {
          for (int i = 0; i < pointsList.length - 1; i++) {
          if (pointsList[i] != null && pointsList[i + 1] != null) {
          canvas.drawLine(pointsList[i].points, pointsList[i + 1].points,
            pointsList[i].paint);
        }
       }
      }
    
        /// Paints the blur indicator onto the canvas
        void _drawBlurIndicator(Canvas canvas) {
           if (blurStartOffset != null && blurIndicatorOffset != null) {
            canvas.drawRect(Rect.fromPoints(blurStartOffset,  blurIndicatorOffset),
          blurPaintSettings);
        }
      }
    
      void setBlurIndicator(Offset localOffset) {
          blurIndicatorOffset = localOffset;
       }
    
      @override
      bool shouldRepaint(DrawingPainter oldDelegate) {
         return true;
      }
    
      Future<Uint8List> save() async {
         //Create canvas
         // Set PictureRecorder on the canvas and start recording
         UI.PictureRecorder recorder = UI.PictureRecorder();
         Canvas canvas = Canvas(recorder);
    
         //Draw image on new canvas
         if (paintedImage != null) {
         final Size imageSize = Size(paintedImage.width.toDouble(), paintedImage.height.toDouble());
    
          //Here image is the problem
          canvas.drawImage(paintedImage, Offset.zero, Paint());
       }
    
       //Draw points on new canvas
          for (int i = 0; i < pointsList.length - 1; i++) {
          if (pointsList[i] != null && pointsList[i + 1] != null) {
          canvas.drawLine(
                 pointsList[i].points,
                 pointsList[i + 1].points,
                 pointsList[i].paint,
          );
         }
       }
    
    //End recording
    final resultImage = await recorder.endRecording().toImage(
          _canvasSize.width.floor(),
          _canvasSize.height.floor(),
        );
    
    final imageBytes =
        await resultImage.toByteData(format: UI.ImageByteFormat.png);
    
    return imageBytes.buffer.asUint8List();
     }
    
    
     }
    
    class DrawingPoints {
         Paint paint;
         Offset points;
    
         DrawingPoints({this.points, this.paint});
    }
    
    enum SelectedMode { StrokeWidth, Opacity, Color, Blur }