hm21 / pro_image_editor

The pro_image_editor is a Flutter widget designed for image editing within your application. It provides a flexible and convenient way to integrate image editing capabilities into your Flutter project.
https://hm21.github.io/pro_image_editor/
BSD 3-Clause "New" or "Revised" License
94 stars 59 forks source link

[Bug]: Reopening editor from history with sticker, severely crops the sticker #171

Closed efalco777 closed 1 month ago

efalco777 commented 1 month ago

Package Version

4.3.0

Flutter Version

3.22.2

Platforms

Android, iOS

How to reproduce?

(using example code I provided) Steps:

  1. Open editor
  2. Drop a sticker on the image
  3. Submit closing the editor & saving the history
  4. Restore the image editor with the same image using the previous history

Expected: Sticker is in the same place with the same size

Actual: Sticker is in the same place but cropped vertically.

Video: https://github.com/user-attachments/assets/a0f925dc-1b2c-4b44-9d36-b3eb00bc334b

Logs (optional)

No response

Example code (optional)

class _FileEditor extends StatefulWidget {
  const _FileEditor({
    required this.file,
  });

  final File file;

  @override
  State<_FileEditor> createState() => _FileEditorState();
}

class _FileEditorState extends State<_FileEditor> {
  late GlobalKey<ProImageEditorState> _key;
  String? _historyJson;

  Uint8List? _editedPhotoBytes;
  bool _showEditor = true;

  bool _stickerEditor2 = false;

  @override
  void initState() {
    super.initState();
    _key = GlobalKey<ProImageEditorState>();
  }

  @override
  Widget build(BuildContext context) {
    return switch (_showEditor) {
      true => Stack(
          children: [
            ProImageEditor.file(
              widget.file,
              key: _key,
              callbacks: ProImageEditorCallbacks(
                onImageEditingComplete: onImageEditingComplete,
              ),
              configs: ProImageEditorConfigs(
                emojiEditorConfigs: const EmojiEditorConfigs(initScale: 3, maxScale: 8),
                stickerEditorConfigs: StickerEditorConfigs(
                  enabled: true,
                  buildStickers: (setLayer, scrollController) {
                    if (_stickerEditor2) {
                      return SizedBox(
                        height: 200,
                        width: double.infinity,
                        child: ElevatedButton(
                          child: const Text('Create blue sticker'),
                          onPressed: () {
                            final sticker = Container(
                              color: Colors.blue,
                              width: 100,
                              height: 50,
                            );
                            setLayer(sticker);
                          },
                        ),
                      );
                    }

                    return SizedBox(
                      height: 200,
                      width: double.infinity,
                      child: ElevatedButton(
                        child: const Text('Create red sticker'),
                        onPressed: () {
                          final sticker = Container(
                            color: Colors.red,
                            width: 50,
                            height: 100,
                          );
                          setLayer(sticker);
                        },
                      ),
                    );
                  },
                ),
                stateHistoryConfigs: StateHistoryConfigs(
                  initStateHistory: _historyJson != null ? ImportStateHistory.fromJson(_historyJson!) : null,
                ),
              ),
            ),
            Positioned(
              left: 10,
              bottom: 100,
              child: TextButton(
                onPressed: () => setState(() => _stickerEditor2 = !_stickerEditor2),
                child: Text('Swap'),
              ),
            ),
          ],
        ),
      false => Scaffold(
          body: Column(
            children: [
              SizedBox(
                height: 400,
                child: Image.memory(_editedPhotoBytes!),
              ),
              const Divider(),
              ElevatedButton(
                onPressed: () => _openEditor(),
                child: const Text('Reopen editor with the history'),
              )
            ],
          ),
        ),
    };
  }

  Future<void> onImageEditingComplete(bytes) async {
    final exportState = await _key.currentState?.exportStateHistory();
    _closeEditor(await exportState?.toJson(), bytes);
  }

  void _closeEditor(String? historyJson, Uint8List? bytes) {
    setState(() {
      _showEditor = false;
      _historyJson = historyJson;
      _editedPhotoBytes = bytes;
    });
  }

  void _openEditor() {
    print('Reopening with: ' + (_historyJson?.toString() ?? 'empty'));
    setState(() {
      _key = GlobalKey<ProImageEditorState>();
      _showEditor = true;
    });
  }
}

Device Model (optional)

iPhone 15

hm21 commented 1 month ago

Thanks for reporting this issue with all the details.

It appears that problem happens because the export targetSize from the sticker isn't correct. Actually, I thought I just make sure the pixelratio will be correct and let the sticker fit to that box size again after we imported it, but seems like that didn't work as wanted. However, I replaced the part here with this part:

Size targetSize = Size(imageWidth, imageWidth);

I used your sample code and in my case everything is working fine now. The problem is that I don't have enough time to test it if it reduces the quality of a sticker when it is an image, so in the case you have time, I would appreciate it if you can test it if everything works fine by you. After your feedback or after I find time to test it by myself and everything is okay, I will release it.

github-actions[bot] commented 1 month ago

This issue is stale because it has been open for 14 days with no activity.

github-actions[bot] commented 1 month ago

This issue was closed because it has been inactive for 5 days since being marked as stale.