flutter / flutter

Flutter makes it easy and fast to build beautiful apps for mobile and beyond
https://flutter.dev
BSD 3-Clause "New" or "Revised" License
165.24k stars 27.27k forks source link

Canvas.drawImageNine incorrect edge case #150299

Open pskink opened 3 months ago

pskink commented 3 months ago

Steps to reproduce

run the attached code, it will call Canvas.drawImageNine 7 times, each case contains two images: the left one is the result of calling Canvas.drawImageNine while the right image shows how the center parameter is positioned within input ui.Image

Expected results

i would expect the case 6 (the one with the red "center" rectangle) to be similar like the case 2 (only reversed / mirrored)

Actual results

the case 6 is the same as cases 1 and 7 where the "center" rectangle is invalid (it is not completely within the image bounds), also i would like to know if the way the image is drawn in those two cases is correct since the official docs don't mention how to deal with the incorrect "center" rect

Code sample

Code sample ```dart import 'dart:typed_data'; import 'dart:ui' as ui; import 'package:flutter/material.dart'; late ui.Image nineImage; void main() async { WidgetsFlutterBinding.ensureInitialized(); final bytes = [ 0x52, 0x49, 0x46, 0x46, 0x46, 0x00, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50, 0x56, 0x50, 0x38, 0x4c, 0x39, 0x00, 0x00, 0x00, 0x2f, 0x07, 0xc0, 0x01, 0x00, 0x1f, 0x20, 0x24, 0x20, 0x9a, 0x03, 0x96, 0xfa, 0x1f, 0xdc, 0x10, 0xd3, 0x10, 0x09, 0x4c, 0xff, 0x8a, 0x22, 0xb8, 0xc4, 0x34, 0xc4, 0xa4, 0xd6, 0x4c, 0x36, 0xe7, 0xfc, 0x07, 0x54, 0xd1, 0xba, 0x81, 0x2c, 0xc0, 0x84, 0x61, 0x02, 0xd1, 0xec, 0x4b, 0x37, 0xa1, 0xac, 0x22, 0xfa, 0x1f, 0xa0, 0xcf, 0xfa, 0xac, 0xa0, 0x00, ]; nineImage = await decodeImageFromList(Uint8List.fromList(bytes)); runApp(MaterialApp( home: Scaffold( body: Padding( padding: const EdgeInsets.all(8), child: SizedBox.expand( child: CustomPaint( painter: FooPainter(), ), ), ), ), )); } class FooPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { double top = 0; final imagePaint = Paint()..color = Colors.black; final imageRect = Offset.zero & const Size(8, 8); for (int i = -1; i < 6; i++) { final center = Rect.fromLTWH(i.toDouble(), 2, 4, 4); final dst = Rect.fromLTWH(0, top, 64, 64); canvas ..drawImageNine(nineImage, center, dst, imagePaint) ..save() ..translate(dst.right + 8, dst.top) ..scale(dst.height / 8) ..translate(1, 0) ..drawRect(imageRect, imagePaint) ..drawRect(center, Paint()..color = switch(i) { -1 || 5 => const Color(0xffff8800), 4 => const Color(0xffff0000), _ => const Color(0xff00ff00), }) ..restore(); top += dst.height + 4; } } @override bool shouldRepaint(FooPainter oldDelegate) => false; } ```

Screenshots or Video

