3

我有一个屏幕,允许用户导入图像并拖放它们以重新定位、调整大小和旋转它们。每个对象都是一个小部件,所有这些对象的父级都是一个Stack小部件。我使用了 matrix_gesture_detector 0.1.0库来处理转换。我已使用屏幕截图库将此Stack小部件保存为图像。但是,如果它很低,我会得到像素化的图像,但如果它很高,图像大小会增加很多(大约 25MB)。我想知道如何在不增加图像大小的情况下提高图像质量。如果还有其他方法,我也想了解它们。我的图像可以是或。pixelRatioAssetImageNetWorkImage

我的截图代码:

....
....

Screenshot(
   controller: screenshotController,
   child: Container(
      key: Key("canvas"),
      decoration: BoxDecoration(
        color: currentColor,
        borderRadius: BorderRadius.all(
          Radius.circular(dp2),
        ),
      ),
      child: Stack(
        alignment: Alignment.center,
        children: _selectedWidgets, // here selected widgets is a lis of widgets
      ),
    ),
  )

//save image method
saveImage() async {
    

    String fileName = DateTime.now().toIso8601String();
    var path = '$directoryPath/$fileName.png';

    screenshotController
        .capture(
      pixelRatio: 5,
      path: path,
      delay: Duration(milliseconds: 10),
    ).then((File image) async {
      image = await compressImage(image);
     
      ........
      .........
      
    }).catchError((onError, stack) {
      log("stack = $stack");
    });
  }

// compressing function compress based on the size of the image
Future<File> compressImage(File image) async {

  int fileSize = image.lengthSync();

  int percentage = getPercentage(fileSize);

  return FlutterNativeImage.compressImage(image.path, quality: 25, percentage: percentage);
}

int getPercentage(int fileSize) {
  if(fileSize <= 1073741824)
    return 75;
  if(fileSize <= 2147483648)
    return 60;
  if(fileSize <= 3221225472)
    return 50;
  if(fileSize <= 4294967296)
    return 40;
  if(fileSize <= 5368709120)
    return 25;

  return 25;
}


4

2 回答 2

1

所以你的应用程序的整个目标是创建一个拼贴图像并将它们保存到磁盘?(如果你先解释一下可能会有用)

我建议要么限制“画布”区域的大小,要么在保存之前调整它的大小。您还可以尝试各种压缩算法,例如 JPEG 及其参数。不知道质量关闭FlutterNativeImage。也许你可以看看其他一些包?我看到flutter_image_compress包有很多赞。

于 2021-09-08T20:41:30.727 回答
0

你可以在这里找到官方的 toImage 示例。但看起来它在按钮点击和 toImage 调用之间没有延迟。

官方仓库有问题: https ://github.com/flutter/flutter/issues/22308

造成这种情况的原因:您的点击初始化按钮的动画,并且 RenderObject.markNeedsPaint 被递归调用,包括父级,因此您应该等待 debugNeedsPaint 再次为 false。在这种情况下,toImage 函数只会抛出断言错误:

Future<ui.Image> toImage({ double pixelRatio = 1.0 }) {
    assert(!debugNeedsPaint);
    final OffsetLayer offsetLayer = layer;
    return offsetLayer.toImage(Offset.zero & size, pixelRatio: pixelRatio);
  }

https://github.com/flutter/flutter/blob/f0553ba58e6455aa63fafcdca16100b81ff5c3ce/packages/flutter/lib/src/rendering/proxy_box.dart#L2857

bool get debugNeedsPaint {
    bool result;
    assert(() {
      result = _needsPaint;
      return true;
    }());
    return result;
  }

https://github.com/flutter/flutter/blob/0ca5e71f281cd549f1b5284e339523ad93544c60/packages/flutter/lib/src/rendering/object.dart#L2011

实际上 assert 函数只在开发中使用,所以你可以看到你不会在生产中遇到错误的麻烦。但是我不知道您会遇到什么样的麻烦,可能没有)。

下一个代码不是很好,但它可以工作:

class _MyHomePageState extends State<MyHomePage> {
  GlobalKey globalKey = GlobalKey();

  Future<Uint8List> _capturePng() async {
    RenderRepaintBoundary boundary = globalKey.currentContext.findRenderObject();

    if (boundary.debugNeedsPaint) {
      print("Waiting for boundary to be painted.");
      await Future.delayed(const Duration(milliseconds: 20));
      return _capturePng();
    }

    var image = await boundary.toImage();
    var byteData = await image.toByteData(format: ImageByteFormat.png);
    return byteData.buffer.asUint8List();
  }

  void _printPngBytes() async {
    var pngBytes = await _capturePng();
    var bs64 = base64Encode(pngBytes);
    print(pngBytes);
    print(bs64);
  }

  @override
  Widget build(BuildContext context) {
    return RepaintBoundary(
      key: globalKey,
      child: Center(
        child: FlatButton(
          color: Color.fromARGB(255, 255, 255, 255),
          child: Text('Capture Png', textDirection: TextDirection.ltr),
          onPressed: _printPngBytes
        ),
      ),
    );
  }
}
于 2021-09-09T07:15:19.533 回答