apple_vision_aesthetics 1.0.1 copy "apple_vision_aesthetics: ^1.0.1" to clipboard
apple_vision_aesthetics: ^1.0.1 copied to clipboard

PlatformiOS

Flutter plugin wrapping Apple Vision CalculateImageAestheticsScoresRequest for image quality scoring, blur detection, and utility image identification. iOS 18+ only.

example/lib/main.dart

import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:apple_vision_aesthetics/apple_vision_aesthetics.dart';

void main() => runApp(const ExampleApp());

class ExampleApp extends StatelessWidget {
  const ExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Apple Vision Aesthetics Demo',
      theme: ThemeData(
        colorSchemeSeed: Colors.deepPurple,
        useMaterial3: true,
      ),
      home: const HomePage(),
    );
  }
}

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

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  final _plugin = AppleVisionAestheticsPlatform.instance;
  bool? _isSupported;
  String? _currentAsset;
  ImageAestheticsResult? _result;
  String? _error;
  bool _loading = false;

  static const _samples = [
    ('Sharp (Nikon D3400)', 'sample_sharp.jpg'),
    ('Defocused Blur (Nikon D3400)', 'sample_defocused.jpg'),
    ('Motion Blur (Nikon D3400)', 'sample_motion_blur.jpg'),
  ];

  @override
  void initState() {
    super.initState();
    _checkSupport();
  }

  Future<void> _checkSupport() async {
    final supported = await _plugin.isSupported();
    setState(() => _isSupported = supported);
  }

  Future<void> _analyzeAsset(String assetName) async {
    setState(() {
      _loading = true;
      _error = null;
      _result = null;
      _currentAsset = assetName;
    });

    try {
      final data = await rootBundle.load('assets/$assetName');
      final tempDir = Directory.systemTemp;
      final tempFile = File('${tempDir.path}/$assetName');
      await tempFile.writeAsBytes(data.buffer.asUint8List());

      final result = await _plugin.analyzeFile(tempFile.path);
      setState(() => _result = result);

      await tempFile.delete();
    } catch (e) {
      setState(() => _error = e.toString());
    } finally {
      setState(() => _loading = false);
    }
  }

  Future<void> _analyzeBatch() async {
    setState(() {
      _loading = true;
      _error = null;
      _result = null;
      _currentAsset = null;
    });

    try {
      final paths = <String>[];
      final tempDir = Directory.systemTemp;

      for (final (_, asset) in _samples) {
        final data = await rootBundle.load('assets/$asset');
        final tempFile = File('${tempDir.path}/$asset');
        await tempFile.writeAsBytes(data.buffer.asUint8List());
        paths.add(tempFile.path);
      }

      final results = await _plugin.analyzeBatch(paths);

      for (final path in paths) {
        await File(path).delete();
      }

      if (!mounted) return;

      showDialog(
        context: context,
        builder: (ctx) => AlertDialog(
          title: const Text('Batch Results'),
          content: Column(
            mainAxisSize: MainAxisSize.min,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              for (var i = 0; i < results.length; i++) ...[
                Text(
                  _samples[i].$1,
                  style: const TextStyle(fontWeight: FontWeight.bold),
                ),
                Text(
                  'Score: ${results[i].overallScore.toStringAsFixed(3)}'
                  ' | Utility: ${results[i].isUtility}'
                  ' | Low quality: ${results[i].isLowQuality()}',
                ),
                const SizedBox(height: 8),
              ],
            ],
          ),
          actions: [
            TextButton(
              onPressed: () => Navigator.pop(ctx),
              child: const Text('OK'),
            ),
          ],
        ),
      );
    } catch (e) {
      setState(() => _error = e.toString());
    } finally {
      setState(() => _loading = false);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Image Aesthetics')),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            // Support status
            Card(
              child: Padding(
                padding: const EdgeInsets.all(16),
                child: Row(
                  children: [
                    Icon(
                      _isSupported == true
                          ? Icons.check_circle
                          : Icons.cancel,
                      color:
                          _isSupported == true ? Colors.green : Colors.red,
                    ),
                    const SizedBox(width: 12),
                    Expanded(
                      child: Text(
                        _isSupported == null
                            ? 'Checking support...'
                            : _isSupported!
                                ? 'iOS 18+ Vision API supported'
                                : 'Not supported (requires iOS 18+)',
                      ),
                    ),
                  ],
                ),
              ),
            ),

            const SizedBox(height: 16),

            // Image preview
            if (_currentAsset != null)
              ClipRRect(
                borderRadius: BorderRadius.circular(12),
                child: Image.asset(
                  'assets/$_currentAsset',
                  height: 200,
                  fit: BoxFit.cover,
                ),
              ),

            const SizedBox(height: 16),

            // Individual analysis buttons
            ...List.generate(_samples.length, (i) {
              final (label, asset) = _samples[i];
              return Padding(
                padding: const EdgeInsets.only(bottom: 8),
                child: ElevatedButton.icon(
                  onPressed: _loading ? null : () => _analyzeAsset(asset),
                  icon: Icon(
                    i == 0
                        ? Icons.photo
                        : i == 1
                            ? Icons.blur_on
                            : Icons.motion_photos_on,
                  ),
                  label: Text('Analyze: $label'),
                ),
              );
            }),

            const SizedBox(height: 8),

            // Batch button
            OutlinedButton.icon(
              onPressed: _loading ? null : _analyzeBatch,
              icon: const Icon(Icons.batch_prediction),
              label: const Text('Batch Analyze All 3'),
            ),

            const SizedBox(height: 24),

            if (_loading) const Center(child: CircularProgressIndicator()),

            // Result card
            if (_result != null)
              Card(
                child: Padding(
                  padding: const EdgeInsets.all(16),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        'Score: ${_result!.overallScore.toStringAsFixed(3)}',
                        style: Theme.of(context).textTheme.headlineSmall,
                      ),
                      const SizedBox(height: 8),
                      _ScoreBar(score: _result!.overallScore),
                      const SizedBox(height: 12),
                      Row(
                        children: [
                          _InfoChip(
                            label: 'Utility',
                            value: _result!.isUtility,
                            trueColor: Colors.orange,
                          ),
                          const SizedBox(width: 8),
                          _InfoChip(
                            label: 'Low quality',
                            value: _result!.isLowQuality(),
                            trueColor: Colors.red,
                          ),
                        ],
                      ),
                    ],
                  ),
                ),
              ),

            if (_error != null)
              Card(
                color: Colors.red.shade50,
                child: Padding(
                  padding: const EdgeInsets.all(16),
                  child: Text(_error!,
                      style: const TextStyle(color: Colors.red)),
                ),
              ),

            const SizedBox(height: 24),

            const Text(
              'Sample images from Kwentar/blur_dataset (GitHub)\n'
              'Triplet: Sharp / Defocused / Motion blur — Nikon D3400 35mm',
              style: TextStyle(fontSize: 12, color: Colors.grey),
              textAlign: TextAlign.center,
            ),
          ],
        ),
      ),
    );
  }
}

