如何在flutter中不使用任何包的情况下裁剪资源图像

问题描述 投票:0回答:1

我有一个资产图像,在这个图像上有一个容器,当单击保存按钮时我必须裁剪图像并将裁剪图像显示到下一个屏幕。我尝试这段代码,但是当单击“保存”按钮时显示错误。在flutter中没有任何包的情况下如何解决这个问题?

import 'dart:async';
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: HomeScreen(),
    );
  }
}

class HomeScreen extends StatefulWidget {
  @override
  _HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  GlobalKey globalKey = GlobalKey();

  Offset framePosition = Offset(100, 100); // Initial position of the frame container
  double frameSize = 150; // Initial size of the frame container
  String backgroundImage = 'images/rose1.png'; // Background image path

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Home Screen"),
      ),
      body: Stack(
        children: [
          // Background image
          Image.asset(
            backgroundImage,
            fit: BoxFit.cover,
            width: MediaQuery.of(context).size.width,
            height: MediaQuery.of(context).size.height,
          ),
          // Frame container
          Positioned(
            left: framePosition.dx,
            top: framePosition.dy,
            child: GestureDetector(
              onPanUpdate: (details) {
                setState(() {
                  framePosition += details.delta;
                });
              },
              child: RepaintBoundary(
                key: globalKey,
                child: Container(
                  width: frameSize,
                  height: frameSize,
                  decoration: BoxDecoration(
                    border: Border.all(color: Colors.red, width: 2.0),
                  ),
                ),
              ),
            ),
          ),
          // Save button
          Positioned(
            bottom: 20,
            right: 20,
            child: ElevatedButton(
              onPressed: () => _cropAndNavigate(context),
              child: Text("Save and Next"),
            ),
          ),
        ],
      ),
    );
  }

  Future<void> _cropAndNavigate(BuildContext context) async {
    // Capture the image within the frame container
    ui.Image image = await _captureImage(globalKey.currentContext!);

    // Get frame container position and size relative to the background image
    RenderBox containerBox = globalKey.currentContext!.findRenderObject() as RenderBox;
    Offset containerPosition = containerBox.localToGlobal(Offset.zero);
    double containerWidth = containerBox.size.width;
    double containerHeight = containerBox.size.height;

    // Calculate the crop coordinates relative to the background image
    double left = (framePosition.dx - containerPosition.dx) * 2;
    double top = (framePosition.dy - containerPosition.dy) * 2;
    double width = frameSize * 2;
    double height = frameSize * 2;

    // Crop the image
    Uint8List croppedImage = await _cropImage(image, left.toInt(), top.toInt(), width.toInt(), height.toInt());

    // Navigate to the next screen and pass the cropped image
    Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) => NextScreen(croppedImage: croppedImage),
      ),
    );
  }

  Future<ui.Image> _captureImage(BuildContext context) async {
    RenderRepaintBoundary boundary = context.findRenderObject() as RenderRepaintBoundary;
    ui.Image image = await boundary.toImage(pixelRatio: 2.0);
    return image;
  }

  Future<Uint8List> _cropImage(ui.Image image, int left, int top, int width, int height) async {
    final ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png);
    final Uint8List pngBytes = byteData!.buffer.asUint8List();

    // Crop the image
    // Here, you should implement your cropping logic based on the given coordinates
    // For simplicity, I'll just create a copy of the original image
    Uint8List croppedImage = Uint8List.fromList(pngBytes.sublist(left, left + width));

    return croppedImage;
  }
}

class NextScreen extends StatelessWidget {
  final Uint8List croppedImage;

  NextScreen({required this.croppedImage});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Next Screen"),
      ),
      body: Center(
        child: Image.memory(
          croppedImage,
          fit: BoxFit.cover,
        ),
      ),
    );
  }
}

我有一个资产图像,在这个图像上有一个容器,当单击保存按钮时我必须裁剪图像并将裁剪图像显示到下一个屏幕。我尝试这段代码,但是当单击“保存”按钮时显示错误。在flutter中没有任何包的情况下如何解决这个问题?

flutter dart crop flutter-container
1个回答
0
投票

要使用偏移量裁剪

Widget
,您可以使用
CustomClipper
SizedOverflowBox
,只需使用一些数学计算对齐即可。 要保存图像,小部件需要位于
RepaintBoundary
下面。如果您想显示其余图像,只需在堆栈中绘制两次即可。

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:ui' as ui;

