Applying Transformations and Effects to Canvas Drawings in Flutter

Flutter’s Canvas offers a powerful way to create custom 2D graphics and animations. Applying transformations and effects to these canvas drawings enhances their visual appeal and provides advanced customization options. Transformations involve manipulating the coordinate space, while effects alter the appearance of drawn shapes. This article dives into how to apply transformations and effects to canvas drawings in Flutter.

Understanding Flutter Canvas

Before delving into transformations and effects, it’s important to grasp the basics of Flutter’s Canvas. The CustomPaint widget, combined with a CustomPainter, is used to draw on the canvas. The paint method within the CustomPainter provides access to the Canvas object, which you use to draw shapes, text, and images.

Basic Canvas Setup

Here’s a basic example of how to set up a Canvas in Flutter:


import 'package:flutter/material.dart';

class MyCanvasPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    // Drawing logic here
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) => false;
}

class MyCanvasWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return CustomPaint(
      painter: MyCanvasPainter(),
      size: Size(200, 200),
    );
  }
}

Transformations in Flutter Canvas

Transformations change the coordinate space before drawing, which can scale, rotate, translate, and skew your drawings. Flutter provides several methods on the Canvas object to apply these transformations.

1. Translate

The translate method moves the origin of the coordinate space. All subsequent drawings will be relative to the new origin.


void paint(Canvas canvas, Size size) {
  final paint = Paint()
    ..color = Colors.blue
    ..style = PaintingStyle.fill;

  // Translate the canvas by 50 pixels in X and 30 pixels in Y
  canvas.translate(50, 30);

  // Draw a rectangle at the new origin
  canvas.drawRect(Rect.fromLTWH(0, 0, 100, 50), paint);
}

2. Rotate

The rotate method rotates the coordinate space by a specified angle (in radians) around the current origin.


import 'dart:math' as math;

void paint(Canvas canvas, Size size) {
  final paint = Paint()
    ..color = Colors.green
    ..style = PaintingStyle.fill;

  // Rotate the canvas by 45 degrees (pi/4 radians)
  canvas.rotate(math.pi / 4);

  // Draw a rectangle, which will be rotated
  canvas.drawRect(Rect.fromLTWH(0, 0, 100, 50), paint);
}

3. Scale

The scale method scales the coordinate space. It takes two scale factors for X and Y dimensions.


void paint(Canvas canvas, Size size) {
  final paint = Paint()
    ..color = Colors.orange
    ..style = PaintingStyle.fill;

  // Scale the canvas by 2 in X and 1.5 in Y
  canvas.scale(2, 1.5);

  // Draw a rectangle, which will be scaled
  canvas.drawRect(Rect.fromLTWH(0, 0, 100, 50), paint);
}

4. Transform

The transform method allows you to apply a matrix transformation directly. It takes a Matrix4 object, providing fine-grained control over transformations.


import 'package:vector_math/vector_math_64.dart' as vmath;

void paint(Canvas canvas, Size size) {
  final paint = Paint()
    ..color = Colors.purple
    ..style = PaintingStyle.fill;

  // Create a Matrix4 object
  final matrix = vmath.Matrix4.identity()
    ..translate(20.0, 20.0)
    ..rotateZ(math.pi / 6)
    ..scale(1.2);

  // Apply the transformation matrix to the canvas
  canvas.transform(matrix.storage);

  // Draw a rectangle, which will be transformed
  canvas.drawRect(Rect.fromLTWH(0, 0, 100, 50), paint);
}

Saving and Restoring the Canvas State

Canvas transformations are cumulative. If you apply multiple transformations, they build upon each other. To isolate transformations, use canvas.save() to save the current transformation matrix and canvas.restore() to revert to the saved state.


void paint(Canvas canvas, Size size) {
  final paint = Paint()
    ..color = Colors.red
    ..style = PaintingStyle.fill;

  // Save the current canvas state
  canvas.save();

  // Apply a translation
  canvas.translate(50, 50);

  // Draw a rectangle
  canvas.drawRect(Rect.fromLTWH(0, 0, 50, 50), paint);

  // Restore the canvas state
  canvas.restore();

  // Draw another rectangle at the original origin
  canvas.drawRect(Rect.fromLTWH(0, 0, 50, 50), paint);
}

Effects in Flutter Canvas

Flutter Canvas provides several effects to modify the appearance of drawings. These effects are primarily applied using the Paint object.

