Skip to content

image edges are always sharp when using Canvas.drawAtlas #152312

Description

@pskink

Steps to reproduce

run the attached code

Expected results

image edges should be smooth like when using Canvas.drawImage

Actual results

they are always sharp (no matter what FilterQuality` is used)

Code sample

Code sample
import 'dart:math';
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'dart:ui';

import 'package:collection/collection.dart';
import 'package:convert/convert.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';

void main() async {
  final rawImageData = [
    0x52, 0x49, 0x46, 0x46, 0x28, 0x00, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50, 0x56, 0x50, 0x38, 0x4c,
    0x1c, 0x00, 0x00, 0x00, 0x2f, 0x3f, 0xc0, 0x03, 0x00, 0x0f, 0x30, 0xff, 0xf3, 0x1f, 0xf3, 0x1f,
    0x0e, 0x04, 0x02, 0x09, 0xc2, 0xfe, 0x96, 0x6b, 0x64, 0x10, 0xd1, 0xff, 0x9c, 0xe3, 0x39, 0x06,
  ];
  WidgetsFlutterBinding.ensureInitialized();
  final image = await decodeImageFromList(Uint8List.fromList(rawImageData));

  runApp(MaterialApp(home: Scaffold(body: Foo(image))));
}

class Foo extends StatefulWidget {
  Foo(this.image);

  final ui.Image image;

  @override
  State<Foo> createState() => _FooState();
}

class _FooState extends State<Foo> with TickerProviderStateMixin {

  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
      child: Column(
        children: [
          ...FilterQuality.values.map((fq) => FooItem(
              image: widget.image,
              label: 'drawAtlas, $fq',
              drawAtlas: true,
              paint: Paint()..filterQuality = fq,
            ),
          ),
          ...FilterQuality.values.map((fq) => FooItem(
              image: widget.image,
              label: 'drawImage, $fq',
              drawAtlas: false,
              paint: Paint()..filterQuality = fq,
            ),
          ),
        ],
      ),
    );
  }
}

class FooItem extends StatelessWidget {
  const FooItem({
    required this.image,
    required this.label,
    required this.drawAtlas,
    required this.paint,
  });

  final ui.Image image;
  final String label;
  final bool drawAtlas;
  final Paint paint;

  @override
  Widget build(BuildContext context) {
    return Row(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        SizedBox.fromSize(size: const Size(150, 70), child: Text(label)),
        Expanded(
          child: CustomPaint(
            painter: FooPainter(
              image: image,
              drawAtlas: drawAtlas,
              paint: paint,
            ),
          ),
        ),
      ],
    );
  }
}

class FooPainter extends CustomPainter {
  FooPainter({
    required this.image,
    required this.drawAtlas,
    required Paint paint,
  }) : _paint = paint;

  final ui.Image image;
  final bool drawAtlas;
  final Paint _paint;

  @override
  void paint(Canvas canvas, Size size) {
    if (drawAtlas) {
      final transforms = [RSTransform.fromComponents(
        rotation: pi / 16,
        scale: 2,
        anchorX: 0,
        anchorY: 0,
        translateX: 0,
        translateY: 0,
      )];
      final rects = [Offset.zero & Size(64 , 16)];
      canvas.drawAtlas(image, transforms, rects, null, null, null, _paint);
    } else {
      final matrix = composeMatrix(
        rotation: pi / 16,
        scale: 2,
      );
      canvas
        ..save()
        ..transform(matrix.storage)
        ..drawImage(image, Offset.zero, _paint)
        ..restore();
    }
  }

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

Matrix4 composeMatrix({
  double scale = 1,
  double rotation = 0,
  Offset translate = Offset.zero,
  Offset anchor = Offset.zero,
}) {
  final double c = cos(rotation) * scale;
  final double s = sin(rotation) * scale;
  final double dx = translate.dx - c * anchor.dx + s * anchor.dy;
  final double dy = translate.dy - s * anchor.dx - c * anchor.dy;
  return Matrix4(c, s, 0, 0, -s, c, 0, 0, 0, 0, 1, 0, dx, dy, 0, 1);
}

Screenshots or Video

Screenshots / Video demonstration

Untitled1

Logs

Logs
[Paste your logs here]

Flutter Doctor output

Doctor output
[✓] Flutter (Channel stable, 3.22.3, on Arch Linux 6.8.1-arch1-1, locale C.UTF-8)
[✓] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
[✓] Chrome - develop for the web
[✓] Linux toolchain - develop for Linux desktop
[✓] Android Studio (version 4.0)
[✓] Android Studio (version 3.4)
[✓] Android Studio (version 3.6)
[✓] Android Studio (version 3.5)
[✓] IntelliJ IDEA Community Edition (version 2017.1)
[✓] IntelliJ IDEA Community Edition (version 2023.2)
[✓] Connected device (2 available)
[✓] Network resources

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Important issues not at the top of the work listfound in release: 3.22Found to occur in 3.22found in release: 3.24Found to occur in 3.24has reproducible stepsThe issue has been confirmed reproducible and is ready to work onteam-engineOwned by Engine teamtriaged-engineTriaged by Engine team

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions