drawDistortedImage method

void drawDistortedImage(
  1. Canvas canvas,
  2. Image image,
  3. Size viewportSize, {
  4. Matrix4? atwTransform,
  5. bool enableChromaticAberration = false,
})

Warps a rendered offline Image onto the target canvas using the pre-computed distortion mesh. Supports atwTransform matrix for Asynchronous Time Warp and enableChromaticAberration for correcting lens chromatic dispersion via multi-pass RGB blend.

Implementation

void drawDistortedImage(
  Canvas canvas,
  Image image,
  Size viewportSize, {
  Matrix4? atwTransform,
  bool enableChromaticAberration = false,
}) {
  final w = viewportSize.width;
  final h = viewportSize.height;

  final imgW = image.width.toDouble();
  final imgH = image.height.toDouble();

  // Map distorted points [-1, 1] to output viewport coordinates [0, w/h]
  final positions = distortedPoints.map((p) => _toScreen(p, w, h)).toList();

  final shaderMatrix = atwTransform ?? Matrix4.identity();
  final shader = ImageShader(
    image,
    TileMode.clamp,
    TileMode.clamp,
    Float64List.fromList(shaderMatrix.storage),
  );

  if (enableChromaticAberration) {
    // Map Green (neutral), Red (expanded 1.008), and Blue (contracted 0.992) texture coords
    final greenCoords = originalPoints.map((p) => _toScreen(p, imgW, imgH)).toList();

    final redCoords = originalPoints.map((p) {
      final shifted = Offset(p.dx * 1.008, p.dy * 1.008);
      return _toScreen(shifted, imgW, imgH);
    }).toList();

    final blueCoords = originalPoints.map((p) {
      final shifted = Offset(p.dx * 0.992, p.dy * 0.992);
      return _toScreen(shifted, imgW, imgH);
    }).toList();

    // Draw Red channel
    final redVertices = Vertices(
      VertexMode.triangles,
      positions,
      textureCoordinates: redCoords,
      indices: indices,
    );
    final redPaint = Paint()
      ..shader = shader
      ..colorFilter = const ColorFilter.matrix([
        1, 0, 0, 0, 0,
        0, 0, 0, 0, 0,
        0, 0, 0, 0, 0,
        0, 0, 0, 1, 0,
      ]);
    canvas.drawVertices(redVertices, BlendMode.srcOver, redPaint);

    // Draw Green channel (BlendMode.plus)
    final greenVertices = Vertices(
      VertexMode.triangles,
      positions,
      textureCoordinates: greenCoords,
      indices: indices,
    );
    final greenPaint = Paint()
      ..shader = shader
      ..blendMode = BlendMode.plus
      ..colorFilter = const ColorFilter.matrix([
        0, 0, 0, 0, 0,
        0, 1, 0, 0, 0,
        0, 0, 0, 0, 0,
        0, 0, 0, 1, 0,
      ]);
    canvas.drawVertices(greenVertices, BlendMode.srcOver, greenPaint);

    // Draw Blue channel (BlendMode.plus)
    final blueVertices = Vertices(
      VertexMode.triangles,
      positions,
      textureCoordinates: blueCoords,
      indices: indices,
    );
    final bluePaint = Paint()
      ..shader = shader
      ..blendMode = BlendMode.plus
      ..colorFilter = const ColorFilter.matrix([
        0, 0, 0, 0, 0,
        0, 0, 0, 0, 0,
        0, 0, 1, 0, 0,
        0, 0, 0, 1, 0,
      ]);
    canvas.drawVertices(blueVertices, BlendMode.srcOver, bluePaint);
  } else {
    // Standard single-pass render without aberration correction
    final textureCoords = originalPoints.map((p) => _toScreen(p, imgW, imgH)).toList();

    final vertices = Vertices(
      VertexMode.triangles,
      positions,
      textureCoordinates: textureCoords,
      indices: indices,
    );

    canvas.drawVertices(
      vertices,
      BlendMode.srcOver,
      Paint()..shader = shader,
    );
  }
}