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
Installation
dependencies:
adaptive_palette: ^1.0.7
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
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,
);
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