You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter中如何反转PDF黑白像素以实现全局深色模式?

实现Syncfusion PDF的全局深色模式(仅反转黑白像素)

问题背景

使用Syncfusion PDF库渲染PDF时,需要实现全局深色模式,要求仅将白色像素替换为黑色、黑色像素替换为白色,而非反转所有颜色。

已尝试方案及问题

方案1:CustomRepaint

尝试通过自定义Painter处理像素,但组件无法渲染:

class WhiteToBlackPainter extends CustomPainter {
  @override
  Future<void> paint(Canvas canvas, Size size) async {
    // Create a paint object with black color.
    final paint = Paint()..color = Colors.black;

    // Draw the widget's background with black color.
    canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), paint);

    // Get the pixel data of the widget using its `toImage()` method.
    ui.Image image = await (ui.window.render(ui.window.physicalSize)).toImage();
    ByteData? byteData = await image.toByteData();
    Uint8List pixelData = byteData!.buffer.asUint8List();

    // Loop through each pixel in the pixel data, and if the pixel is white, change its color to black.
    for (int i = 0; i < pixelData.length; i += 4) {
      if (pixelData[i] == 255 &&
          pixelData[i + 1] == 255 &&
          pixelData[i + 2] == 255) {
        pixelData[i] = 0;
        pixelData[i + 1] = 0;
        pixelData[i + 2] = 0;
      }
    }

    // Create a new image from the modified pixel data.
    ui.Image newImage = await ui.decodeImageFromPixels(
      pixelData.buffer.asUint8List(),
      image.width,
      image.height,
      ui.PixelFormat.rgba8888,
    );

    // Draw the modified image on the widget's canvas.
    canvas.drawImage(newImage, Offset.zero, paint);
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return false;
  }
}

问题原因:paint方法不能是异步的,CustomPainter的paint方法必须同步执行,异步操作会直接中断渲染流程,导致组件无法正常绘制。

方案2:ColorFiltered

使用BlendMode.difference尝试颜色反转,但会反转所有颜色:

ColorFiltered(
  colorFilter: ColorFilter.mode(
    Colors.white,
    BlendMode.difference),
  child: pdf
)

问题原因:BlendMode.difference会计算每个像素与白色的差值,导致所有颜色都被反转,不符合仅反转黑白像素的需求。

可行解决方案

方案1:使用Syncfusion内置深色模式(推荐)

Syncfusion Flutter PDF Viewer本身提供了深色模式支持,无需手动处理像素,是最简洁的实现方式:

PdfViewer(
  document: PdfDocument.fromBytes(pdfBytes),
  themeMode: ThemeMode.dark, // 直接启用内置深色模式
)

该方案会自动适配PDF内容,将背景转为深色、文字转为浅色,完全符合全局深色模式的需求。

方案2:自定义ColorFilter实现精准黑白反转

如果需要精确控制仅反转黑白像素,可以使用ColorFilter.matrix,通过颜色矩阵实现针对性反转:

ColorFiltered(
  colorFilter: ColorFilter.matrix(<double>[
    -1, 0, 0, 0, 255, // 红色通道反转:255 - 原R值
    0, -1, 0, 0, 255, // 绿色通道反转:255 - 原G值
    0, 0, -1, 0, 255, // 蓝色通道反转:255 - 原B值
    0, 0, 0, 1, 0,    // 透明度通道保持不变
  ]),
  child: PdfViewer(
    document: PdfDocument.fromBytes(pdfBytes),
    backgroundColor: Colors.black, // 设置背景为黑色
  ),
)

原理:颜色矩阵中的-1会反转对应颜色通道的值,加上255实现255 - 原颜色值的反转效果。配合黑色背景,可实现黑白像素的精准反转(灰色像素也会被反转,但如果PDF仅包含黑白内容,效果完全符合预期)。

方案3:修复CustomRepaint实现像素级处理

如果必须通过像素级操作实现,需要将异步逻辑移到外部,确保paint方法同步执行:

class BlackWhiteInvertPainter extends CustomPainter {
  final ui.Image pdfImage;

  BlackWhiteInvertPainter(this.pdfImage);

  @override
  void paint(Canvas canvas, Size size) {
    // 绘制黑色背景
    canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), Paint()..color = Colors.black);

    // 获取图片字节数据
    final byteData = pdfImage.toByteData(format: ui.ImageByteFormat.rgba8888);
    if (byteData == null) return;

    final pixels = byteData.buffer.asUint8List();
    // 遍历像素,仅反转黑白(添加阈值兼容非纯黑纯白的情况)
    for (int i = 0; i < pixels.length; i += 4) {
      final r = pixels[i];
      final g = pixels[i+1];
      final b = pixels[i+2];
      // 判断接近白色
      if (r > 240 && g > 240 && b > 240) {
        pixels[i] = 0;
        pixels[i+1] = 0;
        pixels[i+2] = 0;
      }
      // 判断接近黑色
      else if (r < 15 && g < 15 && b < 15) {
        pixels[i] = 255;
        pixels[i+1] = 255;
        pixels[i+2] = 255;
      }
      // 其他颜色保持不变
    }

    // 生成反转后的图片并绘制
    ui.decodeImageFromPixels(
      pixels,
      pdfImage.width,
      pdfImage.height,
      ui.PixelFormat.rgba8888,
      (ui.Image newImage) {
        canvas.drawImage(newImage, Offset.zero, Paint());
      },
    );
  }

  @override
  bool shouldRepaint(covariant BlackWhiteInvertPainter oldDelegate) {
    return pdfImage != oldDelegate.pdfImage;
  }
}

使用时需先将PDF页面转为Image,再传入Painter:

// 渲染PDF页面为Image(Syncfusion提供的方法)
final pdfDocument = PdfDocument.fromBytes(pdfBytes);
final page = pdfDocument.pages[0];
final pdfImage = await page.render();

// 使用CustomPaint渲染
CustomPaint(
  painter: BlackWhiteInvertPainter(pdfImage!),
  size: Size(pdfImage.width.toDouble(), pdfImage.height.toDouble()),
)

注意:该方案复杂度较高,仅在特殊场景下使用。


内容的提问来源于stack exchange,提问作者onie

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 23:47:25