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
相关产品推荐
相关产品推荐