class _ScoreBar extends StatelessWidget {
  const _ScoreBar({required this.score});
  final double score;

  @override
  Widget build(BuildContext context) {
    final normalized = (score + 1) / 2;
    final color = Color.lerp(Colors.red, Colors.green, normalized)!;

    return Column(
      children: [
        ClipRRect(
          borderRadius: BorderRadius.circular(4),
          child: LinearProgressIndicator(
            value: normalized,
            minHeight: 12,
            backgroundColor: Colors.grey.shade200,
            valueColor: AlwaysStoppedAnimation(color),
          ),
        ),
        const SizedBox(height: 4),
        const Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            Text('-1.0', style: TextStyle(fontSize: 10)),
            Text('0.0', style: TextStyle(fontSize: 10)),
            Text('1.0', style: TextStyle(fontSize: 10)),
          ],
        ),
      ],
    );
  }
}

class _InfoChip extends StatelessWidget {
  const _InfoChip({
    required this.label,
    required this.value,
    required this.trueColor,
  });
  final String label;
  final bool value;
  final Color trueColor;

  @override
  Widget build(BuildContext context) {
    return Chip(
      avatar: Icon(
        value ? Icons.warning_rounded : Icons.check,
        size: 18,
        color: value ? trueColor : Colors.green,
      ),
      label: Text('$label: $value'),
    );
  }
}
0
likes
160
points
39
downloads

Publisher

verified publisheralejandrogarcia.io

Weekly Downloads

Flutter plugin wrapping Apple Vision CalculateImageAestheticsScoresRequest for image quality scoring, blur detection, and utility image identification. iOS 18+ only.

Repository (GitHub)
View/report issues

Topics

#image-quality #blur-detection #apple-vision #photo-analysis #computer-vision

Documentation

API reference

License

MIT (license)

Dependencies

flutter

More

Packages that depend on apple_vision_aesthetics

Packages that implement apple_vision_aesthetics