palette_generator_plus 1.0.0 copy "palette_generator_plus: ^1.0.0" to clipboard
palette_generator_plus: ^1.0.0 copied to clipboard

Extract prominent colors from images. A maintained palette_generator successor.

example/lib/main.dart

// Copyright 2026 Kerem Bas. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/material.dart';
import 'package:palette_generator_plus/palette_generator_plus.dart';

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

/// The demo application root.
class ExampleApp extends StatelessWidget {
  /// Creates the demo application.
  const ExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'palette_generator_plus',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorSchemeSeed: const Color(0xff3f630c),
        useMaterial3: true,
        brightness: Brightness.light,
      ),
      home: const HomePage(),
    );
  }
}

/// A bundled demo image.
class _Sample {
  const _Sample(this.label, this.asset);

  final String label;
  final String asset;
}

const List<_Sample> _samples = <_Sample>[
  _Sample('Landscape', 'assets/landscape.png'),
  _Sample('Color blocks', 'assets/dominant.png'),
];

/// The single-screen demo.
class HomePage extends StatefulWidget {
  /// Creates the demo home page.
  const HomePage({super.key});

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

class _HomePageState extends State<HomePage> {
  _Sample _sample = _samples.first;
  bool _useLegacy = false;
  bool _runInIsolate = true;
  PaletteGenerator? _palette;
  bool _loading = false;

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

  Future<void> _generate() async {
    setState(() => _loading = true);
    final PaletteGenerator palette = await PaletteGenerator.fromImageProvider(
      AssetImage(_sample.asset),
      maximumColorCount: 20,
      quantizer: _useLegacy ? const LegacyQuantizer() : const CelebiQuantizer(),
      runInIsolate: _runInIsolate,
    );
    if (!mounted) {
      return;
    }
    setState(() {
      _palette = palette;
      _loading = false;
    });
  }

  @override
  Widget build(BuildContext context) {
    final PaletteGenerator? palette = _palette;
    return Scaffold(
      appBar: AppBar(title: const Text('palette_generator_plus')),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: <Widget>[
          _controls(),
          const SizedBox(height: 16),
          ClipRRect(
            borderRadius: BorderRadius.circular(12),
            child: Image.asset(
              _sample.asset,
              height: 200,
              width: double.infinity,
              fit: BoxFit.cover,
            ),
          ),
          const SizedBox(height: 24),
          if (_loading || palette == null)
            const Center(
                child: Padding(
              padding: EdgeInsets.all(32),
              child: CircularProgressIndicator(),
            ))
          else ...<Widget>[
            _section('Generated Material 3 theme'),
            _SchemePreview(palette: palette),
            const SizedBox(height: 24),
            _section('Named swatches'),
            _NamedSwatches(palette: palette),
            const SizedBox(height: 24),
            _section('All colors (with population)'),
            _AllSwatches(palette: palette),
          ],
        ],
      ),
    );
  }

  Widget _controls() {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(12),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            Wrap(
              spacing: 8,
              children: <Widget>[
                for (final _Sample sample in _samples)
                  ChoiceChip(
                    label: Text(sample.label),
                    selected: _sample == sample,
                    onSelected: (_) {
                      setState(() => _sample = sample);
                      _generate();
                    },
                  ),
              ],
            ),
            SwitchListTile(
              contentPadding: EdgeInsets.zero,
              title: const Text('Use LegacyQuantizer (median cut)'),
              subtitle: const Text('Off uses the default CelebiQuantizer'),
              value: _useLegacy,
              onChanged: (bool value) {
                setState(() => _useLegacy = value);
                _generate();
              },
            ),
            SwitchListTile(
              contentPadding: EdgeInsets.zero,
              title: const Text('Run in background isolate'),
              subtitle: const Text('Falls back to the main thread on web'),
              value: _runInIsolate,
              onChanged: (bool value) {
                setState(() => _runInIsolate = value);
                _generate();
              },
            ),
          ],
        ),
      ),
    );
  }

  Widget _section(String title) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 12),
      child: Text(title, style: Theme.of(context).textTheme.titleMedium),
    );
  }
}

/// Shows the light and dark [ColorScheme]s generated from the palette.
class _SchemePreview extends StatelessWidget {
  const _SchemePreview({required this.palette});

  final PaletteGenerator palette;

  @override
  Widget build(BuildContext context) {
    final ColorScheme light = palette.toColorScheme();
    final ColorScheme dark = palette.toColorScheme(brightness: Brightness.dark);
    return Row(
      children: <Widget>[
        Expanded(child: _schemeCard('Light', light)),
        const SizedBox(width: 12),
        Expanded(child: _schemeCard('Dark', dark)),
      ],
    );
  }