Screenshots / Video demonstration ![screenshot](https://github.com/flutter/flutter/assets/5962376/a316de76-b055-4eb2-ba17-adeb39df681f)

Logs

Logs ```console [Paste your logs here] ```

Flutter Doctor output

Doctor output ```console Doctor summary (to see all details, run flutter doctor -v): [✓] Flutter (Channel stable, 3.22.0, on Arch Linux 6.8.1-arch1-1, locale C.UTF-8) [!] Android toolchain - develop for Android devices (Android SDK version 30.0.3) ✗ Could not determine java version [✓] 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 (3 available) [✓] Network resources ! Doctor found issues in 1 category. ```
danagbemava-nc commented 3 months ago

Reproducible using the code sample provided above.

Labeling for futher investigation.

flutter doctor -v ``` [!] Flutter (Channel stable, 3.22.2, on macOS 14.4.1 23E224 darwin-arm64, locale en-GB) • Flutter version 3.22.2 on channel stable at /Users/nexus/dev/sdks/flutter ! Warning: `flutter` on your path resolves to /Users/nexus/dev/sdks/flutters/bin/flutter, which is not inside your current Flutter SDK checkout at /Users/nexus/dev/sdks/flutter. Consider adding /Users/nexus/dev/sdks/flutter/bin to the front of your path. ! Warning: `dart` on your path resolves to /Users/nexus/dev/sdks/flutters/bin/dart, which is not inside your current Flutter SDK checkout at /Users/nexus/dev/sdks/flutter. Consider adding /Users/nexus/dev/sdks/flutter/bin to the front of your path. • Upstream repository https://github.com/flutter/flutter.git • Framework revision 761747bfc5 (11 days ago), 2024-06-05 22:15:13 +0200 • Engine revision edd8546116 • Dart version 3.4.3 • DevTools version 2.34.3 • If those were intentional, you can disregard the above warnings; however it is recommended to use "git" directly to perform update checks and upgrades. [✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0) • Android SDK at /Users/nexus/Library/Android/sdk • Platform android-34, build-tools 34.0.0 • Java binary at: /Users/nexus/Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java • Java version OpenJDK Runtime Environment (build 17.0.10+0-17.0.10b1087.21-11609105) • All Android licenses accepted. [✓] Xcode - develop for iOS and macOS (Xcode 15.3) • Xcode at /Applications/Xcode-15.3.0.app/Contents/Developer • Build 15E204a • CocoaPods version 1.15.2 [✓] Chrome - develop for the web • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome [✓] Android Studio (version 2024.1) • Android Studio at /Users/nexus/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 17.0.10+0-17.0.10b1087.21-11609105) [✓] IntelliJ IDEA Ultimate Edition (version 2023.2.5) • IntelliJ at /Users/nexus/Applications/IntelliJ IDEA Ultimate.app • Flutter plugin version 77.2.2 • Dart plugin version 232.10286 [✓] VS Code (version 1.89.1) • VS Code at /Applications/Visual Studio Code.app/Contents • Flutter extension version 3.90.0 [✓] Connected device (4 available) • Nexus (mobile) • 00008020-001875E83A38002E • ios • iOS 17.5.1 21F90 • macOS (desktop) • macos • darwin-arm64 • macOS 14.4.1 23E224 darwin-arm64 • Mac Designed for iPad (desktop) • mac-designed-for-ipad • darwin • macOS 14.4.1 23E224 darwin-arm64 • Chrome (web) • chrome • web-javascript • Google Chrome 126.0.6478.62 ! Error: Browsing on the local area network for Dean’s iPad. Ensure the device is unlocked and attached with a cable or associated with the same local area network as this Mac. The device must be opted into Developer Mode to connect wirelessly. (code -27) [✓] Network resources • All expected network resources are available. ! Doctor found issues in 1 category. ``` ``` [✓] Flutter (Channel master, 3.23.0-13.0.pre.242, on macOS 14.4.1 23E224 darwin-arm64, locale en-GB) • Flutter version 3.23.0-13.0.pre.242 on channel master at /Users/nexus/dev/sdks/flutters • Upstream repository https://github.com/flutter/flutter.git • Framework revision 5187cab7bd (2 days ago), 2024-06-14 23:35:34 -0400 • Engine revision 9779c273aa • Dart version 3.5.0 (build 3.5.0-266.0.dev) • DevTools version 2.36.0 [✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0) • Android SDK at /Users/nexus/Library/Android/sdk • Platform android-34, build-tools 34.0.0 • Java binary at: /Users/nexus/Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java • Java version OpenJDK Runtime Environment (build 17.0.10+0-17.0.10b1087.21-11609105) • All Android licenses accepted. [✓] Xcode - develop for iOS and macOS (Xcode 15.3) • Xcode at /Applications/Xcode-15.3.0.app/Contents/Developer • Build 15E204a • CocoaPods version 1.15.2 [✓] Chrome - develop for the web • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome [✓] Android Studio (version 2024.1) • Android Studio at /Users/nexus/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 17.0.10+0-17.0.10b1087.21-11609105) [✓] IntelliJ IDEA Ultimate Edition (version 2023.2.5) • IntelliJ at /Users/nexus/Applications/IntelliJ IDEA Ultimate.app • Flutter plugin version 77.2.2 • Dart plugin version 232.10286 [✓] VS Code (version 1.89.1) • VS Code at /Applications/Visual Studio Code.app/Contents • Flutter extension version 3.90.0 [✓] Connected device (4 available) • Nexus (mobile) • 00008020-001875E83A38002E • ios • iOS 17.5.1 21F90 • macOS (desktop) • macos • darwin-arm64 • macOS 14.4.1 23E224 darwin-arm64 • Mac Designed for iPad (desktop) • mac-designed-for-ipad • darwin • macOS 14.4.1 23E224 darwin-arm64 • Chrome (web) • chrome • web-javascript • Google Chrome 126.0.6478.62 ! Error: Browsing on the local area network for Dean’s iPad. Ensure the device is unlocked and attached with a cable or associated with the same local area network as this Mac. The device must be opted into Developer Mode to connect wirelessly. (code -27) [✓] Network resources • All expected network resources are available. • No issues found! ```
pskink commented 3 months ago

forgot to add that the problem occurs both in x and y axis (when the bottom edge of center rect is equal to the bottom edge of drawn nine-patch image)

jonahwilliams commented 3 months ago

FYI @flar I think you are the only person I know that actually understands drawImageNine, so you might be able to confirm/deny the bug here.

jonahwilliams commented 3 months ago

@pskink just to confirm, this is running on Android?

pskink commented 3 months ago

@jonahwilliams, it's Linux desktop

flar commented 3 months ago

I can demonstrate this with Skia directly: https://fiddle.skia.org/c/122ecd0e0c45ad7f3866106fbd2f93bb

flar commented 3 months ago

It looks like Impeller will happily render out of the bounds which is inconsistent with historic behavior (if we added an edge sampling mode, it might be appropriate, though)

Impeller output Screenshot 2024-06-25 at 1 37 06 PM
jason-simmons commented 3 months ago

The asymmetry between the second and the second-to-last images in the example appears to be a quirk of Skia's implementation.

The second image is using the center rectangle Rect.fromLTRB(0.0, 2.0, 4.0, 6.0) which lies along the left border of the source image. The logic in SkLatticeIter::Valid consider this to be valid.

The second-to-last image is using the center rectangle Rect.fromLTRB(4.0, 2.0, 8.0, 6.0) which lies along the right border. SkLatticeIter::Valid rejects this, so SkCanvas::drawImageNine and SkCanvas::drawImageLattice just draw the input image without the nine patch effect.

As seen above, Impeller does not do the SkLatticeIter::Valid checks and always tries to render a nine patch even if the center rectangle is out of bounds.

flar commented 3 months ago

I filed a bug against Skia: https://issuetracker.google.com/issues/349428795

flar commented 3 months ago

And running on web:

Flutter web with HTML renderer Screenshot 2024-06-25 at 1 58 26 PM