1. ColorFilter

ColorFilter applies a color transformation to the drawn shapes. It can be used to create various visual effects like grayscale, tinting, or color inverting.


import 'package:flutter/painting.dart';

void paint(Canvas canvas, Size size) {
  final paint = Paint()
    ..color = Colors.blue
    ..style = PaintingStyle.fill
    ..colorFilter = ColorFilter.mode(
      Colors.red, // Tint color
      BlendMode.srcATop, // Blend mode
    );

  // Draw a rectangle with the color filter applied
  canvas.drawRect(Rect.fromLTWH(0, 0, 100, 50), paint);
}

2. MaskFilter

MaskFilter applies a blurring or sharpening effect. The most common usage is to create a blur effect.


import 'dart:ui';

void paint(Canvas canvas, Size size) {
  final paint = Paint()
    ..color = Colors.blue
    ..style = PaintingStyle.fill
    ..maskFilter = MaskFilter.blur(BlurStyle.normal, 5); // Apply a blur effect

  // Draw a rectangle with the mask filter applied
  canvas.drawRect(Rect.fromLTWH(0, 0, 100, 50), paint);
}

3. Shader

Shader allows you to fill the drawing with a gradient, an image, or a combination thereof. It’s highly customizable and offers advanced visual effects.


import 'dart:ui';

void paint(Canvas canvas, Size size) {
  final paint = Paint()
    ..shader = LinearGradient(
      colors: [Colors.blue, Colors.green],
      begin: Alignment.topLeft,
      end: Alignment.bottomRight,
    ).createShader(Rect.fromLTWH(0, 0, 200, 100));

  // Draw a rectangle filled with the gradient
  canvas.drawRect(Rect.fromLTWH(0, 0, 200, 100), paint);
}

4. ImageFilter

ImageFilter allows you to apply image-based effects such as blurring or color adjustments to an image before it’s rendered to the canvas.


import 'dart:ui';
import 'dart:io'; // Import dart:io to use dart:ui.Image
import 'dart:typed_data';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

class MyCanvasPainter extends CustomPainter {
  Image? image;

  MyCanvasPainter({required this.image});

  @override
  void paint(Canvas canvas, Size size) {
    if (image == null) {
      return;
    }
    
    final paint = Paint()
      ..imageFilter = ImageFilter.blur(sigmaX: 5.0, sigmaY: 5.0);

    canvas.drawImage(image!, Offset.zero, paint);
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) => false;
}

Future loadImage(String assetPath) async {
  final ByteData data = await rootBundle.load(assetPath);
  final Uint8List bytes = data.buffer.asUint8List();
  return await decodeImageFromList(bytes);
}

class ImageFilterExample extends StatefulWidget {
  @override
  _ImageFilterExampleState createState() => _ImageFilterExampleState();
}

class _ImageFilterExampleState extends State {
  ui.Image? image;

  @override
  void initState() {
    super.initState();
    loadImage('assets/sample_image.jpg').then((img) {
      setState(() {
        image = img;
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Image Filter Example')),
      body: Center(
        child: (image != null) ? CustomPaint(
          painter: MyCanvasPainter(image: image),
          size: Size(300, 200),
        ) : CircularProgressIndicator(),
      ),
    );
  }
}

Combining Transformations and Effects

Transformations and effects can be combined to create sophisticated graphics. Just remember to save and restore the canvas state to manage the coordinate space and isolate effects when needed.


import 'dart:math' as math;
import 'dart:ui';

void paint(Canvas canvas, Size size) {
  final paint = Paint()
    ..color = Colors.green
    ..style = PaintingStyle.fill
    ..maskFilter = MaskFilter.blur(BlurStyle.normal, 3);

  // Save the canvas state
  canvas.save();

  // Translate and rotate
  canvas.translate(100, 100);
  canvas.rotate(math.pi / 6);

  // Draw a blurred rectangle
  canvas.drawRect(Rect.fromLTWH(0, 0, 100, 50), paint);

  // Restore the canvas state
  canvas.restore();
}

Conclusion

Applying transformations and effects to canvas drawings in Flutter opens up a realm of possibilities for creating visually stunning and highly customized graphics. Whether it’s translating, rotating, scaling shapes, or applying color filters and blurs, mastering these techniques enhances your ability to deliver unique and engaging user experiences. Combine these tools wisely, and you’ll be well on your way to creating amazing graphics with Flutter’s Canvas.