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
256 stars 84 forks source link

Returns blacked out image #79

Closed Teutonic-Knight-0 closed 2 years ago

Teutonic-Knight-0 commented 2 years ago

Package just returns a blacked out image.

Source code:

import 'dart:typed_data';
import 'package:crop/crop.dart';
import 'package:dotted_border/dotted_border.dart';
import 'package:flutter/material.dart';
import 'package:camera/camera.dart';
import 'package:extended_image/extended_image.dart';
import 'dart:io';
import 'dart:math';
import 'package:path_provider/path_provider.dart';
import 'dart:ui' as ui;

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Camera Cropper',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  CameraController? _controller;
  late List<CameraDescription> _cameras;
  File? _capturedImage;
  final TransformationController _transformationController = TransformationController();
  final CropController _cropController = CropController();

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

  _getCameras() async {
    _cameras = await availableCameras();
    _controller = CameraController(_cameras[0], ResolutionPreset.max);
    await _controller?.initialize();
    if (mounted) {
      setState(() {});
    }
  }

  String _randomNonceString([int length = 32]) {
    final random = Random();

    final charCodes = List<int>.generate(length, (_) {
      late int codeUnit;

      switch (random.nextInt(3)) {
        case 0:
          codeUnit = random.nextInt(10) + 48;
          break;
        case 1:
          codeUnit = random.nextInt(26) + 65;
          break;
        case 2:
          codeUnit = random.nextInt(26) + 97;
          break;
      }

      return codeUnit;
    });

    return String.fromCharCodes(charCodes);
  }

  @override
  void dispose() {
    super.dispose();
    _controller?.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.black,
      body: _controller == null
          ? const LoadingIndicator()
          : Stack(
              alignment: Alignment.bottomCenter,
              children: [
                Column(
                  children: [
                    Expanded(
                      child: InteractiveViewer(
                        constrained: false,
                        transformationController: _transformationController,
                        child: SizedBox(
                          width: MediaQuery.of(context).size.width,
                          height: MediaQuery.of(context).size.height,
                          child: _capturedImage != null ? ExtendedImage.file(_capturedImage!) : CameraPreview(_controller!),
                        ),
                      ),
                    ),
                  ],
                ),
                if (_capturedImage == null) ResizableWidget(cropController: _cropController),
                Padding(
                  padding: const EdgeInsets.symmetric(vertical: 8.0),
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.spaceAround,
                    children: [
                      TextButton(
                        child: const Icon(
                          Icons.arrow_back_ios,
                          color: Colors.white,
                          size: 40,
                        ),
                        onPressed: () {
                          setState(() {
                            _capturedImage = null;
                          });
                        },
                      ),
                      GestureDetector(
                        onTap: () async {
                          // XFile image = await _controller!.takePicture();
                          // _capturedImage = File(image.path);
                          final double pixelRatio = MediaQuery.of(context).devicePixelRatio;
                          final ui.Image cropped = await _cropController.crop(pixelRatio: pixelRatio);
                          final ByteData? byteData = await cropped.toByteData(format: ui.ImageByteFormat.png);
                          final Uint8List buffer = byteData!.buffer.asUint8List();
                          final Directory dir = await getTemporaryDirectory();
                          final String path = '${dir.path}/${_randomNonceString()}.png';

                          if (!(await File(path).exists())) {
                            File imageFile = await File(path).create();
                            _capturedImage = await imageFile.writeAsBytes(buffer);
                            setState(() {});
                          } else {
                            _capturedImage = await File(path).writeAsBytes(buffer);
                            setState(() {});
                          }
                        },
                        child: const CircleAvatar(
                          radius: 30,
                          backgroundColor: Colors.white,
                        ),
                      ),
                      TextButton(
                        child: Icon(
                          _controller!.value.flashMode == FlashMode.auto
                              ? Icons.flash_auto
                              : _controller!.value.flashMode == FlashMode.off
                                  ? Icons.flash_off
                                  : Icons.flash_on,
                          color: Colors.white,
                          size: 40,
                        ),
                        onPressed: () async {
                          await _controller!.setFlashMode(_controller!.value.flashMode == FlashMode.auto
                              ? FlashMode.off
                              : _controller!.value.flashMode == FlashMode.off
                                  ? FlashMode.always
                                  : FlashMode.auto);
                          setState(() {});
                        },
                      ),
                    ],
                  ),
                )
              ],
            ),
    );
  }
}

