Skip to content

html web-renderer incorrectly renders nine-slice images with an empty center region #78068

@flar

Description

@flar

drawImageNine is not well documented for the case where the center region disappears, but it is implemented consistently for all of the Skia backed platforms such as mobile and the canvaskit web renderer. Unfortunately, the html web renderer throws exceptions on a debug build and produces an inconsistent output on non-debug builds.

There are already issues filed for the lack of documentation of drawImageNine and the centerSlice properties, but the behavior that Skia provides when the center region disappears makes sense and should become the documented behavior. Basically, the center slice takes up all of the scaling until it goes to a zero size. It is fine to have an empty center section, not a condition that should be asserted as in the web renderer. At that point only the 4 corners of the image are visible and if you continue to scale down then the corners will start scaling down with the reduced size.

Here is the test sample:

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

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Nine Slice Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Nine Slice Demo'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  ui.Image theImage;
  double size = 200.0;

  @override
  void initState() {
    super.initState();
    makeImage().then((image) => setState(() {
      theImage = image;
    }));
  }

  void newSize(double newSize) {
    setState(() {
      size = newSize;
    });
  }

  Future<ui.Image> makeImage() {
    Rect bounds = Rect.fromLTWH(0, 0, 200, 200);
    ui.PictureRecorder recorder = ui.PictureRecorder();
    Canvas canvas = Canvas(recorder, bounds);
    paintCheckerboard(canvas, bounds, 10, 10, Colors.white, Colors.grey);
    paintCheckerboard(canvas, bounds.deflate(50), 10, 10, Colors.blue, Colors.green);
    return recorder.endRecording().toImage(200, 200);
  }

  paintCheckerboard(Canvas canvas, Rect bounds, double w, double h, Color c1, Color c2) {
    Paint p = Paint();
    double y = bounds.top;
    for (int i = 0; i < bounds.height / h; i++) {
      double x = bounds.left;
      for (int j = 0; j < bounds.width / w; j++) {
        p.color = (i ^ j) & 1 == 0 ? c1 : c2;
        canvas.drawRect(Rect.fromLTWH(x, y, w, h), p);
        x += w;
      }
      y += h;
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: theImage == null ? null : CustomPaint(
          size: Size(size, size),
          painter: NineSlicePainter(
            theImage,
            const Rect.fromLTWH(50, 50, 100, 100),
            Rect.fromLTWH(0, 0, size, size),
          ),
          willChange: false,
          isComplex: false,
        ),
      ),
      bottomNavigationBar: BottomAppBar(
        child: Wrap(
          children: [
            Slider(
              value: size,
              min: 50,
              max: 300,
              onChanged: theImage == null ? null : newSize,
            ),
          ],
        ),
      ),
    );
  }
}

class NineSlicePainter extends CustomPainter {
  NineSlicePainter(this.theImage, this.center, this.dst);

  Rect center;
  Rect dst;
  ui.Image theImage;

  @override
  void paint(Canvas canvas, Size size) {
    canvas.drawImageNine(theImage, center, dst, Paint());
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    if (oldDelegate is NineSlicePainter) {
      return oldDelegate.dst != dst || oldDelegate.theImage != theImage;
    }
    return true;
  }
}

Metadata

Metadata

Assignees

Labels

e: web_htmlHTML rendering backend for Webengineflutter/engine related. See also e: labels.found in release: 2.1Found to occur in 2.1has reproducible stepsThe issue has been confirmed reproducible and is ready to work onplatform-webWeb applications specifically

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions