oxean_image_editor 0.2.0 copy "oxean_image_editor: ^0.2.0" to clipboard
oxean_image_editor: ^0.2.0 copied to clipboard

A simple image editor for Flutter, allowing users to crop, rotate, flip and drawing

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'dart:io';
import 'dart:ui' as ui;
import 'dart:typed_data';
import 'package:file_picker/file_picker.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:oxean_image_editor/oxean_image_editor.dart';
import 'package:flutter/foundation.dart' show kIsWeb;

// Import dart:html conditionally for web platform
import 'web_helper.dart' if (dart.library.html) 'web_helper_web.dart';

class PageWidget extends StatefulWidget {
  PageWidget({super.key});

  @override
  _PageWidgetState createState() => _PageWidgetState();
}

class _PageWidgetState extends State<PageWidget> {
  late OxeanImageController _imageController;
  File? _imageFileToEdit;
  bool _isSaving = false;

  @override
  void initState() {
    super.initState();
    _imageController = OxeanImageController();
    _loadImageAsset();
    _requestPermissions();
  }

  Future<void> _requestPermissions() async {
    if (!kIsWeb) {
      // Request storage permissions (only for mobile platforms)
      Map<Permission, PermissionStatus> statuses = await [
        Permission.storage,
        Permission.photos,
      ].request();

      debugPrint('Permission statuses: $statuses');
    }
  }

  Future<void> _loadImageAsset() async {
    if (kIsWeb) {
      // For web, we'll use a different approach
      final byteData = await rootBundle.load('assets/images/clusia.jpeg');
      final bytes = byteData.buffer.asUint8List();

      // Create a memory image and pass it to the controller directly
      final codec = await ui.instantiateImageCodec(bytes);
      final frameInfo = await codec.getNextFrame();
      final image = frameInfo.image;

      // Initialize the controller with the image
      _imageController.loadImageFromMemory(image);

      setState(() {
        // We don't need _imageFileToEdit for web
        // but we'll set it to non-null to satisfy the UI logic
        _imageFileToEdit = File('dummy_path');
      });
    } else {
      // For mobile/desktop platforms
      final byteData = await rootBundle.load('assets/images/clusia.jpeg');
      final tempDir = await getTemporaryDirectory();
      final file = File('${tempDir.path}/clusia.jpeg');
      await file.writeAsBytes(byteData.buffer.asUint8List());

      setState(() {
        _imageFileToEdit = file;
      });
    }
  }