class ResizableWidget extends StatefulWidget {
  const ResizableWidget({Key? key, required this.cropController}) : super(key: key);
  final CropController cropController;
  @override
  State<ResizableWidget> createState() => _ResizableWidgetState();
}

const ballDiameter = 30.0;

class _ResizableWidgetState extends State<ResizableWidget> {
  double height = 200;
  double width = 250;

  double top = 250;
  double left = 55;

  void onDrag(double dx, double dy) {
    var newHeight = height + dy;
    var newWidth = width + dx;

    setState(() {
      height = newHeight > 0 ? newHeight : 0;
      width = newWidth > 0 ? newWidth : 0;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: <Widget>[
        Positioned(
          top: top,
          left: left,
          child: DottedBorder(
            borderType: BorderType.RRect,
            radius: const Radius.circular(20),
            dashPattern: const [8, 8],
            color: Colors.white,
            strokeWidth: 2,
            child: SizedBox(
              height: height,
              width: width,
              child: Crop(
                controller: widget.cropController,
                shape: BoxShape.rectangle,
                backgroundColor: Colors.transparent,
                child: Container(
                  decoration: const BoxDecoration(
                    color: Colors.transparent,
                  ),
                  height: height,
                  width: width,
                ),
              ),
            ),
          ),
        ),
        // top left
        Positioned(
          top: top - ballDiameter / 2,
          left: left - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              var mid = (dx + dy) / 2;
              var newHeight = height - 2 * mid;
              var newWidth = width - 2 * mid;

              setState(() {
                height = newHeight > 0 ? newHeight : 0;
                width = newWidth > 0 ? newWidth : 0;
                top = top + mid;
                left = left + mid;
              });
            },
          ),
        ),
        // top middle
        Positioned(
          top: top - ballDiameter / 2,
          left: left + width / 2 - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              var newHeight = height - dy;

              setState(() {
                height = newHeight > 0 ? newHeight : 0;
                top = top + dy;
              });
            },
          ),
        ),
        // top right
        Positioned(
          top: top - ballDiameter / 2,
          left: left + width - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              var mid = (dx + (dy * -1)) / 2;

              var newHeight = height + 2 * mid;
              var newWidth = width + 2 * mid;

              setState(() {
                height = newHeight > 0 ? newHeight : 0;
                width = newWidth > 0 ? newWidth : 0;
                top = top - mid;
                left = left - mid;
              });
            },
          ),
        ),
        // center right
        Positioned(
          top: top + height / 2 - ballDiameter / 2,
          left: left + width - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              var newWidth = width + dx;

              setState(() {
                width = newWidth > 0 ? newWidth : 0;
              });
            },
          ),
        ),
        // bottom right
        Positioned(
          top: top + height - ballDiameter / 2,
          left: left + width - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              var mid = (dx + dy) / 2;

              var newHeight = height + 2 * mid;
              var newWidth = width + 2 * mid;

              setState(() {
                height = newHeight > 0 ? newHeight : 0;
                width = newWidth > 0 ? newWidth : 0;
                top = top - mid;
                left = left - mid;
              });
            },
          ),
        ),
        // bottom center
        Positioned(
          top: top + height - ballDiameter / 2,
          left: left + width / 2 - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              var newHeight = height + dy;

              setState(() {
                height = newHeight > 0 ? newHeight : 0;
              });
            },
          ),
        ),
        // bottom left
        Positioned(
          top: top + height - ballDiameter / 2,
          left: left - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              var mid = ((dx * -1) + dy) / 2;

              var newHeight = height + 2 * mid;
              var newWidth = width + 2 * mid;

              setState(() {
                height = newHeight > 0 ? newHeight : 0;
                width = newWidth > 0 ? newWidth : 0;
                top = top - mid;
                left = left - mid;
              });
            },
          ),
        ),
        //left center
        Positioned(
          top: top + height / 2 - ballDiameter / 2,
          left: left - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              var newWidth = width - dx;

              setState(() {
                width = newWidth > 0 ? newWidth : 0;
                left = left + dx;
              });
            },
          ),
        ),
        // center center
        Positioned(
          top: top + height / 2 - ballDiameter / 2,
          left: left + width / 2 - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              setState(() {
                top = top + dy;
                left = left + dx;
              });
            },
          ),
        ),
      ],
    );
  }
}

