【问题标题】:Flutter Web: Crop image without dart:ioFlutter Web:不使用 dart:io 裁剪图像
【发布时间】:2020-12-14 12:53:06
【问题描述】:

我是在 dart:io 无法用于 Flutter Web 的时候写这篇文章的。 dart:io 具有大多数 Flutter 映像包所需的常用“文件”类型。

尝试以 UInt8List 格式裁剪未知编码的图像。我花了几天时间构建了一个没有 dart:io 的简单裁剪工具

检查下面的解决方案。

【问题讨论】:

    标签: flutter-web


    【解决方案1】:

    我会把它变成一个包,但我正在匆忙完成一个项目,没有时间。

    使用此代码块来初始化裁剪路线:

    
        Future<Uint8List> cropResult = await Navigator.push(
          context,
          MaterialPageRoute(
            builder: (ctx) => Cropper(
              image: _image,
            ),
          ),
        );
        _image = await cropResult;
    

    这是管理作物的路线页面。非常基础。

    import 'dart:async';
    import 'dart:typed_data';
    import 'package:flutter/material.dart';
    import 'package:image/image.dart' as Im;
    import 'dart:math';
    
    class Cropper extends StatefulWidget {
      final Uint8List image;
    
      const Cropper({Key key, this.image}) : super(key: key);
    
      @override
      _CropperState createState() => _CropperState(image: image);
    }
    
    class _CropperState extends State<Cropper> {
      Uint8List image;
      Uint8List resultImg;
      double scale = 1.0;
      double zeroScale;                 //Initial scale to fit image in bounding crop box.
      Offset offset = Offset(0.0, 0.0); //Used in translation of image.
      double cropRatio = 6 / 10;        //aspect ratio of desired crop.
      Im.Image decoded;                 //decoded image to get pixel dimensions
      double imgWidth;                  //img pixel width
      double imgHeight;                 //img pixel height
      Size cropArea;                    //Size of crop bonding box
      double cropPad;                   //Aesthetic crop box padding.
    
      double pXa;                       //Positive X available in translation
      double pYa;                       //Positive Y available in translation
      double totalX;                    //Total X of scaled image
      double totalY;                    //Total Y of scaled image
    
      Completer _decoded = Completer<bool>();
      Completer _encoded = Completer<Uint8List>();
    
      _CropperState({this.image});
    
      @override
      initState() {
        _decodeImg();
        super.initState();
      }
    
      _decodeImg() {
        if (_decoded.isCompleted) return;
        decoded = Im.decodeImage(image);
        imgWidth = decoded.width.toDouble();
        imgHeight = decoded.height.toDouble();
        _decoded?.complete(true);
      }
    
      _encodeImage(Im.Image cropped) async {
        resultImg = Im.encodePng(cropped);
        _encoded?.complete(resultImg);
      }
    
      void _cropImage() async {
        double xPercent = pXa != 0.0 ? 1.0 - (offset.dx + pXa) / (2 * pXa) : 0.0;
        double yPercent = pYa != 0.0 ? 1.0 - (offset.dy + pYa) / (2 * pYa) : 0.0;
        double cropXpx = imgWidth * cropArea.width / totalX;
        double cropYpx = imgHeight * cropArea.height / totalY;
        double x0 = (imgWidth - cropXpx) * xPercent;
        double y0 = (imgHeight - cropYpx) * yPercent;
        Im.Image cropped = Im.copyCrop(
            decoded, x0.toInt(), y0.toInt(), cropXpx.toInt(), cropYpx.toInt());
        _encodeImage(cropped);
        Navigator.pop(context, _encoded.future);
      }
    
      computeRelativeDim(double newScale) {
        totalX = newScale * cropArea.height * imgWidth / imgHeight;
        totalY = newScale * cropArea.height;
        pXa = 0.5 * (totalX - cropArea.width);
        pYa = 0.5 * (totalY - cropArea.height);
      }
    
      bool init = true;
    
      @override
      Widget build(BuildContext context) {
        final theme = Theme.of(context);
        return Scaffold(
          appBar: AppBar(
            title: Text('Crop Photo'),
            centerTitle: true,
            leading: IconButton(
              onPressed: _cropImage,
              tooltip: 'Crop',
              icon: Icon(Icons.crop),
            ),
            actions: [
              RaisedButton(
                onPressed: () => Navigator.pop(context, null),
                child: Text('Cancel'),
              )
            ],
          ),
          body: Column(
            children: <Widget>[
              Expanded(
                child: FutureBuilder(
                  future: _decoded.future,
                  builder: (ctx, snap) {
                    if (!snap.hasData)
                      return Center(
                        child: Text('Loading...'),
                      );
                    return LayoutBuilder(
                      builder: (ctx, cstr) {
                        if (init) {
                          cropPad = cstr.maxHeight * 0.05;
                          double tmpWidth = cstr.maxWidth - 2 * cropPad;
                          double tmpHeight = cstr.maxHeight - 2 * cropPad;
                          cropArea = (tmpWidth / cropRatio > tmpHeight)
                              ? Size(tmpHeight * cropRatio, tmpHeight)
                              : Size(tmpWidth, tmpWidth / cropRatio);
                          zeroScale = cropArea.height / imgHeight;
                          computeRelativeDim(scale);
                          init = false;
                        }
                        return GestureDetector(
                          onPanUpdate: (pan) {
                            double dy;
                            double dx;
                            if (pan.delta.dy > 0)
                              dy = min(pan.delta.dy, pYa - offset.dy);
                            else
                              dy = max(pan.delta.dy, -pYa - offset.dy);
                            if (pan.delta.dx > 0)
                              dx = min(pan.delta.dx, pXa - offset.dx);
                            else
                              dx = max(pan.delta.dx, -pXa - offset.dx);
                            setState(() => offset += Offset(dx, dy));
                          },
                          child: Stack(
                            children: [
                              Container(
                                color: Colors.black.withOpacity(0.5),
                                height: cstr.maxHeight,
                                width: cstr.maxWidth,
                                child: ClipRect(
                                  child: Container(
                                    alignment: Alignment.center,
                                    height: cropArea.height,
                                    width: cropArea.width,
                                    child: Transform.translate(
                                      offset: offset,
                                      child: Transform.scale(
                                        scale: scale * zeroScale,
                                        child: OverflowBox(
                                          maxWidth: imgWidth,
                                          maxHeight: imgHeight,
                                          child: Image.memory(
                                            image,
                                          ),
                                        ),
                                      ),
                                    ),
                                  ),
                                ),
                              ),
                              IgnorePointer(
                                child: Center(
                                  child: Container(
                                    height: cropArea.height,
                                    width: cropArea.width,
                                    decoration: BoxDecoration(
                                      border:
                                          Border.all(color: Colors.white, width: 2),
                                    ),
                                  ),
                                ),
                              ),
                            ],
                          ),
                        );
                      },
                    );
                  },
                ),
              ),
              Row(
                children: <Widget>[
                  Text('Scale:'),
                  Expanded(
                    child: SliderTheme(
                      data: theme.sliderTheme,
                      child: Slider(
                        divisions: 50,
                        value: scale,
                        min: 1,
                        max: 2,
                        label: '$scale',
                        onChanged: (n) {
                          double dy;
                          double dx;
                          computeRelativeDim(n);
                          dy = (offset.dy > 0)
                              ? min(offset.dy, pYa)
                              : max(offset.dy, -pYa);
                          dx = (offset.dx > 0)
                              ? min(offset.dx, pXa)
                              : max(offset.dx, -pXa);
                          setState(() {
                            offset = Offset(dx, dy);
                            scale = n;
                          });
                        },
                      ),
                    ),
                  ),
                ],
              ),
            ],
          ),
        );
      }
    }
    

    【讨论】:

    • 你能告诉我如何将裁剪图像的 Uint8List 上传到 Firebase 存储吗?我无法生成 Uint8List 文件。
    • 您是通过浏览器上传图片吗?如果您已成功完成此操作,则必须使用 "Base64Decoder().convert(reader.result.toString().split(",").last);" 解码文件Reader 是 FileReader 的一个实例。
    • 在flutter web的html和canvaskit渲染器上都能用吗?
    猜你喜欢
    • 2019-05-25
    • 1970-01-01
    • 2022-01-23
    • 2021-06-24
    • 2013-06-10
    • 2017-05-22
    • 1970-01-01
    • 2020-11-01
    • 1970-01-01
    相关资源
    最近更新 更多