Adaptive Palette

pub package CI License: MIT Platform

Adaptive Palette

Immersive fluid animated backgrounds from images with intelligent color extraction.

Create stunning, music app-style backgrounds that adapt to your images with smooth animations and vibrant colors.

Features

✨ Fluid Animated Backgrounds - Layered image shaders with orbital motion and heavy blur 🎨 Intelligent Color Extraction - Weighted k-means clustering for accurate palette generation 🌊 Smooth Transitions - Cross-fade between images with palette color tweening πŸ’« Corner Accent Glows - Radial gradients using extracted colors 🎭 Matte Treatment - Prevents harsh whites, ensures rich vibrant colors ⚑ Performance Optimized - Instant fallback, background extraction, 60fps animations

Installation

dependencies:
  adaptive_palette: ^3.0.0

Quick Start

The easiest way - widget handles everything automatically:

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

class MusicPlayer extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return FluidBackground(
      imageProvider: NetworkImage('https://example.com/album.jpg'),
      child: Scaffold(
        backgroundColor: Colors.transparent,
        appBar: AppBar(
          backgroundColor: Colors.transparent,
          elevation: 0,
          title: Text('Now Playing'),
        ),
        body: YourContent(),
      ),
    );
  }
}

Option 2: Manual Color Extraction

For custom implementations where you need direct access to colors:

import 'dart:ui' as ui;
import 'package:adaptive_palette/adaptive_palette.dart';

// Load and extract
final ui.Image image = await loadImageFromProvider(
  NetworkImage('https://example.com/album.jpg'),
);
final palette = await FluidPaletteExtractor.extract(image);

// Use colors
Container(color: palette.baseDark);   // Dark base
Container(color: palette.accent1);    // Top-left glow
Container(color: palette.accent2);    // Top-right glow
Container(color: palette.accent3);    // Bottom-left glow
Container(color: palette.accent4);    // Bottom-right glow

FluidBackground Widget

Basic Usage

FluidBackground(
  imageProvider: NetworkImage(albumUrl),
  child: YourContent(),
)

Full Configuration

FluidBackground(
  // Optional - shows matte fallback if null
  imageProvider: imageUrl == null ? null : NetworkImage(imageUrl),

  // Blur intensity (60-120 recommended)
  blurSigma: 80,

  // Dark overlay for text legibility (0.05-0.20 recommended)
  overlayDarken: 0.10,

  // Enable slow orbital motion
  animate: true,

  // Transition duration for palette changes
  transitionDuration: Duration(milliseconds: 1400),

  child: YourContent(),
)

Parameters

Parameter Type Default Description
imageProvider ImageProvider? null Optional image to extract colors from
child Widget required Content to display on top
blurSigma double 80 Blur intensity (higher = softer)
overlayDarken double 0.10 Dark overlay opacity for legibility
animate bool true Enable orbital motion animation
transitionDuration Duration 1400ms Palette transition duration

FluidPalette Model

class FluidPalette {
  final Color baseDark;   // Dark base (lightness ≀ 0.22)
  final Color accent1;    // Top-left glow
  final Color accent2;    // Top-right glow
  final Color accent3;    // Bottom-left glow
  final Color accent4;    // Bottom-right glow
}

Methods

// Fallback palette
FluidPalette.fallback()

// Interpolate between palettes
FluidPalette.lerp(paletteA, paletteB, 0.5)

// Copy with changes
palette.copyWith(accent1: newColor)

Complete Example

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

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Music Player',
      theme: ThemeData.dark(),
      home: MusicPlayerPage(),
    );
  }
}

class MusicPlayerPage extends StatefulWidget {
  @override
  State<MusicPlayerPage> createState() => _MusicPlayerPageState();
}

class _MusicPlayerPageState extends State<MusicPlayerPage> {
  int currentIndex = 0;

  final albums = [
    'https://picsum.photos/seed/album1/800/800',
    'https://picsum.photos/seed/album2/800/800',
    'https://picsum.photos/seed/album3/800/800',
  ];

  void nextSong() => setState(() =>
    currentIndex = (currentIndex + 1) % albums.length
  );

  @override
  Widget build(BuildContext context) {
    return FluidBackground(
      imageProvider: NetworkImage(albums[currentIndex]),
      child: Scaffold(
        backgroundColor: Colors.transparent,
        appBar: AppBar(
          backgroundColor: Colors.transparent,
          elevation: 0,
          title: Text('Now Playing'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              ClipRRect(
                borderRadius: BorderRadius.circular(16),
                child: Image.network(
                  albums[currentIndex],
                  width: 300,
                  height: 300,
                ),
              ),
              SizedBox(height: 32),
              Text('Song Title', style: TextStyle(fontSize: 28)),
              SizedBox(height: 8),
              Text('Artist Name', style: TextStyle(fontSize: 18)),
              SizedBox(height: 32),
              IconButton(
                icon: Icon(Icons.skip_next),
                iconSize: 48,
                onPressed: nextSong,
              ),
            ],
          ),
        ),
      ),
    );
  }
}

How It Works

The FluidBackground widget creates immersive backgrounds through these steps:

  1. Instant Display - Shows matte gradient fallback immediately
  2. Background Extraction - Loads image and extracts FluidPalette
  3. Smooth Reveal - Cross-fades from fallback to extracted colors (1400ms)
  4. Fluid Shaders - Renders 4 layered ImageShaders with transforms:
    • Layer 1: scale 1.35, rotation -0.08β†’0.10, opacity 0.55
    • Layer 2: scale 0.95, rotation 0.12β†’-0.06, opacity 0.45
    • Layer 3: scale 0.70, rotation -0.18β†’0.04, opacity 0.35
    • Layer 4: scale 1.85, rotation 0.05β†’-0.12, opacity 0.25
  5. Heavy Blur - Applies 80Οƒ Gaussian blur for atmospheric effect
  6. Corner Glows - Adds 4 radial gradients (360Γ—360px, 90Οƒ blur)
  7. Dark Overlay - Subtle dark layer ensures white text legibility

Color Extraction Algorithm

FluidPaletteExtractor uses advanced color science:

  1. Smart Sampling - Samples every 10th pixel, filters extremes
  2. Vibrancy Weighting - Weights by (saturation Γ— 0.75 + lightness Γ— 0.25)
  3. Center Proximity - Increases weight for pixels near center
  4. Weighted K-Means - Clusters into 6 centers (10 iterations)
  5. Perceptual Scoring - Ranks by (vivid Γ— 1.2 + mid-tone Γ— 0.8)
  6. Matte Treatment - Blends near-whites with gray (14% mix)
  7. Brightness Clamping - Limits lightness to 0.78 max
  8. Dark Base Enforcement - Ensures base ≀ 0.22 lightness

Use Cases

Perfect for:

  • 🎡 Music player screens
  • 🎬 Video player backgrounds
  • πŸ–ΌοΈ Gallery detail pages
  • πŸ“± App hero screens
  • 🎨 Media browsing UIs
  • ✨ Immersive experiences

Tips & Best Practices

Visual Quality

  • Blur intensity: 60-80 for subtle, 90-120 for dramatic
  • Overlay darkness: 0.05-0.10 for vibrant, 0.15-0.20 for dramatic
  • Animation: Disable (animate: false) for battery savings
  • Transition speed: 1000-1500ms feels smooth

Performance

  • Fast startup: Matte fallback shows instantly (0ms)
  • Background loading: Image extraction doesn't block UI
  • Smooth animations: 60fps orbital motion with GPU acceleration
  • Memory efficient: Downsampled color sampling, shader caching

Common Patterns

No image available:

FluidBackground(
  imageProvider: null,  // Shows matte fallback only
  child: YourContent(),
)

Conditional image:

FluidBackground(
  imageProvider: imageUrl == null ? null : NetworkImage(imageUrl),
  child: YourContent(),
)

Disable animation:

FluidBackground(
  imageProvider: NetworkImage(url),
  animate: false,  // Save battery
  child: YourContent(),
)

Examples

Check out the example directory for complete, runnable examples:

Run the example:

cd example
flutter run -t lib/fluid_background_example.dart

Requirements

  • Flutter SDK: >=3.0.0
  • Dart SDK: >=3.0.0 <4.0.0

Migration from v2.x

Version 3.0 deprecates old APIs in favor of FluidBackground. All v2.x code continues to work but shows deprecation warnings.

Old (v2.x)

// Old Material Design theming API
final colors = await AdaptivePalette.fromImage(NetworkImage(url));
MaterialApp(theme: colors.toThemeData());

New (v3.0)

// New FluidBackground widget
FluidBackground(
  imageProvider: NetworkImage(url),
  child: Scaffold(
    backgroundColor: Colors.transparent,
    body: YourContent(),
  ),
)

// Or manual extraction
final image = await loadImageFromProvider(NetworkImage(url));
final palette = await FluidPaletteExtractor.extract(image);

Old widgets will be removed in v4.0.0:

  • ❌ AdaptivePalette β†’ βœ… FluidPaletteExtractor
  • ❌ PaletteScope β†’ βœ… FluidBackground
  • ❌ AdaptiveImageOverlay β†’ βœ… FluidBackground
  • ❌ AdaptiveGlowFrame β†’ βœ… FluidBackground
  • ❌ AdaptiveGradientScaffold β†’ βœ… FluidBackground

Troubleshooting

Colors look washed out

  • Reduce overlayDarken (try 0.05)
  • Check source image quality

Background is too bright for white text

  • Increase overlayDarken (try 0.15-0.20)

Animation is choppy

  • Reduce blurSigma (try 60)
  • Disable animation: animate: false

Image doesn't load

  • Check network connectivity
  • Verify image URL
  • FluidBackground will show fallback automatically

License

MIT License - see LICENSE file for details.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Credits

Created by Hardik Jain

Built with advanced color extraction using weighted k-means clustering, perceptual scoring, and fluid shader layers.

Libraries

adaptive_palette
Immersive fluid animated backgrounds from images with intelligent color extraction.
main