Repository: flutter
Issue: boundary.toImage().toByteData() handles transparency differently on physical iphone devices with 3.27.1
Labels: platform-ios,engine,dependency: skia,has reproducible steps,P2,team-engine,triaged-engine,found in release: 3.27,found in release: 3.28

### Steps to reproduce

Paint on a canvas wrapped in a RepaintBoundary but leave some area unpainted (transparent).
call boundary.toImage().toByteData(format: ui.ImageByteFormat.png) and then save the resulting image as png file.

On flutter 3.24.x and prior on all devices the unpainted pixel RGBA values are 0,0,0,0 (black transparent). I have tested android emulator, android devices, web, ios emulator, ios devices.
On flutter 3.27.1 ios physical devices have unpainted pixel values of 255,255,255,0 (white transparent) *only* ios physical devices, ios emulators, android, and web all still have pixel values of 0,0,0,0.

Why does the color of a transparent pixel matter? If you call canvas.drawImage(img, Offset(0,0), paint) the paint gets applied to all non-zero pixels. It also effects anything that looks at the pixel values of course besides the alpha channel.



### Expected results

Run the below code, and press the 'toImage' in the lower right to run the test .

The output should be:

![Screenshot_1736475919](https://github.com/user-attachments/assets/7bba8044-3794-4acc-9205-4ef261449528)

The top is the canvas, the bottom are saved versions of the output. The last one shows the image with the alpha channel removed.
The expected value of the unpainted area around the red circle is black (historically and on all devices except physical iphones)



### Actual results

On physical iphone devices (not emulator) with flutter 3.27.1 the unpainted pixels are 255,255,255,0
This results in the output below.

![image](https://github.com/user-attachments/assets/e96cfa75-4a78-4291-8fd3-4e954bdbf20e)

Also the image quality is terrible on a physical iphone device in addition to the pixel values being different.
This is not true on an iphone emulator of the same device type:

![simulator_screenshot_97B93356-99AF-445E-B1E4-1E359B4CFB4E](https://github.com/user-attachments/assets/c57e4e7b-746e-4e88-9137-939585c1c6eb)


Note the pixel values printed are correct/expected, because they are pulled from toImage(rawRGBA). So i suspect this issue is related to the png encoding process on iphone specifically.

Edit: I also tried some 'hacks' like calling drawColor(0x00000000, BlendMode.src) on the canvas, and that did not fix it on the iphone. 

### Code sample

<details open><summary>Code sample</summary>

```dart
import 'dart:typed_data';
import 'dart:ui' as ui;

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:image/image.dart' as imglib;

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'PixelTest',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'RepaintBoundary Pixel Test'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  static final GlobalKey repaintBoundaryKey = GlobalKey();

  String _hex = "";
  int _red = -1;
  int _green = -1;
  int _blue = -1;
  int _alpha = -1;

  ui.Image? _image;
  Uint8List? _png1;
  Uint8List? _png2;
  Uint8List? _png3;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Container(
              decoration: BoxDecoration(border: Border.all(color: Colors.blueAccent)),
              child: RepaintBoundary(
                key: repaintBoundaryKey,
                child: ClipRect(
                  child: CustomPaint(
                    size: Size(100, 100),
                    painter: MyCustomPainter(),
                  ),
                ),
              ),
            ),
            SizedBox(height: 20),
            Text('Background Color:'),
            Text('Red: $_red Green: $_green Blue: $_blue Alpha: $_alpha'),
            Text('Hex: $_hex'),
            SizedBox(height: 20),
            _image != null
                ? Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      Text("Raw ui.Image "),
                      Container(
                        decoration: BoxDecoration(border: Border.all(color: Colors.blueAccent), color: Colors.grey),
                        child: RawImage(image: _image),
                      ),
                    ],
                  )
                : Container(),
            SizedBox(height: 20),
            _png1 != null
                ? Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      Text("toByteData(format: png) "),
                      Container(
                        decoration: BoxDecoration(border: Border.all(color: Colors.blueAccent), color: Colors.grey),
                        child: Image.memory(_png1!),
                      ),
                    ],
                  )
                : Container(),
            SizedBox(height: 20),
            _png2 != null
                ? Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      Text("Reencoded "),
                      Container(
                        decoration: BoxDecoration(border: Border.all(color: Colors.blueAccent), color: Colors.grey),
                        child: Image.memory(_png2!),
                      ),
                    ],
                  )
                : Container(),
            SizedBox(height: 20),
            _png3 != null
                ? Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      Text("No Alpha "),
                      Container(
                        decoration: BoxDecoration(border: Border.all(color: Colors.blueAccent), color: Colors.grey),
                        child: Image.memory(_png3!),
                      ),
                    ],
                  )
                : Container(),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () async {
          RenderRepaintBoundary boundary = repaintBoundaryKey.currentContext!.findRenderObject() as RenderRepaintBoundary;
          ui.Image image = await boundary.toImage();
          ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.rawRgba);
          if (byteData == null) {
            return;
          }
          var uintlist = byteData!.buffer.asUint32List();
          if (uintlist.isEmpty) {
            return;
          }
          // png1 - just raw output of toByteData()
          ByteData? pngData = await image.toByteData(format: ui.ImageByteFormat.png);
          _png1 = pngData!.buffer.asUint8List();

          // png2 - convert to image.Image and back
          imglib.Image? img = imglib.decodePng(_png1!);
          _png2 = imglib.encodePng(img!);

          // png3 - remove alpha channel
          img = img.convert(format: imglib.Format.uint8, numChannels: 3);
          img = img.convert(format: imglib.Format.uint8, numChannels: 4, alpha: img.maxChannelValue);
          _png3 = imglib.encodePng(img);

          setState(() {
            _image = image;
            _hex = "0x${uintlist[0].toRadixString(16).padLeft(8, '0')}";
            _red = (uintlist[0] & 0x000000ff);
            _green = (uintlist[0] & 0x0000ff00) >> 8;
            _blue = (uintlist[0] & 0x00ff0000) >> 16;
            _alpha = (uintlist[0] & 0xff000000) >> 24;
          });
        },
        child: const Text("toImage"),
      ),
    );
  }
}

// paints nothing
class MyCustomPainter extends CustomPainter {
  MyCustomPainter();

  @override
  void paint(Canvas canvas, Size size) async {
    // canvas.drawColor(const Color(0xff000000), BlendMode.src); // black
    // canvas.drawColor(const Color(0xffff0000), BlendMode.src); // green
    // canvas.drawColor(const Color(0xff00ff00), BlendMode.src); // green
    // canvas.drawColor(const Color(0xff0000ff), BlendMode.src); // blue
    // canvas.drawColor(const Color(0x00ffffff), BlendMode.src); // white transpaent
    // canvas.drawColor(const Color(0x00000000), BlendMode.src); // black transparent

    Paint p = Paint()..color = Colors.red;
    canvas.drawCircle(Offset(50, 50), 25.0, p);
  }

  @override
  bool shouldRepaint(MyCustomPainter oldDelegate) => true;
}
```

</details>


### Flutter Doctor output

<details open><summary>Doctor output</summary>

```console
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.27.1, on Debian GNU/Linux 12 (bookworm) 6.1.0-28-amd64, locale en_US.UTF-8)
[✓] Android toolchain - develop for Android devices (Android SDK version 35.0.0-rc2)
[✓] Chrome - develop for the web
[✓] Linux toolchain - develop for Linux desktop
[✓] Android Studio (version 2023.2)
[✓] Connected device (3 available)
[✓] Network resources

• No issues found!
```

</details>