Skip to main content

image_color_backdrop 0.1.0 copy "image_color_backdrop: ^0.1.0" to clipboard
image_color_backdrop: ^0.1.0 copied to clipboard

Extracts the dominant, vibrant or muted color from an image, icon or logo and turns it into a background color with opacity, tint, contrast and gradient controls.

example/lib/main.dart

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

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

/// The sample images bundled with the example app.
const List<({String label, String asset})> samples = [
  (
    label: 'Coral bolt (transparent PNG)',
    asset: 'assets/samples/coral_bolt.png',
  ),
  (label: 'Teal ring (transparent PNG)', asset: 'assets/samples/teal_ring.png'),
  (label: 'Amber hexagon', asset: 'assets/samples/amber_hex.png'),
  (label: 'Purple badge', asset: 'assets/samples/purple_badge.png'),
  (
    label: 'Indigo diamond (full bleed)',
    asset: 'assets/samples/indigo_diamond.png',
  ),
  (label: 'Green apple on white', asset: 'assets/samples/green_on_white.png'),
  (label: 'White mark on dark', asset: 'assets/samples/white_on_dark.png'),
  (label: 'Sunset', asset: 'assets/samples/sunset.png'),
];

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'image_color_backdrop',
      theme: ThemeData(colorSchemeSeed: Colors.indigo, useMaterial3: true),
      darkTheme: ThemeData(
        colorSchemeSeed: Colors.indigo,
        brightness: Brightness.dark,
        useMaterial3: true,
      ),
      home: const HomePage(),
    );
  }
}

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

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

class _HomePageState extends State<HomePage> {
  int _index = 0;

  static const _pages = <Widget>[
    GalleryPage(),
    PlaygroundPage(),
    UseTheColorPage(),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('image_color_backdrop')),
      body: _pages[_index],
      bottomNavigationBar: NavigationBar(
        selectedIndex: _index,
        onDestinationSelected: (i) => setState(() => _index = i),
        destinations: const [
          NavigationDestination(icon: Icon(Icons.grid_view), label: 'Gallery'),
          NavigationDestination(icon: Icon(Icons.tune), label: 'Playground'),
          NavigationDestination(
            icon: Icon(Icons.palette_outlined),
            label: 'Use the color',
          ),
        ],
      ),
    );
  }
}

// ---------------------------------------------------------------------------
// 1. Gallery: ImageBackdrop.image with different styles.
// ---------------------------------------------------------------------------

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

  @override
  Widget build(BuildContext context) {
    return ListView(
      padding: const EdgeInsets.all(16),
      children: [
        const _Section('Solid (default: dominant color)'),
        _row(const BackdropOptions(), BackdropStyle.original),
        const _Section('Soft tint: BackdropStyle.soft'),
        _row(const BackdropOptions(), BackdropStyle.soft),
        const _Section('Pastel: BackdropStyle.pastel'),
        _row(const BackdropOptions(), BackdropStyle.pastel),
        const _Section('Deep: BackdropStyle.deep'),
        _row(const BackdropOptions(), BackdropStyle.deep),
        const _Section('Vibrant strategy + ignore white studio backgrounds'),
        _row(
          const BackdropOptions(
            strategy: BackdropStrategy.vibrant,
            ignoreNearWhite: true,
          ),
          BackdropStyle.original,
        ),
        const _Section('Edges only: match what surrounds the logo'),
        _row(
          const BackdropOptions(region: BackdropRegion.edges),
          BackdropStyle.original,
        ),
        const _Section('Tonal gradient'),
        _row(
          const BackdropOptions(),
          BackdropStyle.original,
          gradient: BackdropGradientMode.tonal,
        ),
        const _Section('Palette gradient'),
        _row(
          const BackdropOptions(),
          BackdropStyle.original,
          gradient: BackdropGradientMode.palette,
        ),
      ],
    );
  }

  Widget _row(
    BackdropOptions options,
    BackdropStyle style, {
    BackdropGradientMode gradient = BackdropGradientMode.none,
  }) {
    return SizedBox(
      height: 112,
      child: ListView.separated(
        scrollDirection: Axis.horizontal,
        itemCount: samples.length,
        separatorBuilder: (_, _) => const SizedBox(width: 12),
        itemBuilder: (context, i) => ImageBackdrop.image(
          image: AssetImage(samples[i].asset),
          options: options,
          style: style,
          gradientMode: gradient,
          width: 112,
          height: 112,
          padding: const EdgeInsets.all(12),
          borderRadius: BorderRadius.circular(20),
        ),
      ),
    );
  }
}

