fromImageProvider static method
- required ImageProvider<
Object> provider, - Brightness brightness = Brightness.light,
- DynamicSchemeVariant dynamicSchemeVariant = DynamicSchemeVariant.tonalSpot,
- double contrastLevel = 0.0,
- Color? primary,
- Color? onPrimary,
- Color? primaryContainer,
- Color? onPrimaryContainer,
- Color? primaryFixed,
- Color? primaryFixedDim,
- Color? onPrimaryFixed,
- Color? onPrimaryFixedVariant,
- Color? secondary,
- Color? onSecondary,
- Color? secondaryContainer,
- Color? onSecondaryContainer,
- Color? secondaryFixed,
- Color? secondaryFixedDim,
- Color? onSecondaryFixed,
- Color? onSecondaryFixedVariant,
- Color? tertiary,
- Color? onTertiary,
- Color? tertiaryContainer,
- Color? onTertiaryContainer,
- Color? tertiaryFixed,
- Color? tertiaryFixedDim,
- Color? onTertiaryFixed,
- Color? onTertiaryFixedVariant,
- Color? error,
- Color? onError,
- Color? errorContainer,
- Color? onErrorContainer,
- Color? outline,
- Color? outlineVariant,
- Color? surface,
- Color? onSurface,
- Color? surfaceDim,
- Color? surfaceBright,
- Color? surfaceContainerLowest,
- Color? surfaceContainerLow,
- Color? surfaceContainer,
- Color? surfaceContainerHigh,
- Color? surfaceContainerHighest,
- Color? onSurfaceVariant,
- Color? inverseSurface,
- Color? onInverseSurface,
- Color? inversePrimary,
- Color? shadow,
- Color? scrim,
- Color? surfaceTint,
- @Deprecated('Use surface instead. ' 'This feature was deprecated after v3.18.0-0.1.pre.') Color? background,
- @Deprecated('Use onSurface instead. ' 'This feature was deprecated after v3.18.0-0.1.pre.') Color? onBackground,
- @Deprecated('Use surfaceContainerHighest instead. ' 'This feature was deprecated after v3.18.0-0.1.pre.') Color? surfaceVariant,
Generate a ColorScheme derived from the given imageProvider.
Material Color Utilities extracts the dominant color from the supplied ImageProvider. Using this color, a ColorScheme is generated with harmonious colors that meet contrast requirements for accessibility.
If any of the optional color parameters are non-null, they will be used in place of the generated colors for that field in the resulting ColorScheme. This allows apps to override specific colors for their needs.
Given the nature of the algorithm, the most dominant color of the
imageProvider may not wind up as one of the ColorScheme colors.
The provided image will be scaled down to a maximum size of 112x112 pixels during color extraction.
This sample shows how to use ColorScheme.fromImageProvider to create content-based dynamic color schemes.
To see it in action, copy and run this code snippet on DartPad.
import 'package:material_ui/material_ui.dart';
/// Flutter code sample for [ColorScheme.fromImageProvider] with content-based dynamic color.
const Widget divider = SizedBox(height: 10);
const double narrowScreenWidthThreshold = 400;
void main() => runApp(const DynamicColorExample());
class DynamicColorExample extends StatefulWidget {
const DynamicColorExample({this.loadColorScheme, super.key});
static const List<ImageProvider> images = <NetworkImage>[
NetworkImage(
'https://flutter.github.io/assets-for-api-docs/assets/material/content_based_color_scheme_1.png',
),
NetworkImage(
'https://flutter.github.io/assets-for-api-docs/assets/material/content_based_color_scheme_2.png',
),
NetworkImage(
'https://flutter.github.io/assets-for-api-docs/assets/material/content_based_color_scheme_3.png',
),
NetworkImage(
'https://flutter.github.io/assets-for-api-docs/assets/material/content_based_color_scheme_4.png',
),
NetworkImage(
'https://flutter.github.io/assets-for-api-docs/assets/material/content_based_color_scheme_5.png',
),
NetworkImage(
'https://flutter.github.io/assets-for-api-docs/assets/material/content_based_color_scheme_6.png',
),
];
final Future<ColorScheme> Function(
ImageProvider<Object> provider,
Brightness brightness,
)?
loadColorScheme;
@override
State<DynamicColorExample> createState() => _DynamicColorExampleState();
}
class _DynamicColorExampleState extends State<DynamicColorExample> {
late ColorScheme currentColorScheme;
String currentHyperlinkImage = '';
late int selectedImage;
late bool isLight;
late bool isLoading;
@override
void initState() {
super.initState();
selectedImage = 0;
isLight = true;
isLoading = true;
currentColorScheme = const ColorScheme.light();
WidgetsBinding.instance.addPostFrameCallback((_) {
_updateImage(DynamicColorExample.images[selectedImage]);
isLoading = false;
});
}
@override
Widget build(BuildContext context) {
final ColorScheme colorScheme = currentColorScheme;
final Color selectedColor = currentColorScheme.primary;
final ThemeData lightTheme = ThemeData(
colorSchemeSeed: selectedColor,
brightness: .light,
useMaterial3: false,
);
final ThemeData darkTheme = ThemeData(
colorSchemeSeed: selectedColor,
brightness: .dark,
useMaterial3: false,
);
Widget schemeLabel(String brightness, ColorScheme colorScheme) {
return Padding(
padding: const .symmetric(vertical: 15),
child: Text(
brightness,
style: TextStyle(
fontWeight: .bold,
color: colorScheme.onSecondaryContainer,
),
),
);
}
Widget schemeView(ThemeData theme) {
return Padding(
padding: const .symmetric(horizontal: 15),
child: ColorSchemeView(colorScheme: theme.colorScheme),
);
}
return MaterialApp(
theme: ThemeData(colorScheme: colorScheme),
debugShowCheckedModeBanner: false,
home: Builder(
builder: (BuildContext context) => Scaffold(
appBar: AppBar(
title: const Text('Content Based Dynamic Color'),
backgroundColor: colorScheme.primary,
foregroundColor: colorScheme.onPrimary,
actions: <Widget>[
const Icon(Icons.light_mode),
Switch(
activeThumbColor: colorScheme.primary,
activeTrackColor: colorScheme.surface,
inactiveTrackColor: colorScheme.onSecondary,
value: isLight,
onChanged: (bool value) {
setState(() {
isLight = value;
_updateImage(DynamicColorExample.images[selectedImage]);
});
},
),
],
),
body: Center(
child: isLoading
? const CircularProgressIndicator()
: ColoredBox(
color: colorScheme.secondaryContainer,
child: Column(
children: <Widget>[
divider,
_imagesRow(
context,
DynamicColorExample.images,
colorScheme,
),
divider,
Expanded(
child: ColoredBox(
color: colorScheme.surface,
child: LayoutBuilder(
builder:
(
BuildContext context,
BoxConstraints constraints,
) {
if (constraints.maxWidth <
narrowScreenWidthThreshold) {
return SingleChildScrollView(
child: Column(
children: <Widget>[
divider,
schemeLabel(
'Light ColorScheme',
colorScheme,
),
schemeView(lightTheme),
divider,
divider,
schemeLabel(
'Dark ColorScheme',
colorScheme,
),
schemeView(darkTheme),
],
),
);
} else {
return SingleChildScrollView(
child: Padding(
padding: const .only(top: 5),
child: Column(
children: <Widget>[
Row(
children: <Widget>[
Expanded(
child: Column(
children: <Widget>[
schemeLabel(
'Light ColorScheme',
colorScheme,
),
schemeView(lightTheme),
],
),
),
Expanded(
child: Column(
children: <Widget>[
schemeLabel(
'Dark ColorScheme',
colorScheme,
),
schemeView(darkTheme),
],
),
),
],
),
],
),
),
);
}
},
),
),
),
],
),
),
),
),
),
);
}
Future<void> _updateImage(ImageProvider provider) async {
final ColorScheme newColorScheme;
if (widget.loadColorScheme != null) {
newColorScheme = await widget.loadColorScheme!(
provider,
isLight ? .light : .dark,
);
} else {
newColorScheme = await ColorScheme.fromImageProvider(
provider: provider,
brightness: isLight ? .light : .dark,
);
}
if (!mounted) {
return;
}
setState(() {
selectedImage = DynamicColorExample.images.indexOf(provider);
currentColorScheme = newColorScheme;
});
}
// For small screens, have two rows of image selection. For wide screens,
// fit them onto one row.
Widget _imagesRow(
BuildContext context,
List<ImageProvider> images,
ColorScheme colorScheme,
) {
final double windowHeight = MediaQuery.heightOf(context);
final double windowWidth = MediaQuery.widthOf(context);
return Padding(
padding: const .all(8.0),
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
if (constraints.maxWidth > 800) {
return _adaptiveLayoutImagesRow(images, colorScheme, windowHeight);
} else {
return Column(
children: <Widget>[
_adaptiveLayoutImagesRow(
images.sublist(0, 3),
colorScheme,
windowWidth,
),
_adaptiveLayoutImagesRow(
images.sublist(3),
colorScheme,
windowWidth,
),
],
);
}
},
),
);
}
Widget _adaptiveLayoutImagesRow(
List<ImageProvider> images,
ColorScheme colorScheme,
double windowWidth,
) {
return Row(
mainAxisAlignment: .center,
children: images
.map(
(ImageProvider image) => Flexible(
flex: (images.length / 3).floor(),
child: GestureDetector(
onTap: () => _updateImage(image),
child: Card(
color:
DynamicColorExample.images.indexOf(image) == selectedImage
? colorScheme.primaryContainer
: colorScheme.surface,
child: Padding(
padding: const .all(5.0),
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: windowWidth * .25),
child: ClipRRect(
borderRadius: .circular(8.0),
child: Image(image: image),
),
),
),
),
),
),
)
.toList(),
);
}
}
class ColorSchemeView extends StatelessWidget {
const ColorSchemeView({super.key, required this.colorScheme});
final ColorScheme colorScheme;
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
ColorGroup(
children: <ColorChip>[
ColorChip(
label: 'primary',
color: colorScheme.primary,
onColor: colorScheme.onPrimary,
),
ColorChip(
label: 'onPrimary',
color: colorScheme.onPrimary,
onColor: colorScheme.primary,
),
ColorChip(
label: 'primaryContainer',
color: colorScheme.primaryContainer,
onColor: colorScheme.onPrimaryContainer,
),
ColorChip(
label: 'onPrimaryContainer',
color: colorScheme.onPrimaryContainer,
onColor: colorScheme.primaryContainer,
),
],
),
divider,
ColorGroup(
children: <ColorChip>[
ColorChip(
label: 'secondary',
color: colorScheme.secondary,
onColor: colorScheme.onSecondary,
),
ColorChip(
label: 'onSecondary',
color: colorScheme.onSecondary,
onColor: colorScheme.secondary,
),
ColorChip(
label: 'secondaryContainer',
color: colorScheme.secondaryContainer,
onColor: colorScheme.onSecondaryContainer,
),
ColorChip(
label: 'onSecondaryContainer',
color: colorScheme.onSecondaryContainer,
onColor: colorScheme.secondaryContainer,
),
],
),
divider,
ColorGroup(
children: <ColorChip>[
ColorChip(
label: 'tertiary',
color: colorScheme.tertiary,
onColor: colorScheme.onTertiary,
),
ColorChip(
label: 'onTertiary',
color: colorScheme.onTertiary,
onColor: colorScheme.tertiary,
),
ColorChip(
label: 'tertiaryContainer',
color: colorScheme.tertiaryContainer,
onColor: colorScheme.onTertiaryContainer,
),
ColorChip(
label: 'onTertiaryContainer',
color: colorScheme.onTertiaryContainer,
onColor: colorScheme.tertiaryContainer,
),
],
),
divider,
ColorGroup(
children: <ColorChip>[
ColorChip(
label: 'error',
color: colorScheme.error,
onColor: colorScheme.onError,
),
ColorChip(
label: 'onError',
color: colorScheme.onError,
onColor: colorScheme.error,
),
ColorChip(
label: 'errorContainer',
color: colorScheme.errorContainer,
onColor: colorScheme.onErrorContainer,
),
ColorChip(
label: 'onErrorContainer',
color: colorScheme.onErrorContainer,
onColor: colorScheme.errorContainer,
),
],
),
divider,
ColorGroup(
children: <ColorChip>[
ColorChip(
label: 'surface',
color: colorScheme.surface,
onColor: colorScheme.onSurface,
),
ColorChip(
label: 'onSurface',
color: colorScheme.onSurface,
onColor: colorScheme.surface,
),
ColorChip(
label: 'onSurfaceVariant',
color: colorScheme.onSurfaceVariant,
onColor: colorScheme.surfaceContainerHighest,
),
],
),
divider,
ColorGroup(
children: <ColorChip>[
ColorChip(label: 'outline', color: colorScheme.outline),
ColorChip(label: 'shadow', color: colorScheme.shadow),
ColorChip(
label: 'inverseSurface',
color: colorScheme.inverseSurface,
onColor: colorScheme.onInverseSurface,
),
ColorChip(
label: 'onInverseSurface',
color: colorScheme.onInverseSurface,
onColor: colorScheme.inverseSurface,
),
ColorChip(
label: 'inversePrimary',
color: colorScheme.inversePrimary,
onColor: colorScheme.primary,
),
],
),
],
);
}
}
class ColorGroup extends StatelessWidget {
const ColorGroup({super.key, required this.children});
final List<Widget> children;
@override
Widget build(BuildContext context) {
return RepaintBoundary(
child: Card(
clipBehavior: .antiAlias,
child: Column(children: children),
),
);
}
}
class ColorChip extends StatelessWidget {
const ColorChip({
super.key,
required this.color,
required this.label,
this.onColor,
});
final Color color;
final Color? onColor;
final String label;
static Color contrastColor(Color color) {
final Brightness brightness = ThemeData.estimateBrightnessForColor(color);
return switch (brightness) {
.dark => Colors.white,
.light => Colors.black,
};
}
@override
Widget build(BuildContext context) {
final Color labelColor = onColor ?? contrastColor(color);
return ColoredBox(
color: color,
child: Padding(
padding: const .all(16),
child: Row(
children: <Expanded>[
Expanded(
child: Text(label, style: TextStyle(color: labelColor)),
),
],
),
),
);
}
}
See also:
- M3 Guidelines: Dynamic color from content
- pub.dev/packages/dynamic_color, a package to create ColorSchemes based on a platform's implementation of dynamic color.
- m3.material.io/styles/color/the-color-system/color-roles, the Material 3 Color system specification.
- pub.dev/packages/material_color_utilities, the package used to algorithmically determine the dominant color and to generate the ColorScheme.
Implementation
// TODO(framework): Replace the following block with a @dartpad directive
// when it's supported. https://github.com/dart-lang/dartdoc/issues/4123
/// {@macro material_ui.dartpad_guide}
///
/// {@example /example/lib/color_scheme/dynamic_content_color.0.dart#body}
///
/// </callout-box>
///
/// See also:
///
/// * [M3 Guidelines: Dynamic color from content](https://m3.material.io/styles/color/dynamic-color/user-generated-color#8af550b9-a19e-4e9f-bb0a-7f611fed5d0f)
/// * <https://pub.dev/packages/dynamic_color>, a package to create
/// [ColorScheme]s based on a platform's implementation of dynamic color.
/// * <https://m3.material.io/styles/color/the-color-system/color-roles>, the
/// Material 3 Color system specification.
/// * <https://pub.dev/packages/material_color_utilities>, the package
/// used to algorithmically determine the dominant color and to generate
/// the [ColorScheme].
static Future<ColorScheme> fromImageProvider({
required ImageProvider provider,
Brightness brightness = Brightness.light,
DynamicSchemeVariant dynamicSchemeVariant = DynamicSchemeVariant.tonalSpot,
double contrastLevel = 0.0,
Color? primary,
Color? onPrimary,
Color? primaryContainer,
Color? onPrimaryContainer,
Color? primaryFixed,
Color? primaryFixedDim,
Color? onPrimaryFixed,
Color? onPrimaryFixedVariant,
Color? secondary,
Color? onSecondary,
Color? secondaryContainer,
Color? onSecondaryContainer,
Color? secondaryFixed,
Color? secondaryFixedDim,
Color? onSecondaryFixed,
Color? onSecondaryFixedVariant,
Color? tertiary,
Color? onTertiary,
Color? tertiaryContainer,
Color? onTertiaryContainer,
Color? tertiaryFixed,
Color? tertiaryFixedDim,
Color? onTertiaryFixed,
Color? onTertiaryFixedVariant,
Color? error,
Color? onError,
Color? errorContainer,
Color? onErrorContainer,
Color? outline,
Color? outlineVariant,
Color? surface,
Color? onSurface,
Color? surfaceDim,
Color? surfaceBright,
Color? surfaceContainerLowest,
Color? surfaceContainerLow,
Color? surfaceContainer,
Color? surfaceContainerHigh,
Color? surfaceContainerHighest,
Color? onSurfaceVariant,
Color? inverseSurface,
Color? onInverseSurface,
Color? inversePrimary,
Color? shadow,
Color? scrim,
Color? surfaceTint,
@Deprecated(
'Use surface instead. '
'This feature was deprecated after v3.18.0-0.1.pre.',
)
Color? background,
@Deprecated(
'Use onSurface instead. '
'This feature was deprecated after v3.18.0-0.1.pre.',
)
Color? onBackground,
@Deprecated(
'Use surfaceContainerHighest instead. '
'This feature was deprecated after v3.18.0-0.1.pre.',
)
Color? surfaceVariant,
}) async {
// Extract dominant colors from image.
final QuantizerResult quantizerResult = await _extractColorsFromImageProvider(provider);
final Map<int, int> colorToCount = quantizerResult.colorToCount.map(
(int key, int value) => MapEntry<int, int>(_getArgbFromAbgr(key), value),
);
// Score colors for color scheme suitability.
final List<int> scoredResults = Score.score(colorToCount, desired: 1);
final baseColor = Color(scoredResults.first);
final DynamicScheme scheme = _buildDynamicScheme(
brightness,
baseColor,
dynamicSchemeVariant,
contrastLevel,
);
return ColorScheme(
primary: primary ?? Color(MaterialDynamicColors.primary.getArgb(scheme)),
onPrimary: onPrimary ?? Color(MaterialDynamicColors.onPrimary.getArgb(scheme)),
primaryContainer:
primaryContainer ?? Color(MaterialDynamicColors.primaryContainer.getArgb(scheme)),
onPrimaryContainer:
onPrimaryContainer ?? Color(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)),
primaryFixed: primaryFixed ?? Color(MaterialDynamicColors.primaryFixed.getArgb(scheme)),
primaryFixedDim:
primaryFixedDim ?? Color(MaterialDynamicColors.primaryFixedDim.getArgb(scheme)),
onPrimaryFixed: onPrimaryFixed ?? Color(MaterialDynamicColors.onPrimaryFixed.getArgb(scheme)),
onPrimaryFixedVariant:
onPrimaryFixedVariant ??
Color(MaterialDynamicColors.onPrimaryFixedVariant.getArgb(scheme)),
secondary: secondary ?? Color(MaterialDynamicColors.secondary.getArgb(scheme)),
onSecondary: onSecondary ?? Color(MaterialDynamicColors.onSecondary.getArgb(scheme)),
secondaryContainer:
secondaryContainer ?? Color(MaterialDynamicColors.secondaryContainer.getArgb(scheme)),
onSecondaryContainer:
onSecondaryContainer ?? Color(MaterialDynamicColors.onSecondaryContainer.getArgb(scheme)),
secondaryFixed: secondaryFixed ?? Color(MaterialDynamicColors.secondaryFixed.getArgb(scheme)),
secondaryFixedDim:
secondaryFixedDim ?? Color(MaterialDynamicColors.secondaryFixedDim.getArgb(scheme)),
onSecondaryFixed:
onSecondaryFixed ?? Color(MaterialDynamicColors.onSecondaryFixed.getArgb(scheme)),
onSecondaryFixedVariant:
onSecondaryFixedVariant ??
Color(MaterialDynamicColors.onSecondaryFixedVariant.getArgb(scheme)),
tertiary: tertiary ?? Color(MaterialDynamicColors.tertiary.getArgb(scheme)),
onTertiary: onTertiary ?? Color(MaterialDynamicColors.onTertiary.getArgb(scheme)),
tertiaryContainer:
tertiaryContainer ?? Color(MaterialDynamicColors.tertiaryContainer.getArgb(scheme)),
onTertiaryContainer:
onTertiaryContainer ?? Color(MaterialDynamicColors.onTertiaryContainer.getArgb(scheme)),
tertiaryFixed: tertiaryFixed ?? Color(MaterialDynamicColors.tertiaryFixed.getArgb(scheme)),
tertiaryFixedDim:
tertiaryFixedDim ?? Color(MaterialDynamicColors.tertiaryFixedDim.getArgb(scheme)),
onTertiaryFixed:
onTertiaryFixed ?? Color(MaterialDynamicColors.onTertiaryFixed.getArgb(scheme)),
onTertiaryFixedVariant:
onTertiaryFixedVariant ??
Color(MaterialDynamicColors.onTertiaryFixedVariant.getArgb(scheme)),
error: error ?? Color(MaterialDynamicColors.error.getArgb(scheme)),
onError: onError ?? Color(MaterialDynamicColors.onError.getArgb(scheme)),
errorContainer: errorContainer ?? Color(MaterialDynamicColors.errorContainer.getArgb(scheme)),
onErrorContainer:
onErrorContainer ?? Color(MaterialDynamicColors.onErrorContainer.getArgb(scheme)),
outline: outline ?? Color(MaterialDynamicColors.outline.getArgb(scheme)),
outlineVariant: outlineVariant ?? Color(MaterialDynamicColors.outlineVariant.getArgb(scheme)),
surface: surface ?? Color(MaterialDynamicColors.surface.getArgb(scheme)),
surfaceDim: surfaceDim ?? Color(MaterialDynamicColors.surfaceDim.getArgb(scheme)),
surfaceBright: surfaceBright ?? Color(MaterialDynamicColors.surfaceBright.getArgb(scheme)),
surfaceContainerLowest:
surfaceContainerLowest ??
Color(MaterialDynamicColors.surfaceContainerLowest.getArgb(scheme)),
surfaceContainerLow:
surfaceContainerLow ?? Color(MaterialDynamicColors.surfaceContainerLow.getArgb(scheme)),
surfaceContainer:
surfaceContainer ?? Color(MaterialDynamicColors.surfaceContainer.getArgb(scheme)),
surfaceContainerHigh:
surfaceContainerHigh ?? Color(MaterialDynamicColors.surfaceContainerHigh.getArgb(scheme)),
surfaceContainerHighest:
surfaceContainerHighest ??
Color(MaterialDynamicColors.surfaceContainerHighest.getArgb(scheme)),
onSurface: onSurface ?? Color(MaterialDynamicColors.onSurface.getArgb(scheme)),
onSurfaceVariant:
onSurfaceVariant ?? Color(MaterialDynamicColors.onSurfaceVariant.getArgb(scheme)),
inverseSurface: inverseSurface ?? Color(MaterialDynamicColors.inverseSurface.getArgb(scheme)),
onInverseSurface:
onInverseSurface ?? Color(MaterialDynamicColors.inverseOnSurface.getArgb(scheme)),
inversePrimary: inversePrimary ?? Color(MaterialDynamicColors.inversePrimary.getArgb(scheme)),
shadow: shadow ?? Color(MaterialDynamicColors.shadow.getArgb(scheme)),
scrim: scrim ?? Color(MaterialDynamicColors.scrim.getArgb(scheme)),
surfaceTint: surfaceTint ?? Color(MaterialDynamicColors.primary.getArgb(scheme)),
brightness: brightness,
// DEPRECATED (newest deprecations at the bottom)
background: background ?? Color(MaterialDynamicColors.background.getArgb(scheme)),
onBackground: onBackground ?? Color(MaterialDynamicColors.onBackground.getArgb(scheme)),
surfaceVariant: surfaceVariant ?? Color(MaterialDynamicColors.surfaceVariant.getArgb(scheme)),
);
}