xclud / flutter_crop

Crop any widget/image in Android, iOS, Web and Desktop with fancy and customizable UI, in pure Dart code.
https://pub.dev/packages/crop
MIT License
259 stars 84 forks source link

Cropping and zooming doesn't work on Web #11

Closed stalko closed 3 years ago

stalko commented 4 years ago
  1. The image looks darkened then it has;
  2. Can't zoom image(trying with the mouse wheel);
  3. Got the error: "Unsupported operation: toImage is not supported on the Web" after pressing "Crop" button.

Tested on: Chrome(Version 80.0.3987.163 (Official Build) (64-bit)), Mozilla Firefox(75.0 (64-bit)) deps:

crop: ^0.3.1+1
image_picker_web: ^1.0.6
image_picker: ^0.6.5

Source code:

import 'package:flutter/material.dart';
import 'package:image_picker_web/image_picker_web.dart';
import './image.dart';
import 'dart:io';
import 'package:image_picker/image_picker.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'dart:ui';
import './centered_slider_track_shape.dart';
import 'package:flutter/material.dart';
import 'package:crop/crop.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:path/path.dart' as Path;
import 'package:path_provider/path_provider.dart';
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'dart:math';

class UserPage extends StatefulWidget {
  @override
  _UserPageState createState() => _UserPageState();
}

class _UserPageState extends State<UserPage> {
  Image pickedImage;

  @override
  void initState() {
    super.initState();
  }

  Future getImageMobile() async {
    var image = await ImagePicker.pickImage(source: ImageSource.gallery);

    setState(() {
      pickedImage = Image.file(
        image,
        fit: BoxFit.cover,
      );
    });
  }

  getImageWeb() async {
    Image fromPicker =
        await ImagePickerWeb.getImage(outputType: ImageType.widget);
    if (fromPicker != null) {
      setState(() {
        pickedImage = fromPicker;
      });
    }
  }

  pickImage() async {
    if (kIsWeb) {
      await getImageWeb();
    } else {
      await getImageMobile();
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Image Picker Web Example'),
        ),
        body: Center(
            child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                crossAxisAlignment: CrossAxisAlignment.center,
                mainAxisSize: MainAxisSize.min,
                children: <Widget>[
              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                crossAxisAlignment: CrossAxisAlignment.center,
                mainAxisSize: MainAxisSize.min,
                children: <Widget>[
                  AnimatedSwitcher(
                    duration: Duration(milliseconds: 300),
                    switchInCurve: Curves.easeIn,
                    child: SizedBox(
                          width: 200,
                          child: pickedImage,
                        ) ??
                        Container(),
                  ),
                  SizedBox(
                    width: 15,
                  ),
                ],
              ),
              ButtonBar(alignment: MainAxisAlignment.center, children: <Widget>[
                RaisedButton(
                  onPressed: () => pickImage(),
                  child: Text('Select Image'),
                ),
                RaisedButton(
                  onPressed: () {
                    Navigator.of(context).push(
                      MaterialPageRoute<void>(
                          builder: (_) => ImagePage(
                                image: pickedImage,
                              )),
                    );
                  },
                  child: Text('Crop'),
                ),
              ]),
            ])),
      ),
    );
  }
}

class ImagePage extends StatefulWidget {
  ImagePage({Key key, @required this.image}) : super(key: key);

  final Image image;
  @override
  _ImagePageState createState() => _ImagePageState(image);
}

class _ImagePageState extends State<ImagePage> {
  _ImagePageState(this.image);
  final Image image;
  String _uploadedFileURL;
  final controller = CropController(aspectRatio: 1000 / 667.0);
  double _rotation = 0;

  @override
  void initState(){
    super.initState();
    controller.aspectRatio = 1;
    setState(() {

    });
  }