  Future<void> _pickImage() async {
    try {
      // Use a more direct approach without compression
      FilePickerResult? result = await FilePicker.platform.pickFiles(
        type: FileType.image,
        allowCompression: false,
        withData: kIsWeb, // Get bytes for web
      );

      if (result != null) {
        if (kIsWeb) {
          // Web platform handling
          final bytes = result.files.single.bytes;
          if (bytes != null) {
            // Create a memory image and pass it to the controller directly
            final codec = await ui.instantiateImageCodec(bytes);
            final frameInfo = await codec.getNextFrame();
            final image = frameInfo.image;

            // Initialize a new controller with the image
            setState(() {
              _imageController = OxeanImageController();
              _imageController.loadImageFromMemory(image);
              _imageFileToEdit = File('dummy_path'); // Dummy path for web
            });
          }
        } else {
          // Mobile/desktop platform handling
          if (result.files.single.path != null) {
            final path = result.files.single.path!;
            debugPrint('Selected image path: $path');

            // Check if file exists and is readable
            final file = File(path);
            if (await file.exists()) {
              setState(() {
                _imageFileToEdit = file;
                _imageController = OxeanImageController();
              });
            } else {
              throw Exception('Selected file does not exist or is not accessible');
            }
          }
        }
      }
    } catch (e) {
      debugPrint('Error picking image: $e');
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text('Error picking image: $e'),
            duration: const Duration(seconds: 5),
          ),
        );
      }
    }
  }

  @override
  void dispose() {
    _imageController.dispose();
    super.dispose();
  }

  Future<void> _saveImage(BuildContext scaffoldContext) async {
    if (_isSaving) return;
    setState(() => _isSaving = true);

    ui.Image? editedImage;

    try {
      editedImage = await _imageController.exportCroppedAndTransformedImage();

      if (editedImage == null) {
        debugPrint("SaveImage: No edited image to save or error in getCroppedAndTransformedImage.");
        if (mounted) {
          ScaffoldMessenger.of(scaffoldContext).showSnackBar(
            const SnackBar(content: Text('Could not process image for saving.')),
          );
        }
        return;
      }

      final ByteData? byteData = await editedImage.toByteData(format: ui.ImageByteFormat.png);

      if (byteData == null) {
        debugPrint("SaveImage: Failed to get byte data from image.");
        if (mounted) {
          ScaffoldMessenger.of(scaffoldContext).showSnackBar(
            const SnackBar(content: Text('Failed to convert image to bytes.')),
          );
        }
        return;
      }

      final Uint8List bytes = byteData.buffer.asUint8List();

      if (kIsWeb) {
        ui.Image? newImageFromByteData = await ui.instantiateImageCodec(bytes).then((codec) => codec.getNextFrame().then((frameInfo) => frameInfo.image));
        // For web, we'll use the browser's download capability
        final fileName = 'edited_${DateTime.now().millisecondsSinceEpoch}.png';
        WebHelper.downloadBytes(bytes, fileName);
        _imageController.loadImageFromMemory(newImageFromByteData!);

        if (mounted && scaffoldContext.mounted) {
          ScaffoldMessenger.of(scaffoldContext).showSnackBar(
            const SnackBar(content: Text('Image downloaded')),
          );
        }
      } else {
        // For mobile/desktop platforms
        // Save to a directory that's accessible on Android
        final String fileName = "edited_${DateTime.now().millisecondsSinceEpoch}.png";
        final String filePath = "${(await getApplicationDocumentsDirectory()).path}/$fileName";
        final File file = File(filePath);

        try {
          await file.writeAsBytes(bytes);
          debugPrint("SaveImage: Image saved successfully to $filePath");

          // Update the image file and controller to show the saved image
          if (mounted) {
            setState(() {
              _imageFileToEdit = file;
              _imageController = OxeanImageController();
            });
          }

          if (mounted && scaffoldContext.mounted) {
            ScaffoldMessenger.of(scaffoldContext).showSnackBar(
              SnackBar(content: Text('Image saved to $filePath')),
            );
          }
        } on FileSystemException catch (e) {
          debugPrint("SaveImage: FileSystemException writing file: $e");
          String errorMessage = "Error saving PNG: ${e.message}.";
          if (e.osError?.errorCode == 2 || e.osError?.errorCode == 13 || e.osError?.errorCode == 30) {
               errorMessage = "Error: Could not write to '$filePath'. Path may be read-only or inaccessible. Ensure the 'assets/image/' directory can be written to, or choose a different save location.";
          }
          if (mounted) {
            ScaffoldMessenger.of(scaffoldContext).showSnackBar(
              SnackBar(content: Text(errorMessage), duration: const Duration(seconds: 7)),
            );
          }
        }
      }
    } catch (e, s) {
      debugPrint("SaveImage: Unexpected error processing image for saving: $e\n$s");
      if (mounted) {
        ScaffoldMessenger.of(scaffoldContext).showSnackBar(
          SnackBar(content: Text('An unexpected error occurred while saving: $e')),
        );
      }
    } finally {
      editedImage?.dispose();
      if (mounted) {
        setState(() => _isSaving = false);
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Image Editor'),
        actions: [
          IconButton(
            icon: const Icon(Icons.folder_open),
            onPressed: _pickImage,
            tooltip: 'Open Image',
          ),
          Builder(
            builder: (buttonContext) => IconButton(
              icon: _isSaving ? const SizedBox(width:24, height:24, child:CircularProgressIndicator(strokeWidth: 2, color: Colors.white)) : const Icon(Icons.save),
              onPressed: _isSaving ? null : () => _saveImage(buttonContext),
              tooltip: 'Save Image',
            ),
          ),
        ],
      ),
      body: Padding(
        padding: const EdgeInsets.all(8.0),
        child: buildEditor(),
      ),
    );
  }

  Widget buildEditor() {
    if (_imageFileToEdit == null) return Container();

    if (kIsWeb) {
      // For web, we use the controller directly since it already has the image loaded
      return OxeanImageEditor.fromController(
        key: ValueKey('web_editor_${DateTime.now().millisecondsSinceEpoch}'),
        controller: _imageController,
      );
    } else {
      // For mobile/desktop platforms
      return OxeanImageEditor(
        key: ValueKey(_imageFileToEdit!.path),
        imageFile: _imageFileToEdit!,
        controller: _imageController,
      );
    }
  }
}

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Image Editor Demo',
      theme: ThemeData(
        useMaterial3: true,
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
      ),
      home: PageWidget(),
    );
  }
}
1
likes
120
points
3.23k
downloads

Publisher

verified publisheroxeanbits.com

Weekly Downloads

A simple image editor for Flutter, allowing users to crop, rotate, flip and drawing

Homepage

Documentation

API reference

License

unknown (license)

Dependencies

flutter, intl

More

Packages that depend on oxean_image_editor