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

Compress images to JPEG, PNG, WebP, HEIC, or AVIF using the platform's native encoders.

example/lib/main.dart

import 'dart:typed_data';

import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:native_image_compress/image_compress.dart';

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

enum _Format { jpeg, png, webp, heic, avif }

extension on _Format {
  String get label => switch (this) {
    _Format.jpeg => 'JPEG',
    _Format.png => 'PNG',
    _Format.webp => 'WebP',
    _Format.heic => 'HEIC',
    _Format.avif => 'AVIF',
  };

  bool get supportsQuality => this != _Format.png;

  double get defaultQuality => switch (this) {
    _Format.jpeg => 80,
    _Format.webp => 75,
    _Format.heic => 70,
    _Format.avif => 55,
    _Format.png => 100,
  };
}

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

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final _picker = ImagePicker();

  Uint8List? _original;
  Uint8List? _compressed;
  _Format _format = _Format.jpeg;
  final Map<_Format, double> _quality = {
    for (final f in _Format.values) f: f.defaultQuality,
  };
  bool _isCompressing = false;
  String? _error;

  Future<void> _pickImage() async {
    final picked = await _picker.pickImage(source: ImageSource.gallery);
    if (picked == null) return;

    final bytes = await picked.readAsBytes();
    setState(() {
      _original = bytes;
      _compressed = null;
      _error = null;
    });
  }

  Future<void> _compress() async {
    final original = _original;
    if (original == null) return;

    setState(() {
      _isCompressing = true;
      _error = null;
    });

    try {
      final quality = _quality[_format]!.round();
      final result = switch (_format) {
        _Format.jpeg => await compressToJpeg(original, quality: quality),
        _Format.png => await compressToPng(original),
        _Format.webp => await compressToWebp(original, quality: quality),
        _Format.heic => await compressToHeic(original, quality: quality),
        _Format.avif => await compressToAvif(original, quality: quality),
      };
      setState(() => _compressed = result);
    } catch (e) {
      setState(() => _error = e.toString());
    } finally {
      setState(() => _isCompressing = false);
    }
  }

  String _formatBytes(int bytes) {
    if (bytes < 1024) return '$bytes B';
    final kb = bytes / 1024;
    if (kb < 1024) return '${kb.toStringAsFixed(1)} KB';
    return '${(kb / 1024).toStringAsFixed(2)} MB';
  }

  @override
  Widget build(BuildContext context) {
    final original = _original;
    final compressed = _compressed;
    final reduction = (original != null && compressed != null)
        ? 100 * (1 - compressed.length / original.length)
        : null;

    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('native_image_compress example')),
        body: SafeArea(
          child: SingleChildScrollView(
            padding: const EdgeInsets.all(16),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: [
                FilledButton.icon(
                  onPressed: _pickImage,
                  icon: const Icon(Icons.image),
                  label: const Text('Pick image'),
                ),
                const SizedBox(height: 16),
                SingleChildScrollView(
                  scrollDirection: Axis.horizontal,
                  child: SegmentedButton<_Format>(
                    segments: _Format.values
                        .map(
                          (f) => ButtonSegment(value: f, label: Text(f.label)),
                        )
                        .toList(),
                    selected: {_format},
                    onSelectionChanged: (selection) =>
                        setState(() => _format = selection.first),
                  ),
                ),
                const SizedBox(height: 8),
                if (_format.supportsQuality)
                  Row(
                    children: [
                      const Text('Quality'),
                      Expanded(
                        child: Slider(
                          value: _quality[_format]!,
                          min: 1,
                          max: 100,
                          divisions: 99,
                          label: _quality[_format]!.round().toString(),
                          onChanged: (v) =>
                              setState(() => _quality[_format] = v),
                        ),
                      ),
                      Text(_quality[_format]!.round().toString()),
                    ],
                  ),
                const SizedBox(height: 8),
                FilledButton.icon(
                  onPressed: (_original == null || _isCompressing)
                      ? null
                      : _compress,
                  icon: _isCompressing
                      ? const SizedBox(
                          width: 16,
                          height: 16,
                          child: CircularProgressIndicator(strokeWidth: 2),
                        )
                      : const Icon(Icons.compress),
                  label: Text(_isCompressing ? 'Compressing...' : 'Compress'),
                ),
                if (_error != null) ...[
                  const SizedBox(height: 16),
                  Text(
                    _error!,
                    style: TextStyle(
                      color: Theme.of(context).colorScheme.error,
                    ),
                  ),
                ],
                const SizedBox(height: 24),
                Row(
                  children: [
                    Expanded(
                      child: _ImagePreview(
                        title: 'Original',
                        bytes: original,
                        subtitle: original == null
                            ? null
                            : _formatBytes(original.length),
                      ),
                    ),
                    const SizedBox(width: 16),
                    Expanded(
                      child: _ImagePreview(
                        title: 'Compressed',
                        bytes: compressed,
                        subtitle: compressed == null
                            ? null
                            : _formatBytes(compressed.length),
                      ),
                    ),
                  ],
                ),
                if (reduction != null) ...[
                  const SizedBox(height: 16),
                  Text(
                    reduction >= 0
                        ? '${reduction.toStringAsFixed(1)}% smaller'
                        : '${(-reduction).toStringAsFixed(1)}% larger',
                    textAlign: TextAlign.center,
                    style: Theme.of(context).textTheme.titleMedium,
                  ),
                ],
              ],
            ),
          ),
        ),
      ),
    );
  }
}

class _ImagePreview extends StatelessWidget {
  const _ImagePreview({
    required this.title,
    required this.bytes,
    this.subtitle,
  });

  final String title;
  final Uint8List? bytes;
  final String? subtitle;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text(title, style: Theme.of(context).textTheme.titleSmall),
        const SizedBox(height: 8),
        AspectRatio(
          aspectRatio: 1,
          child: DecoratedBox(
            decoration: BoxDecoration(
              border: Border.all(color: Theme.of(context).dividerColor),
            ),
            child: switch (bytes) {
              null => const Icon(Icons.image_outlined, size: 48),
              final b => Image.memory(
                b,
                fit: BoxFit.contain,
                errorBuilder: (context, error, stackTrace) =>
                    const Icon(Icons.insert_drive_file_outlined, size: 48),
              ),
            },
          ),
        ),
        if (subtitle != null) ...[const SizedBox(height: 4), Text(subtitle!)],
      ],
    );
  }
}
1
likes
0
points
439
downloads

Publisher

verified publishernexosphere.xyz

Weekly Downloads

Compress images to JPEG, PNG, WebP, HEIC, or AVIF using the platform's native encoders.

Repository

License

unknown (license)

Dependencies

flutter, meta, plugin_platform_interface

More

Packages that depend on native_image_compress

Packages that implement native_image_compress