  Widget _schemeCard(String label, ColorScheme scheme) {
    return Container(
      padding: const EdgeInsets.all(12),
      decoration: BoxDecoration(
        color: scheme.surface,
        borderRadius: BorderRadius.circular(12),
        border: Border.all(color: scheme.outlineVariant),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          Text(label, style: TextStyle(color: scheme.onSurface)),
          const SizedBox(height: 8),
          _swatchRow('Primary', scheme.primary, scheme.onPrimary),
          _swatchRow('Secondary', scheme.secondary, scheme.onSecondary),
          _swatchRow('Tertiary', scheme.tertiary, scheme.onTertiary),
        ],
      ),
    );
  }

  Widget _swatchRow(String label, Color color, Color onColor) {
    return Container(
      margin: const EdgeInsets.only(top: 6),
      padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
      decoration: BoxDecoration(
        color: color,
        borderRadius: BorderRadius.circular(8),
      ),
      width: double.infinity,
      child: Text(label, style: TextStyle(color: onColor)),
    );
  }
}

/// Shows the named target swatches (dominant, vibrant, muted, etc.).
class _NamedSwatches extends StatelessWidget {
  const _NamedSwatches({required this.palette});

  final PaletteGenerator palette;

  @override
  Widget build(BuildContext context) {
    final Map<String, PaletteColor?> named = <String, PaletteColor?>{
      'Dominant': palette.dominantColor,
      'Vibrant': palette.vibrantColor,
      'Light Vibrant': palette.lightVibrantColor,
      'Dark Vibrant': palette.darkVibrantColor,
      'Muted': palette.mutedColor,
      'Light Muted': palette.lightMutedColor,
      'Dark Muted': palette.darkMutedColor,
    };
    return Column(
      children: <Widget>[
        for (final MapEntry<String, PaletteColor?> entry in named.entries)
          _NamedSwatchTile(label: entry.key, swatch: entry.value),
      ],
    );
  }
}

class _NamedSwatchTile extends StatelessWidget {
  const _NamedSwatchTile({required this.label, required this.swatch});

  final String label;
  final PaletteColor? swatch;

  @override
  Widget build(BuildContext context) {
    final PaletteColor? value = swatch;
    if (value == null) {
      return ListTile(
        dense: true,
        contentPadding: EdgeInsets.zero,
        leading: const SizedBox(
          width: 48,
          height: 32,
          child: Placeholder(strokeWidth: 1),
        ),
        title: Text(label),
        subtitle: const Text('not found'),
      );
    }
    return Container(
      margin: const EdgeInsets.only(bottom: 8),
      padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
      decoration: BoxDecoration(
        color: value.color,
        borderRadius: BorderRadius.circular(8),
      ),
      child: Row(
        children: <Widget>[
          // accessibleOnColor() picks black or white for readable text.
          Text(label, style: TextStyle(color: value.accessibleOnColor())),
          const Spacer(),
          Text(
            '#${value.color.toARGB32().toRadixString(16).substring(2)}',
            style: TextStyle(
              color: value.accessibleOnColor(),
              fontFeatures: const <FontFeature>[FontFeature.tabularFigures()],
            ),
          ),
        ],
      ),
    );
  }
}

/// Shows every extracted color with its population.
class _AllSwatches extends StatelessWidget {
  const _AllSwatches({required this.palette});

  final PaletteGenerator palette;

  @override
  Widget build(BuildContext context) {
    return Wrap(
      spacing: 8,
      runSpacing: 8,
      children: <Widget>[
        for (final PaletteColor swatch in palette.paletteColors)
          Container(
            width: 96,
            height: 64,
            padding: const EdgeInsets.all(6),
            decoration: BoxDecoration(
              color: swatch.color,
              borderRadius: BorderRadius.circular(8),
            ),
            alignment: Alignment.bottomLeft,
            child: Text(
              '${swatch.population}',
              style: TextStyle(
                color: swatch.accessibleOnColor(),
                fontWeight: FontWeight.bold,
              ),
            ),
          ),
      ],
    );
  }
}
1
likes
160
points
853
downloads

Documentation

API reference

Publisher

verified publisherkerembas.com.tr

Weekly Downloads

Extract prominent colors from images. A maintained palette_generator successor.

Repository (GitHub)
View/report issues

Topics

#color #palette #theming #material #image

License

BSD-3-Clause (license)

Dependencies

collection, flutter, material_color_utilities, meta

More

Packages that depend on palette_generator_plus