final paintKey = GlobalKey();

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

  @override
  State<Crop> createState() => _CropState();
}

class _CropState extends State<Crop> {
  int imageName = 0;
  List<File> files = [];
  double dx = 25;
  double dy = 25;
  double width = 100;
  double height = 100;
  @override
  Widget build(BuildContext context) {
    final imageWidget = FlutterLogo(size: 200);
    return Scaffold(
      appBar: AppBar(
        title: const Text('Crop'),
      ),
      body: ListView(
        children: [
          ImageView(
              imageWidget: imageWidget,
              dx: dx,
              dy: dy,
              width: width,
              height: height,
              originSize: Size(200, 200)),
          Slider(
              value: dx,
              onChanged: (value) => setState(() => dx = value),
              max: 100),
          Slider(
              value: dy,
              onChanged: (value) => setState(() => dy = value),
              max: 100),
          Slider(
              value: width,
              onChanged: (value) => setState(() => width = value),
              max: 100),
          Slider(
              value: height,
              onChanged: (value) => setState(() => height = value),
              max: 100),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () async {
          final boundary = paintKey.currentContext!.findRenderObject()
              as RenderRepaintBoundary;
          final image = await boundary.toImage();
          final byteData =
              await image.toByteData(format: ui.ImageByteFormat.png);
          final pngBytes = byteData!.buffer.asUint8List();

          final dir = await getApplicationDocumentsDirectory();
          final file = File('${dir.path}/image$imageName.png');
          await file.writeAsBytes(pngBytes);
          files.add(file);
          imageName += 1;
          if (context.mounted) {
            Navigator.of(context).push(MaterialPageRoute(
              builder: (context) => Scaffold(
                appBar: AppBar(
                  title: const Text('Crop'),
                ),
                body: Center(
                  child: GridView.builder(
                    gridDelegate:
                        const SliverGridDelegateWithFixedCrossAxisCount(
                      crossAxisCount: 4,
                    ),
                    itemCount: files.length,
                    itemBuilder: (context, index) {
                      return Image.file(files[index]);
                    },
                  ),
                ),
              ),
            ));
          }
          // Save the image to the device.
        },
        child: const Icon(Icons.save),
      ),
    );
  }
}

class ImageView extends StatelessWidget {
  const ImageView({
    super.key,
    required this.imageWidget,
    required this.dx,
    required this.dy,
    required this.width,
    required this.height,
    required this.originSize,
  });

  final FlutterLogo imageWidget;
  final double dx;
  final double dy;
  final double width;
  final double height;
  final Size originSize;

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        imageWidget,
        Positioned(
          left: dx,
          top: dy,
          child: Container(
            foregroundDecoration: BoxDecoration(
              border: Border.all(
                color: Colors.red,
                width: 2,
              ),
            ),
            child: ImageCliper(
                rect: Rect.fromLTWH(dx, dy, width, height),
                size: originSize,
                child: imageWidget),
          ),
        ),
      ],
    );
  }
}

class ImageCliper extends StatelessWidget {
  const ImageCliper({
    super.key,
    required this.child,
    required this.rect,
    required this.size,
  });
  final Widget child;
  final Rect rect;
  final Size size;

  @override
  Widget build(BuildContext context) {
    assert(Rect.fromLTWH(0, 0, size.width, size.height)
        .contains(rect.bottomRight));
    assert(Rect.fromLTWH(0, 0, size.width, size.height).contains(rect.topLeft));

    final widthLeft = size.width - rect.width;
    final heightLeft = size.height - rect.height;
    final widthAlignment = rect.left / widthLeft * 2 - 1;
    final heightAlignment = rect.top / heightLeft * 2 - 1;

    return RepaintBoundary(
      key: paintKey,
      child: SizedOverflowBox(
        size: Size(rect.width, rect.height),
        alignment: Alignment(widthAlignment, heightAlignment),
        child: ClipRect(
          clipper: MyCliper(rect),
          child: child,
        ),
      ),
    );
  }
}

class MyCliper extends CustomClipper<Rect> {
  MyCliper(this.rect);
  final Rect rect;
  @override
  Rect getClip(Size size) {
    return rect;
  }

  @override
  bool shouldReclip(covariant MyCliper oldClipper) {
    return oldClipper.rect != rect;
  }
}
© www.soinside.com 2019 - 2024. All rights reserved.