class _Section extends StatelessWidget {
  const _Section(this.title);

  final String title;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.only(top: 20, bottom: 8),
      child: Text(title, style: Theme.of(context).textTheme.titleSmall),
    );
  }
}

// ---------------------------------------------------------------------------
// 2. Playground: change every option live.
// ---------------------------------------------------------------------------

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

  @override
  State<PlaygroundPage> createState() => _PlaygroundPageState();
}

class _PlaygroundPageState extends State<PlaygroundPage> {
  int _sample = 0;
  BackdropStrategy _strategy = BackdropStrategy.dominant;
  BackdropGradientMode _gradient = BackdropGradientMode.none;
  bool _ignoreWhite = false;
  bool _edges = false;
  double _opacity = 1;
  double _brightness = 0;
  double _saturation = 0;

  @override
  Widget build(BuildContext context) {
    final image = AssetImage(samples[_sample].asset);
    final options = BackdropOptions(
      strategy: _strategy,
      ignoreNearWhite: _ignoreWhite,
      region: _edges ? BackdropRegion.edges : BackdropRegion.whole,
    );
    final style = BackdropStyle(
      opacity: _opacity,
      brightness: _brightness,
      saturation: _saturation,
    );

    return ListView(
      padding: const EdgeInsets.all(16),
      children: [
        Center(
          child: ImageBackdrop.image(
            image: image,
            options: options,
            style: style,
            gradientMode: _gradient,
            width: 220,
            height: 220,
            padding: const EdgeInsets.all(28),
            borderRadius: BorderRadius.circular(32),
          ),
        ),
        const SizedBox(height: 12),
        BackdropColorBuilder(
          image: image,
          options: options,
          style: style,
          builder: (context, result) => _PaletteRow(result: result),
        ),
        const SizedBox(height: 8),
        Wrap(
          spacing: 8,
          children: [
            for (var i = 0; i < samples.length; i++)
              ChoiceChip(
                label: Text('${i + 1}'),
                selected: i == _sample,
                onSelected: (_) => setState(() => _sample = i),
              ),
          ],
        ),
        const SizedBox(height: 8),
        Text(samples[_sample].label),
        const Divider(height: 32),
        _labeled(
          'Strategy',
          DropdownButton<BackdropStrategy>(
            value: _strategy,
            isExpanded: true,
            items: [
              for (final s in BackdropStrategy.values)
                DropdownMenuItem(value: s, child: Text(s.name)),
            ],
            onChanged: (v) => setState(() => _strategy = v ?? _strategy),
          ),
        ),
        _labeled(
          'Gradient',
          DropdownButton<BackdropGradientMode>(
            value: _gradient,
            isExpanded: true,
            items: [
              for (final g in BackdropGradientMode.values)
                DropdownMenuItem(value: g, child: Text(g.name)),
            ],
            onChanged: (v) => setState(() => _gradient = v ?? _gradient),
          ),
        ),
        SwitchListTile(
          title: const Text('ignoreNearWhite'),
          value: _ignoreWhite,
          onChanged: (v) => setState(() => _ignoreWhite = v),
        ),
        SwitchListTile(
          title: const Text('Sample edges only'),
          value: _edges,
          onChanged: (v) => setState(() => _edges = v),
        ),
        _slider('opacity', _opacity, 0, 1, (v) => setState(() => _opacity = v)),
        _slider(
          'brightness',
          _brightness,
          -1,
          1,
          (v) => setState(() => _brightness = v),
        ),
        _slider(
          'saturation',
          _saturation,
          -1,
          1,
          (v) => setState(() => _saturation = v),
        ),
      ],
    );
  }

  Widget _labeled(String label, Widget child) => Row(
    children: [
      SizedBox(width: 90, child: Text(label)),
      Expanded(child: child),
    ],
  );

