Flutter -如何让用户擦除图像的一部分并使其透明?

wlwcrazw  于 2022-11-25  发布在  Flutter
关注(0)|答案(2)|浏览(328)

我的目标是让用户选择图像(通过image_picker),然后使用橡皮擦类型的工具来修改图像,使其透明。

vdgimpew

vdgimpew1#

你需要做的并不是很直接,但是你可以尝试一下image package,用它可以做很多事情。

ru9i0ody

ru9i0ody2#

简单示例:

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:image_edit/image_edit_screen.dart';
import 'package:image_edit/image_processor.dart';
import 'dart:ui' as ui;

void main() => runApp(
      ProviderScope(
        child: MaterialApp(
          debugShowCheckedModeBanner: false,
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: MyApp(),
        ),
      ),
    );

class MyApp extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => MyAppState();
}

class MyAppState extends State<MyApp> {
  @override
  void initState() {
    super.initState();
  }

  _enterImageEditController() async {
    final imageBytes =
        await ImageProcessor.getImageBytesAsset('lib/basic_0_people.png');
    final bgImageBytes =
        await ImageProcessor.getImageBytesAsset('lib/eraser_bg.jpg');
    ui.decodeImageFromList(imageBytes, (result) async {
      ui.decodeImageFromList(bgImageBytes, (bgResult) async {
        Navigator.of(context).push(
          MaterialPageRoute<void>(
            builder: (BuildContext ctx) => ImageEditScreen(
              imageBytes: imageBytes,
              bgImageBytes: bgImageBytes,
              image: result,
              bgImage: bgResult,
            ),
          ),
        );
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Color.fromARGB(255, 56, 66, 66),
      floatingActionButtonLocation: FloatingActionButtonLocation.centerTop,
      floatingActionButton: Wrap(
        direction: Axis.horizontal,
        children: [
          Center(
            child: ElevatedButton(
              onPressed: () {
                _enterImageEditController();
              },
              child: Text(
                'ImageEdit Controller',
                style: TextStyle(fontSize: 25),
              ),
            ),
          ),
        ],
      ),
    );
  }
}
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

class ImageProcessor {
  static Future<Uint8List> getImageBytesAsset(String path) async {
    WidgetsFlutterBinding.ensureInitialized();
    final byteData = await rootBundle.load(path);
    final uint8List = Uint8List.view(byteData.buffer);
    return uint8List;
  }
}
import 'dart:async';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'dart:ui' as ui;
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:image_edit/image_edit_service.dart';

class ImageEditScreen extends StatelessWidget {
  final Uint8List imageBytes;
  final Uint8List bgImageBytes;
  final ui.Image image;
  final ui.Image bgImage;

  ImageEditScreen({
    Key? key,
    required this.imageBytes,
    required this.bgImageBytes,
    required this.image,
    required this.bgImage,
  }) : super(key: key);

  GlobalKey bgImageKey = GlobalKey();
  GlobalKey imageKey = GlobalKey();
  GlobalKey bgImageEraserKey = GlobalKey();
  GlobalKey imageEraserKey = GlobalKey();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.cyanAccent,
      floatingActionButtonLocation: FloatingActionButtonLocation.centerTop,
      floatingActionButton: Wrap(
        direction: Axis.horizontal,
        children: [
          Center(
            child: FittedBox(
              child: SizedBox(
                width: image.width.toDouble(),
                height: image.height.toDouble(),
                child: Consumer(
                  builder: (context, ref, child) {
                    return GestureDetector(
                      onPanStart: (details) {
                        final imageEditService =
                            ref.read(imageEditProvider.notifier);
                        imageEditService.startEdit(details.localPosition);
                      },
                      onPanUpdate: (details) {
                        final imageEditService =
                            ref.read(imageEditProvider.notifier);
                        imageEditService.updateEdit(details.localPosition);
                      },
                      child: Container(
                        color: Colors.transparent,
                        child: Stack(
                          children: [
                            Positioned.fill(
                              child: RepaintBoundary(
                                key: bgImageKey,
                                child: ImageEditPaint(
                                  canvasPaths: [],
                                  image: bgImage,
                                ),
                              ),
                            ),
                            Positioned.fill(
                              child: Consumer(
                                builder: (context, ref, child) {
                                  final imageEditState =
                                      ref.watch(imageEditProvider);
                                  return RepaintBoundary(
                                    key: imageEraserKey,
                                    child: ImageEditPaint(
                                      canvasPaths: imageEditState.eraserPath,
                                      image: image,
                                    ),
                                  );
                                },
                              ),
                            ),
                          ],
                        ),
                      ),
                    );
                  },
                ),
              ),
            ),
          ),
          Center(
            child: Consumer(
              builder: (context, ref, child) {
                return ElevatedButton(
                  child: Text(
                    'undo',
                    style: TextStyle(
                      fontSize: 25,
                    ),
                  ),
                  onPressed: () {
                    final imageEditService =
                        ref.read(imageEditProvider.notifier);
                    imageEditService.undo();
                  },
                );
              },
            ),
          ),
        ],
      ),
    );
  }

  Future<Uint8List> takeScreenShot(GlobalKey screenshotKey) async {
    RenderRepaintBoundary boundary = screenshotKey.currentContext!
        .findRenderObject() as RenderRepaintBoundary;
    ui.Image image = await boundary.toImage();
    ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png);
    Uint8List pngBytes = byteData!.buffer.asUint8List();
    return pngBytes;
  }
}

class ImageEditPaint extends StatelessWidget {
  final List<PlaygroundEraserCanvasPath> canvasPaths;
  final ui.Image image;

  const ImageEditPaint({
    Key? key,
    required this.canvasPaths,
    required this.image,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return CustomPaint(
      isComplex: true,
      willChange: true,
      foregroundPainter: EraserPainter(
        canvasPaths: canvasPaths,
        image: image,
      ),
    );
  }
}

class EraserPainter extends CustomPainter {
  final List<PlaygroundEraserCanvasPath> canvasPaths;
  final ui.Image image;

  EraserPainter({
    required this.canvasPaths,
    required this.image,
  });

  @override
  void paint(Canvas canvas, Size size) {
    canvas.saveLayer(Rect.fromLTWH(0, 0, size.width, size.height), Paint());
    canvas.drawImage(
      image,
      Offset.zero,
      Paint()..filterQuality = FilterQuality.high,
    );
    if (canvasPaths.isNotEmpty) {
      for (var canvasPath in canvasPaths) {
        if (canvasPath.drawPoints.isNotEmpty) {
          var eraserPaint = Paint()
            ..strokeWidth = 50
            ..style = PaintingStyle.stroke
            ..strokeCap = StrokeCap.round
            ..strokeJoin = StrokeJoin.round
            ..blendMode = BlendMode.clear;
          for (int i = 0; i < canvasPath.drawPoints.length; i++) {
            Offset drawPoint = canvasPath.drawPoints[i];
            if (canvasPath.drawPoints.length > 1) {
              if (i == 0) {
                canvas.drawLine(drawPoint, drawPoint, eraserPaint);
              } else {
                canvas.drawLine(
                    canvasPath.drawPoints[i - 1], drawPoint, eraserPaint);
              }
            } else {
              canvas.drawLine(drawPoint, drawPoint, eraserPaint);
            }
          }
        }
      }
    }
    canvas.restore();
  }

  @override
  bool shouldRepaint(covariant EraserPainter oldDelegate) => true;
}
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

final imageEditProvider =
    StateNotifierProvider.autoDispose<ImageEditService, ImageEditState>((ref) {
  return ImageEditService();
});

class ImageEditService extends StateNotifier<ImageEditState> {
  ImageEditService()
      : super(ImageEditState(
            editType: EditType.eraser, eraserPath: [], paintPath: []));

  PlaygroundEraserCanvasPath _currentPath =
      PlaygroundEraserCanvasPath(drawPoints: []);

  void startEdit(Offset position) {
    _currentPath = PlaygroundEraserCanvasPath(drawPoints: [position]);
    if (state.editType == EditType.eraser) {
      _editingHistory.add(EditType.eraser);
      List<PlaygroundEraserCanvasPath> tempList = List.from(state.eraserPath);
      tempList.add(_currentPath);
      state = state.copyWith(eraserPath: tempList);
    } else {
      _editingHistory.add(EditType.paint);
      List<PlaygroundEraserCanvasPath> tempList = List.from(state.paintPath);
      tempList.add(_currentPath);
      state = state.copyWith(paintPath: tempList);
    }
  }

  void updateEdit(Offset position) {
    _currentPath.drawPoints.add(position);
    if (state.editType == EditType.eraser) {
      List<PlaygroundEraserCanvasPath> tempList = List.from(state.eraserPath);
      tempList.last = _currentPath;
      state = state.copyWith(eraserPath: tempList);
    } else {
      List<PlaygroundEraserCanvasPath> tempList = List.from(state.paintPath);
      tempList.last = _currentPath;
      state = state.copyWith(paintPath: tempList);
    }
  }

  List<EditType> _editingHistory = [];

  void undo() {
    if (_editingHistory.isEmpty) return;
    final historyLast = _editingHistory.last;
    if (historyLast == EditType.eraser) {
      List<PlaygroundEraserCanvasPath> tempList = List.from(state.eraserPath);
      tempList.removeLast();
      state = state.copyWith(eraserPath: tempList);
    } else {
      List<PlaygroundEraserCanvasPath> tempList = List.from(state.paintPath);
      tempList.removeLast();
      state = state.copyWith(paintPath: tempList);
    }
    _editingHistory.removeLast();
  }

  void updateEditType() {
    state = state.copyWith(
      editType:
          state.editType == EditType.eraser ? EditType.paint : EditType.eraser,
    );
  }
}

class PlaygroundEraserCanvasPath {
  final List<Offset> drawPoints;

  PlaygroundEraserCanvasPath({
    required this.drawPoints,
  });
}

@immutable
class ImageEditState {
  EditType editType;
  List<PlaygroundEraserCanvasPath> eraserPath;
  List<PlaygroundEraserCanvasPath> paintPath;

  ImageEditState({
    required this.editType,
    required this.eraserPath,
    required this.paintPath,
  });

  ImageEditState copyWith({
    EditType? editType,
    List<PlaygroundEraserCanvasPath>? eraserPath,
    List<PlaygroundEraserCanvasPath>? paintPath,
  }) {
    return ImageEditState(
      editType: editType ?? this.editType,
      eraserPath: eraserPath ?? this.eraserPath,
      paintPath: paintPath ?? this.paintPath,
    );
  }
}

enum EditType {
  eraser,
  paint,
}

image example

相关问题