Adaptive Palette
Dynamic theming from images for Flutter. Extract vibrant colors and create accessible Material Design 3 themes with Spotify/Luma-style adaptive backgrounds.
Features
- Intelligent color extraction - Adaptive algorithm that automatically adjusts to different image types (colorful, monochromatic, or normal)
- Perceptual color science - Uses CAM16/HCT color space for human-eye-accurate color analysis
- Material Design 3 - Full M3 theming with tonal palettes and dynamic color
- Accessible by default - WCAG contrast validation ensures readable text (4.5:1 minimum)
- Animated transitions - Smooth theme changes with
PaletteScope - Performance optimized - Content-based caching, efficient median-cut quantization
- Production ready - Works with all image types: photos, products, landscapes, UI screenshots
- Flutter 3.0+ compatible - Supports ~2 years of Flutter releases
- Hero-ready widgets - Drop-in overlay/glow/scaffold widgets for Spotify, YouTube, and Luma-style UIs
Installation
dependencies:
adaptive_palette: ^2.0.3
Examples
Check out the example directory for complete, runnable examples:
- main.dart - Full-featured app with blurred backgrounds and animated color palette display
- simple_example.dart - Minimal implementation showing basic usage
Run the example:
cd example
flutter run
Quick Start
import 'package:adaptive_palette/adaptive_palette.dart';
// Extract colors from any ImageProvider
final colors = await AdaptivePalette.fromImage(
NetworkImage('https://example.com/image.jpg'),
targetBrightness: Brightness.dark,
);
// colors contains: primary, secondary, background, surface, and their on-colors
Adaptive Palette - Image Overlay Template
Build Spotify/Luma-style overlays without wiring anything yourself. The package now ships with three drop-in widgets:
AdaptiveImageOverlay– hero/playlist cards with adaptive gradients.AdaptiveGlowImageFrame– YouTube-style border glow pulled from image colors.AdaptiveGradientScaffold– fills an entire screen background with the image palette.
AdaptiveImageOverlay
AdaptiveImageOverlay.network(
'https://picsum.photos/1600/900',
overlayStyle: const AdaptiveOverlayStyle(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
stops: [0.0, 0.4, 0.7, 1.0],
opacities: [0.95, 0.65, 0.25, 0.0],
),
borderRadius: BorderRadius.circular(28),
padding: const EdgeInsets.all(32),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: const [
Text('Gradient Overlay', style: TextStyle(fontSize: 24)),
SizedBox(height: 12),
Text('Drop this on any hero image to mirror Spotify/Luma.'),
],
),
);
AdaptiveGlowImageFrame
AdaptiveGlowImageFrame.network(
'https://picsum.photos/2000/1100',
aspectRatio: 21 / 9,
blurRadius: 48,
spreadRadius: 10,
tone: AdaptiveOverlayTone.secondary,
);
AdaptiveGradientScaffold
AdaptiveGradientScaffold.network(
'https://picsum.photos/1800/1200',
appBar: AppBar(
title: const Text('Adaptive Palette Overlay Kit'),
backgroundColor: Colors.transparent,
),
body: ListView(children: const [Text('Your content here')]),
);
Wrap your app with PaletteScope and the scaffold will automatically drive your Material theme (set syncWithPaletteScope: false if you want manual control). Each widget also exposes onColorsReady so you can tap into the extracted palette.
AdaptiveOverlayStyle powers all three widgets. Use it to change gradient direction, tone (primary/secondary/surface/background), or provide explicit colors if you want a custom ramp.
Gradient Patterns
Left-to-right hero overlay:
const AdaptiveOverlayStyle(
begin: Alignment.centerLeft,
end: Alignment.center,
stops: [0.0, 0.4, 0.7, 1.0],
opacities: [0.9, 0.6, 0.3, 0.0],
)
Diagonal accent overlay:
const AdaptiveOverlayStyle(
begin: Alignment.bottomLeft,
end: Alignment.topRight,
stops: [0.0, 0.45, 1.0],
opacities: [0.95, 0.45, 0.0],
tone: AdaptiveOverlayTone.secondary,
)
See
example/lib/image_overlay_template.dartor runflutter run -t lib/image_overlay_template.dartinsideexample/to try all three black-box widgets together.
Usage
Complete App Example
import 'package:adaptive_palette/adaptive_palette.dart';
import 'package:flutter/material.dart';
import 'dart:ui' as ui;
void main() => runApp(
const PaletteScope(
seed: ThemeColors.fallback(),
brightness: Brightness.dark,
child: MyApp(),
),
);
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) => MaterialApp(
theme: PaletteScope.of(context).theme,
home: const MyHomePage(),
);
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
ThemeColors? _colors;
@override
void initState() {
super.initState();
_extractColors();
}
Future<void> _extractColors() async {
final colors = await AdaptivePalette.fromImage(
const NetworkImage('https://picsum.photos/800/600'),
targetBrightness: Brightness.dark,
);
if (!mounted) return;
setState(() => _colors = colors);
// Animate to new theme
PaletteScope.of(context).animateTo(
colors,
duration: const Duration(milliseconds: 800),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: _colors == null
? const CircularProgressIndicator()
: Text(
'Theme extracted!',
style: Theme.of(context).textTheme.headlineMedium,
),
),
);
}
}
Creating a Blurred Background (Spotify/Luma Style)
// Build a blurred adaptive background manually
Stack(
fit: StackFit.expand,
children: [
Transform.scale(
scale: 1.2,
child: ImageFiltered(
imageFilter: ui.ImageFilter.blur(sigmaX: 80, sigmaY: 80),
child: Image.network(
'https://example.com/cover.jpg',
fit: BoxFit.cover,
),
),
),
Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Theme.of(context).colorScheme.surface.withOpacity(0.1),
Theme.of(context).colorScheme.surface.withOpacity(0.3),
],
),
),
),
// Your content here
],
)
Demo
Run the included demo to see adaptive theming in action:
git clone https://github.com/HardikSJain/adaptive_palette.git
cd adaptive_palette
flutter pub get
flutter run
The demo shows a Luma-style adaptive background that extracts colors from an image and smoothly transitions the theme.
API Reference
AdaptivePalette.fromImage()
Extract a theme color palette from an image.
Future<ThemeColors> fromImage(
ImageProvider provider, {
Brightness targetBrightness = Brightness.light,
int quantizeColors = 24, // 8-64, more = better quality, slower
int resize = 96, // 64-256, larger = slower but more accurate
double minContrast = 4.5, // WCAG contrast ratio: 4.5=AA, 7.0=AAA
})
Parameters:
provider- Any FlutterImageProvider(NetworkImage, AssetImage, FileImage, etc.)targetBrightness- Generate colors for light or dark themequantizeColors- Number of colors to extract (higher = better quality, slower)resize- Downsample image to this size for faster processingminContrast- Minimum WCAG contrast ratio for text colors
ThemeColors
Generated theme color palette with accessibility guarantees.
class ThemeColors {
final Color primary; // Main brand color
final Color onPrimary; // Text on primary (contrast-safe)
final Color secondary; // Accent color
final Color onSecondary; // Text on secondary (contrast-safe)
final Color background; // Page background
final Color onBackground; // Text on background (contrast-safe)
final Color surface; // Card/surface color
final Color onSurface; // Text on surface (contrast-safe)
}
PaletteScope
Animated theme container for app-wide color transitions.
PaletteScope({
required ThemeColors seed,
required Widget child,
Brightness brightness = Brightness.light,
})
// Access the controller
final controller = PaletteScope.of(context);
// Get current theme
final theme = controller.theme;
// Animate to new colors
controller.animateTo(
newColors,
duration: const Duration(milliseconds: 800),
curve: Curves.easeOutCubic,
);
AdaptiveOverlayStyle
Generate gradient overlays directly from ThemeColors.
final gradient = colors.overlayGradient(
style: const AdaptiveOverlayStyle(
begin: Alignment.bottomLeft,
end: Alignment.topRight,
stops: [0.0, 0.5, 1.0],
opacities: [0.9, 0.45, 0.0],
tone: AdaptiveOverlayTone.secondary,
),
);
Parameters:
begin/end- gradient directionstops&opacities- control color fallofftone- choose which palette tone powers the overlay (primary/secondary/surface/background)colors- optional list of explicit colors if you need multi-tone gradientscolorOverride- inject a custom base color while still using the adaptive opacity curve
How It Works
- Image processing - Downsamples image for performance
- Median-cut quantization - Extracts dominant colors using histogram-based algorithm
- Perceptual scoring - Ranks colors by saturation, luminance, and population
- HCT color space - Uses Material Design 3's perceptual color system (Hue-Chroma-Tone)
- WCAG validation - Ensures all text colors meet accessibility standards
- SHA-1 caching - Content-based cache prevents re-processing identical images
Performance Tips
Fast extraction (for scrolling lists)
final colors = await AdaptivePalette.fromImage(
image,
resize: 64, // Smaller = faster
quantizeColors: 24, // Fewer colors = faster
);
High quality (for hero/detail views)
final colors = await AdaptivePalette.fromImage(
image,
resize: 256, // Larger = more accurate
quantizeColors: 64, // More colors = better selection
);
Preload palettes
// Extract colors during app initialization
await Future.wait(
images.map((img) => AdaptivePalette.fromImage(img)),
);
// Results are cached for instant access later
Requirements
- Flutter SDK: >=3.0.0
- Dart SDK: >=3.0.0 <4.0.0
Dependencies
material_color_utilities- Material Design 3 color sciencecrypto- SHA-1 hashing for content-based cachingcached_network_image- Efficient network image loadingpath_provider- Cache directory access
Libraries
- adaptive_palette
- Spotify/Luma-style dynamic theming from an image with contrast guardrails.
- main