  void _cropImage() async {
    final pixelRatio = MediaQuery.of(context).devicePixelRatio;
    final cropped = await controller.crop(pixelRatio: pixelRatio);

    Navigator.of(context).push(
      MaterialPageRoute(
        builder: (context) => Scaffold(
          appBar: AppBar(
            title: Text('Crop Result'),
            centerTitle: true,
            actions: <Widget>[
              IconButton(
                icon: Icon(Icons.file_upload),
                tooltip: 'Upload',
                onPressed: () async {
                  // getting a directory path for saving
                  var dir = await getApplicationDocumentsDirectory();
                  final ByteData data = await cropped.toByteData(format: ImageByteFormat.png);
                  final buffer = data.buffer;

                  final String fileName = Random().nextInt(10000).toString() +'.png';
                  print(fileName);

                  final File newImage = await new File('${dir.path}/$fileName').writeAsBytes(
                      buffer.asUint8List(data.offsetInBytes, data.lengthInBytes));
                  await uploadFile(newImage);
                },
              ),
            ],
          ),
          body: Center(
            child: RawImage(
              image: cropped,
            ),
          ),
        ),
        fullscreenDialog: true,
      ),
    );
  }

  Future uploadFile(File img) async {
  }

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    return Scaffold(
      appBar: AppBar(
        title: Text('Crop Demo'),
        centerTitle: true,
        actions: <Widget>[
          IconButton(
            onPressed: _cropImage,
            tooltip: 'Crop',
            icon: Icon(Icons.crop),
          )
        ],
      ),
      body: Column(
        children: <Widget>[
          Expanded(
            child: Container(
              color: Colors.black,
              padding: EdgeInsets.all(8),
              child: Crop(
                controller: controller,
                child: image,
                helper: Container(
                  decoration: BoxDecoration(
                    border: Border.all(color: Colors.white, width: 2),
                  ),
                ),
              ),
            ),
          ),
          Row(
            children: <Widget>[
              IconButton(
                icon: Icon(Icons.undo),
                tooltip: 'Undo',
                onPressed: () {
                  controller.rotation = 0;
                  controller.scale = 1;
                  controller.offset = Offset.zero;
                  setState(() {
                    _rotation = 0;
                  });
                },
              ),
              Expanded(
                child: SliderTheme(
                  data: theme.sliderTheme.copyWith(
                    trackShape: CenteredRectangularSliderTrackShape(),
                  ),
                  child: Slider(
                    divisions: 361,
                    value: _rotation,
                    min: -180,
                    max: 180,
                    label: '$_rotation°',
                    onChanged: (n) {
                      setState(() {
                        _rotation = n.roundToDouble();
                        controller.rotation = _rotation;
                      });
                    },
                  ),
                ),
              ),
            ],
          ),
        ],
      ),
    );
  }
}
xclud commented 4 years ago

@stalko Flutter for web is not ready for production yet. There are several bugs in flutter itself that prevents this package from working.

As for the darkened area, here you can get more details about the bug: https://github.com/flutter/flutter/issues/49534

I hope it will eventually work in the production version of flutter for web.

firatcetiner commented 4 years ago

@stalko Flutter for web is not ready for production yet. There are several bugs in flutter itself that prevents this package from working.

As for the darkened area, here you can get more details about the bug: flutter/flutter#49534

I hope it will eventually work in the production version of flutter for web.

Then you should not state that "This package is entirely written in Dart and supports Android, iOS, Web and Desktop." as written in the package descrption since it only work with the iOS and Android.

Good package though.

xclud commented 4 years ago

@firatcetiner This package is designed to work on all platforms. This means i didn't use any platform specific/native code. So technically talking, this package works on the web. Package authors are responsible for their own code not other packages or flutter bugs. I hope this clarifies.

xclud commented 4 years ago

Fixing the clip behaviors in flutter team's todonow. Check out https://github.com/flutter/flutter/projects/156

I hope this bug will soon be fixed. Also check out the following issues:

https://github.com/flutter/flutter/issues/59392 https://github.com/flutter/flutter/issues/58397

xclud commented 4 years ago

Hi again everyone, I just run Crop on Windows 10. Attached is the screenshot.

Crop on Windows 10

lewooz commented 4 years ago

Still getting the same error for web. I hope this gets fixed soon.

deakjahn commented 4 years ago

@xclud My PR https://github.com/flutter/engine/pull/19655 is already there. Hopefully it will be accepted soon. :-)

xclud commented 4 years ago

@deakjahn Thank you for your efforts. I really appreciate it. This is the best news for my package.

deakjahn commented 4 years ago

I needed it so badly in my app that I couldn't find any other way but to fix it. :-) Similarly to your package, but probably even at a higher level, I need picture manipulation, both simple canvas and even seriously more than that.

I'm adding your crop widget to my app right now, literally. I look forward to it. :-)

xclud commented 4 years ago

@deakjahn I am curious about your project. Can you share a link? If it's not open source executables are enough.

I am already in love with https://itsallwidgets.com/postmuse-for-instagram by @andreidiaconu but it seems it's not open source.

deakjahn commented 4 years ago

Sorry, this isn't possible right now. :-) When finished, it will be available in Play Store, App Store, Web, whatever, but right now, it's still in development and we're doing it for a company, not as a standalone developer. :-) But the main point is that we allow the user to create stuff using, among other items, their own images and we also allow them to modify those pictures in many ways, the usual ones, from brightness and contrast to sophisticated filters. Including cropping, of course.

Now the time has come to add crop and rotate. I had a previous version written in Xamarin some time ago but we left Xamarin for Flutter and never looked back. This previous solution was more like the image_cropper package, I mean, by the looks of it. The usual grid. Actually, I used https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/graphics/skiasharp/bitmaps/cropping as a starting point back then.

Just for fun, I started to experiment with the same look for the helper decoration with your package. And while we're at it, I'm a bit tempted to implement the measuring tape-like rotation slider of that "competitor" package of yours. :-) And, while we're still at it, I might also ask you a bit about what finger gestures you actually have for mobile and web, maybe two finger rotation and pinch scale? Needless to say, I'll send back any addition you might be interested in.

xclud commented 4 years ago

Can't wait to play with your widget once it's published.

Pinch scale already works, i have plans forv2 fingervrotation with no ETA.

And yes! Please do not hesitate to share any additional details, but please open a new issue for that as it might go off-topic as of this issue.

And please contribute!

deakjahn commented 4 years ago

Will do with that simple box decoration right now, then we'll see what's next.

michaldev commented 4 years ago

Hi. When I try cut the image, I see this error: Unsupported operation: toImage is not supported on the Web

This is from: final cropped = await controller.crop(pixelRatio: pixelRatio);

xclud commented 4 years ago

@michaldev This is already discussed above. TL;DR its a bug/wip in flutter for web.

michaldev commented 4 years ago

Okay, I thought the discussion was about zooming issues.

xclud commented 4 years ago

@michaldev 👍 :)

Supportcomm-app commented 4 years ago

Exist a solution for this problem? Crop WEB

deisold commented 4 years ago

I also need the cropping on web. Apparently the ui.Image class is not (yet) properly implemented on web. Is there a way to extract the cropped image to a Uint8List instead of an image? This would work then with Image.memory().

I came across https://github.com/flutter/flutter/issues/47721 where its commented: "This is unlikely to be addressed in the short term in the HTML backend. Browsers do not allow grabbing the pixels of HTML content due to CORS restrictions." If this is true, the approach of this package would not be feasible for web.

Any other ideas?

xclud commented 4 years ago

@deisold it seam a PR by @deakjahn is working and i hope it will be merge. Check this out: https://github.com/flutter/engine/pull/19655

deisold commented 4 years ago

@xclud Does this PR apply to the problematic point RenderRepaintBoundary::toImage()? https://github.com/flutter/engine/pull/19655 talks about Image.toByteData and Picture.toImage and was merged into master.

I'm currently on master and cropping (this package) is not working on web.

xclud commented 4 years ago

@xclud Does this PR apply to the problematic point RenderRepaintBoundary::toImage()? flutter/engine#19655 talks about Image.toByteData and Picture.toImage and was merged into master.