  Widget _slider(
    String label,
    double value,
    double min,
    double max,
    ValueChanged<double> onChanged,
  ) {
    return Row(
      children: [
        SizedBox(width: 90, child: Text(label)),
        Expanded(
          child: Slider(value: value, min: min, max: max, onChanged: onChanged),
        ),
        SizedBox(width: 44, child: Text(value.toStringAsFixed(2))),
      ],
    );
  }
}

class _PaletteRow extends StatelessWidget {
  const _PaletteRow({required this.result});

  final BackdropResult result;

  @override
  Widget build(BuildContext context) {
    final swatches = result.palette.swatches;
    if (swatches.isEmpty) {
      return const SizedBox(height: 32);
    }
    return SizedBox(
      height: 32,
      child: Row(
        children: [
          for (final s in swatches)
            Expanded(
              flex: (s.proportion * 1000).round().clamp(1, 1000),
              child: Container(color: s.color),
            ),
        ],
      ),
    );
  }
}

// ---------------------------------------------------------------------------
// 3. Use the color somewhere else.
// ---------------------------------------------------------------------------

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

  @override
  State<UseTheColorPage> createState() => _UseTheColorPageState();
}

class _UseTheColorPageState extends State<UseTheColorPage> {
  final BackdropController _controller = BackdropController(
    style: const BackdropStyle(opacity: 0.9),
  );

  @override
  void initState() {
    super.initState();
    _controller.load(AssetImage(samples.first.asset));
  }

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

  @override
  Widget build(BuildContext context) {
    return ListenableBuilder(
      listenable: _controller,
      builder: (context, _) {
        final result = _controller.result;
        return AnimatedContainer(
          duration: const Duration(milliseconds: 300),
          color: result.color.withValues(alpha: 0.25),
          child: ListView(
            padding: const EdgeInsets.all(16),
            children: [
              Text(
                'The tile you tap tints this whole page through a '
                'BackdropController.',
                style: Theme.of(context).textTheme.bodyLarge,
              ),
              const SizedBox(height: 16),
              Wrap(
                spacing: 12,
                runSpacing: 12,
                children: [
                  for (final sample in samples)
                    GestureDetector(
                      onTap: () => _controller.load(AssetImage(sample.asset)),
                      child: SizedBox(
                        width: 72,
                        height: 72,
                        child: Image.asset(sample.asset),
                      ),
                    ),
                ],
              ),
              const SizedBox(height: 24),
              Text(
                'BackdropColorBuilder: color any widget you like',
                style: Theme.of(context).textTheme.titleSmall,
              ),
              const SizedBox(height: 8),
              BackdropColorBuilder(
                image: const AssetImage('assets/samples/coral_bolt.png'),
                style: BackdropStyle.pastel,
                builder: (context, result) {
                  return Card(
                    color: result.color,
                    child: ListTile(
                      leading: Image.asset('assets/samples/coral_bolt.png'),
                      title: Text(
                        'Custom card',
                        style: TextStyle(color: result.onColor),
                      ),
                      subtitle: Text(
                        'Colored with result.color',
                        style: TextStyle(color: result.onColor),
                      ),
                    ),
                  );
                },
              ),
              const SizedBox(height: 24),
              Text(
                'A logo inside a container that matches it',
                style: Theme.of(context).textTheme.titleSmall,
              ),
              const SizedBox(height: 8),
              ImageBackdrop(
                image: const AssetImage('assets/samples/teal_ring.png'),
                style: BackdropStyle.soft,
                borderRadius: BorderRadius.circular(16),
                padding: const EdgeInsets.all(16),
                child: Row(
                  children: [
                    Image.asset('assets/samples/teal_ring.png', width: 48),
                    const SizedBox(width: 16),
                    const Expanded(
                      child: Text('Text and icons adapt automatically.'),
                    ),
                    const Icon(Icons.check_circle),
                  ],
                ),
              ),
            ],
          ),
        );
      },
    );
  }
}
3
likes
160
points
332
downloads
screenshot

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Extracts the dominant, vibrant or muted color from an image, icon or logo and turns it into a background color with opacity, tint, contrast and gradient controls.

Repository (GitHub)
View/report issues
Contributing

Topics

#color #image #palette #background #widget

License

MIT (license)

Dependencies

flutter

More

Packages that depend on image_color_backdrop