Skip to content

[Impeller] ImageShader align is different for different PaintingStyle #126739

@olksij

Description

@olksij

Is there an existing issue for this?

Steps to reproduce

  1. Run the code sample on iOS (with Impeller enabled)
  2. Optionally press "Togge border" button to see the difference in painting
  3. Run without Impeller and observe that by tapping the button there should be no difference [expected]

Expected results

ImageShader aligns image to fill the Canvas, not boundaries of the shape drawn.

Actual results

If Paint().style is PaintingStyle.stroke, the Image provided to ImageShader is sized to fit the shape drawn (Rect in the sample). The painting behaves as expected when the Paint().style is PaintingStyle.fill, and with Skia renderer. You can check Screenshots demonstration section

Code sample

Code sample
import 'dart:ui' as UI;

import 'package:http/http.dart' as http;
import 'package:flutter/material.dart';

class Painter extends CustomPainter {
  Painter(this.image, this.border);

  final UI.Image image;
  final bool border;

  @override
  void paint(Canvas canvas, Size size) {
    final transform = Matrix4.identity().storage;
    final baseRect = Rect.fromLTWH(0, 0, size.width / 2, size.height / 2);
    final rect = border ? baseRect.deflate(32) : baseRect;

    final style = border ? PaintingStyle.stroke : PaintingStyle.fill;
    final shader = ImageShader(image, tile, tile, transform);

    final paint = Paint()
      ..style = style
      ..strokeWidth = 64
      ..shader = shader;

    canvas.drawRect(rect, paint);
  }

  @override
  bool shouldRepaint(oldDelegate) => false;

  static const tile = TileMode.decal;
}

class _MainAppState extends State<MainApp> {
  static const url = 'https://docs.flutter.dev/assets/images/dash/Dash.png';

  bool border = true;

  static Future<UI.Image> loadImage() async {
    final response = await http.get(Uri.parse(url));
    return await decodeImageFromList(response.bodyBytes);
  }

  @override
  Widget build(context) {
    return FutureBuilder(
      future: loadImage(),
      builder: (_, snapshot) {
        if (!snapshot.hasData) {
          return const SizedBox();
        }

        final painter = Painter(snapshot.data!, border);
        final children = [
          SizedBox.expand(child: CustomPaint(painter: painter)),
          ElevatedButton(onPressed: onTap, child: buttonText),
        ];

        return Directionality(
          textDirection: TextDirection.ltr,
          child: Stack(alignment: Alignment.center, children: children),
        );
      },
    );
  }

  onTap() => setState(() => border = !border);
  static const buttonText = Text('Toggle border');
}

class MainApp extends StatefulWidget {
  const MainApp({super.key});

  @override
  State<MainApp> createState() => _MainAppState();
}

void main() => runApp(const MainApp());

Screenshots or Video

Screenshots demonstration

PaintingStyle.fill (Impeller & Skia):

Image aligns to the Canvas size [expected]
simulator_screenshot_127F7CD1-4A1A-4C59-9735-A7A0D5691944

PaintingStyle.stroke (Impeller):

Image aligns to the shape drawn
simulator_screenshot_EAAEEBFE-3E60-4857-85A5-66F868186754

PaintingStyle.stroke (Skia) [EXPECTED]:

simulator_screenshot_2659E7E9-FEB3-4B2C-8C21-A5461CE7E3CC

Logs

Logs
[Paste your logs here]

Flutter Doctor output

Doctor output
[✓] Flutter (Channel stable, 3.10.0, on macOS 13.3.1 22E772610a darwin-arm64, locale en-SE)
    • Flutter version 3.10.0 on channel stable at /Users/oleksiibesida/Documents/flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision 84a1e904f4 (4 days ago), 2023-05-09 07:41:44 -0700
    • Engine revision d44b5a94c9
    • Dart version 3.0.0
    • DevTools version 2.23.1

[✓] Android toolchain - develop for Android devices (Android SDK version 33.0.1)
    • Android SDK at /Users/oleksiibesida/Library/Android/sdk
    • Platform android-33, build-tools 33.0.1
    • Java binary at: /Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 11.0.15+0-b2043.56-8887301)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 14.2)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Build 14C18
    • CocoaPods version 1.11.3

[✓] Chrome - develop for the web
    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Android Studio (version 2022.1)
    • Android Studio at /Applications/Android Studio.app/Contents
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build 11.0.15+0-b2043.56-8887301)

[✓] VS Code (version 1.78.1)
    • VS Code at /Applications/Visual Studio Code.app/Contents
    • Flutter extension version 3.64.0

[✓] Connected device (3 available)
    • iPhone 14 Pro (mobile) • CDB9D79D-EE22-474C-8CC6-EFE204E9BC4E • ios            • com.apple.CoreSimulator.SimRuntime.iOS-16-2
      (simulator)
    • macOS (desktop)        • macos                                • darwin-arm64   • macOS 13.3.1 22E772610a darwin-arm64
    • Chrome (web)           • chrome                               • web-javascript • Google Chrome 111.0.5563.64

[✓] Network resources
    • All expected network resources are available.

• No issues found!

Metadata

Metadata

Assignees

Labels

P1High-priority issues at the top of the work liste: impellerImpeller rendering backend issues and features requestsengineflutter/engine related. See also e: labels.found in release: 3.10Found to occur in 3.10found in release: 3.11Found to occur in 3.11has reproducible stepsThe issue has been confirmed reproducible and is ready to work onplatform-iosiOS applications specifically

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions