chooyan-eng / crop_your_image

A flutter plugin which provides Crop Widget for cropping images.
https://pub.dev/packages/crop_your_image
Apache License 2.0
156 stars 136 forks source link

tempImage being null cause a fatal exception #144

Open gmarizy opened 6 months ago

gmarizy commented 6 months ago

Version used: 1.0.2.

Stack trace: Fatal Exception: io.flutter.plugins.firebase.crashlytics.FlutterError: Null check operator used on a null value

00 pc 0x5d3ffb party.mojito (imageImageParser. [image_image_parser.dart:27]) (BuildId: b866ace14c967e51f7dae5b7c6ff4765)

01 pc 0x5a10ab party.mojito (_parseFunc [crop.dart:751]) (BuildId: b866ace14c967e51f7dae5b7c6ff4765)

02 pc 0x5a0f53 party.mojito (_parseFunc [crop.dart:748]) (BuildId: b866ace14c967e51f7dae5b7c6ff4765)

...

chooyan-eng commented 6 months ago

@gmarizy Thank you for your feedback. It seems parsing the original image of Uint8List fails, but can you provide a minimum snippet that reproduces the issue?

gmarizy commented 6 months ago

It happened in production. Occurrence is not systematic, rather rare in fact. I tried a few things but didn't succeeded to reproduce it.

chooyan-eng commented 6 months ago

@gmarizy Can you provide info at least about what argument of Crop is used, and the original image is possible?

gmarizy commented 6 months ago

I use a very UI-tweaked version of Crop (brace yourself):

class InteractiveCrop extends StatelessWidget {
  final Uint8List image;
  final double aspectRatio;
  final CropController controller;
  final ValueChanged<Uint8List> onCropped;
  final ValueChanged<CropStatus>? onStatusChanged;

  const InteractiveCrop({
    super.key,
    required this.image,
    this.aspectRatio = 9 / 16,
    required this.controller,
    required this.onCropped,
    this.onStatusChanged,
  });

  @override
  Widget build(BuildContext context) => LayoutBuilder(
        builder: (context, constraints) {
          final cropZone = _getCropZone(constraints.biggest);
          final roundedCropZone = RRect.fromRectAndRadius(
            cropZone,
            const Radius.circular(Dimens.radiusM),
          );

          return Stack(
            children: [
              Crop(
                initialRectBuilder: (viewportRect, imageRect) => cropZone,
                cornerDotBuilder: (_, __) => const SizedBox.shrink(),
                interactive: true,
                fixCropRect: true,
                maskColor: Colors.transparent,
                baseColor: Theme.of(context).colorScheme.background,
                controller: controller,
                image: image,
                onCropped: onCropped,
                onStatusChanged: onStatusChanged,
              ),
              IgnorePointer(child: _RevealOverlay(roundedCropZone)),
              IgnorePointer(
                child: Container(
                  margin:
                      EdgeInsets.only(left: cropZone.left, top: cropZone.top),
                  width: cropZone.width,
                  height: cropZone.height,
                  decoration: BoxDecoration(
                    border: Border.all(
                      color: Colors.white,
                      width: 4,
                    ),
                    borderRadius: roundedCornerBorderRadius,
                  ),
                ),
              )
            ],
          );
        },
      );

  Rect _getCropZone(Size size) {
    final rect = Rect.fromLTWH(
      0,
      0,
      size.width,
      size.height,
    );

    final maxWidth = rect.width * 2 / 3;
    final maxHeight = rect.height * 2 / 3;

    if (maxWidth / aspectRatio < maxHeight) {
      return Rect.fromCenter(
        center: rect.center,
        width: maxWidth,
        height: maxWidth / aspectRatio,
      );
    } else {
      return Rect.fromCenter(
        center: rect.center,
        width: maxHeight * aspectRatio,
        height: maxWidth,
      );
    }
  }
}

// https://www.flutterclutter.dev/flutter/tutorials/how-to-cut-a-hole-in-an-overlay/2020/510/
class _RevealOverlay extends StatelessWidget {
  final RRect _revealed;

  const _RevealOverlay(this._revealed);

  @override
  Widget build(BuildContext context) => ClipPath(
        clipper: _RevealClipper(_revealed),
        child: Container(
          color: MojitoColors.intenseDarkVeil,
        ),
      );
}

class _RevealClipper extends CustomClipper<Path> {
  const _RevealClipper(this._revealed);

  final RRect _revealed;

  @override
  Path getClip(Size size) => Path()
    ..addRect(Rect.fromLTWH(0, 0, size.width, size.height))
    ..addRRect(_revealed)
    ..fillType = PathFillType.evenOdd;

  @override
  bool shouldReclip(CustomClipper<Path> oldClipper) => true;
}

I don't have the original image sadly; I use this lib to crop images before uploading them to my server.

chooyan-eng commented 6 months ago

@gmarizy Thanks! I'll try to reproduce with this code first.

gmarizy commented 6 months ago

@chooyan-eng I found a way to reproduce this error; when I try to crop an image from an .heic source on an Android device, this error is systematic.

chooyan-eng commented 6 months ago

@gmarizy Thanks! I'll try that.

gmarizy commented 6 months ago

@chooyan-eng Did you succeed to reproduce the bug with .heic on Android device ?