I'm currently on master and cropping (this package) is not working on web.

Oh, my mistake. Then, i don't know the answer to your question. One idea is, on web, also send pan, zoom and rotation values to the server along with original image. Then the server can crop the image. But i don't know if this works for everyone.

deisold commented 4 years ago

@xclud Does this PR apply to the problematic point RenderRepaintBoundary::toImage()? flutter/engine#19655 talks about Image.toByteData and Picture.toImage and was merged into master. I'm currently on master and cropping (this package) is not working on web.

Oh, my mistake. Then, i don't know the answer to your question. One idea is, on web, also send pan, zoom and rotation values to the server along with original image. Then the server can crop the image. But i don't know if this works for everyone.

No, worries. Thanks for answering! We already had the idea to shift the cropping to the server. I hope I can extract all necessary information from the controller.

deakjahn commented 4 years ago

That PR has actually been closed and a new one opened (see the link at the end) because GitHub handled something wrong, or I don't know what else happened, but I had to reopen a new one. This only handles three of the four possible combinations (with the focus being on CanvasKit rather than DomCanvas) and RenderPaintBoundary is not yet among those. The question already popped up and I'm unsure yet whether the current approach is enough for RPB or not. At any rate, I don't want to touch that PR any more because the whole engine is a very fast moving target right now, and even that single PR takes many weeks to land because of the constant changes of the underlying engine. When it's accepted, I could (or somebody else could) look into the RPB case, too.

ayush97123 commented 4 years ago

I am facing the same issue while cropping the image. Is there any workaround or fixes yet? I have read the above posts but did not get any clarity on if there's any fix. Thanks for the help.

xclud commented 4 years ago

@ayush97123 There is no fix yet. One solution is to get scale, rotation and offset and send theme along with your image to the server. Then do server-side cropping.

xclud commented 3 years ago

https://github.com/flutter/flutter/issues/42767 This issue will be fixed this October.

michaldev commented 3 years ago

Hi. Any changes?

xclud commented 3 years ago

@michaldev Unfortunately no progress so far.

SaadArdati commented 3 years ago

It's November dammit..

teipeps commented 3 years ago

Any forecast?

xclud commented 3 years ago

https://github.com/flutter/engine/pull/22085

I will give it a try on the weekends.

xclud commented 3 years ago

If any of you guys have free time to check, switch to master channel and try this command. FLUTTER_WEB_USE_SKIA will compile with Skia/CanvasKit.

flutter build web --dart-define=FLUTTER_WEB_USE_SKIA=true
xclud commented 3 years ago

Good news: Crop now works on web! if you enable CanvasKit. See my comment above.

xclud commented 3 years ago

https://pwa.ir/crop/ for the demo.

pitazzo commented 3 years ago

Hey @xclud, are you sure it is working on web? I may have not understand how this plugin works, but wherever I drag the mouse in the demo, the cropped area moves back to its original position.

SittiphanSittisak commented 2 years ago

Hi, I have an issue with using toImage. Unsupported operation: toImage is not supported on the Web How to fix this? Am I need to use https://pub.dev/packages/crop package? Is there only one solution?

SittiphanSittisak commented 2 years ago

My error happens when I am using

RenderRepaintBoundary? boundary;
Image? image = await boundary?.toImage();

Have any function can use instead toImage()? Can someone give me an example like this:

RenderRepaintBoundary? boundary;
Image? image = await newFunctionInsteadToImage(boundary);

Future<Image?> newFunctionInsteadToImage(RenderRepaintBoundary? boundary){
...
}
SittiphanSittisak commented 2 years ago

ref https://stackoverflow.com/questions/64583461/how-to-use-skia-canvaskit-in-flutter-web

I can use toImage when building with flutter build web --web-renderer canvaskit. But this method makes the image display very slow and has a bug https://github.com/flutter/flutter/issues/101738.

if anybody has some function that can make an Image(newFunctionInsteadToImage) with a basic flutter build, please share it.