class ManipulatingBall extends StatefulWidget {
  const ManipulatingBall({Key? key, required this.onDrag}) : super(key: key);

  final Function onDrag;

  @override
  State<ManipulatingBall> createState() => _ManipulatingBallState();
}

class _ManipulatingBallState extends State<ManipulatingBall> {
  double _initX = 0;
  double _initY = 0;

  _handleDrag(details) {
    setState(() {
      _initX = details.globalPosition.dx;
      _initY = details.globalPosition.dy;
    });
  }

  _handleUpdate(details) {
    var dx = details.globalPosition.dx - _initX;
    var dy = details.globalPosition.dy - _initY;
    _initX = details.globalPosition.dx;
    _initY = details.globalPosition.dy;
    widget.onDrag(dx, dy);
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onPanStart: _handleDrag,
      onPanUpdate: _handleUpdate,
      child: Container(
        width: ballDiameter,
        height: ballDiameter,
        decoration: BoxDecoration(
          color: Colors.pink.withOpacity(0.5),
          shape: BoxShape.circle,
        ),
      ),
    );
  }
}

class LoadingIndicator extends StatelessWidget {
  const LoadingIndicator({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const Center(
      child: CircularProgressIndicator(color: Colors.pink),
    );
  }
}

Pubspec:

name: camera_cropper
description: A new Flutter project.
publish_to: 'none'

version: 1.0.0+1

environment:
  sdk: '>=2.18.0-130.0.dev <3.0.0'

dependencies:
  flutter:
    sdk: flutter

  camera: ^0.9.6
  image: ^3.2.0
  extended_image: ^6.2.1
  dotted_border: ^2.0.0+2
  crop: ^0.5.2

dev_dependencies:
  flutter_test:
    sdk: flutter

  flutter_lints: ^2.0.1

flutter:
  uses-material-design: true

Flutter doctor:

Running flutter doctor... Doctor summary (to see all details, run flutter doctor -v): [✓] Flutter (Channel master, 3.1.0-0.0.pre.821, on macOS 12.3.1 21E258 darwin-arm, locale en-ZA) [✓] Android toolchain - develop for Android devices (Android SDK version 32.1.0-rc1) [✓] Xcode - develop for iOS and macOS (Xcode 13.4) [✓] Chrome - develop for the web [✓] Android Studio (version 2021.1) [✓] Android Studio (version 2021.1) [✓] IntelliJ IDEA Ultimate Edition (version 2022.1.1) [✓] IntelliJ IDEA Ultimate Edition (version 2022.1.1) [✓] IntelliJ IDEA Ultimate Edition (version 2022.1) [✓] Connected device (3 available) [✓] HTTP Host Availability

• No issues found!

xclud commented 2 years ago

Are you using Flutter 3.0? There is an issue with it. Check:

78

https://github.com/flutter/flutter/issues/103803

xclud commented 2 years ago

This is fixed in the latest Flutter version on master channel. I am closing this issue but please open a new issue anytime.