从图片中选择主要颜色
从图片中选择主要颜色
我刚接触Dart/Flutter框架,正在探索它们的可能性。
我知道在Android中,可以通过编程方式拍照并提取主要颜色值。(Android示例)
我想知道,如何在纯Dart中实现这一点?我希望它能兼容iOS和Android操作系统。
近年来,随着移动设备的普及和人们对图片处理需求的增加,从图片中提取主要颜色成为了一个常见的需求。本文将讨论一个名为"Pick main color from picture"的问题,即如何从图片中提取主要颜色。
问题的解决方法是通过使用Flutter框架和相关库来实现。下面是解决该问题的代码:
import 'dart:async';
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:image/image.dart' as img;
import 'package:flutter/services.dart' show rootBundle;
void main() => runApp(const MaterialApp(home: MyApp()));
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
State
}
class _MyAppState extends State
String imagePath = 'assets/5.jpg';
GlobalKey imageKey = GlobalKey();
GlobalKey paintKey = GlobalKey();
bool useSnapshot = true;
late GlobalKey currentKey;
final StreamController
img.Image? photo;
void initState() {
currentKey = useSnapshot ? paintKey : imageKey;
super.initState();
}
Widget build(BuildContext context) {
final String title = useSnapshot ? "snapshot" : "basic";
return SafeArea(
child: Scaffold(
appBar: AppBar(title: Text("Color picker $title")),
body: StreamBuilder(
initialData: Colors.green[500],
stream: _stateController.stream,
builder: (buildContext, snapshot) {
Color selectedColor = snapshot.data as Color ?? Colors.green;
return Stack(
children:
RepaintBoundary(
key: paintKey,
child: GestureDetector(
onPanDown: (details) {
searchPixel(details.globalPosition);
},
onPanUpdate: (details) {
searchPixel(details.globalPosition);
},
child: Center(
child: Image.asset(
imagePath,
key: imageKey,
fit: BoxFit.contain,
),
),
),
),
Container(
margin: const EdgeInsets.all(70),
width: 50,
height: 50,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: selectedColor!,
border: Border.all(width: 2.0, color: Colors.white),
boxShadow: [
const BoxShadow(
color: Colors.black12,
blurRadius: 4,
offset: Offset(0, 2))
]),
),
Positioned(
child: Text('${selectedColor}',
style: const TextStyle(
color: Colors.white,
backgroundColor: Colors.black54)),
left: 114,
top: 95,
),
],
);
}),
),
);
}
void searchPixel(Offset globalPosition) async {
if (photo == null) {
await (useSnapshot ? loadSnapshotBytes() : loadImageBundleBytes());
}
_calculatePixel(globalPosition);
}
void _calculatePixel(Offset globalPosition) {
RenderBox box = currentKey.currentContext!.findRenderObject() as RenderBox;
Offset localPosition = box.globalToLocal(globalPosition);
double px = localPosition.dx;
double py = localPosition.dy;
if (!useSnapshot) {
double widgetScale = box.size.width / photo!.width;
px = (px / widgetScale);
py = (py / widgetScale);
}
int pixel32 = photo!.getPixelSafe(px.toInt(), py.toInt());
int hex = abgrToArgb(pixel32);
_stateController.add(Color(hex));
}
Future
ByteData imageBytes = await rootBundle.load(imagePath);
setImageBytes(imageBytes);
}
Future
RenderRepaintBoundary boxPaint =
paintKey.currentContext!.findRenderObject() as RenderRepaintBoundary;
ui.Image capture = await boxPaint.toImage();
ByteData? imageBytes =
await capture.toByteData(format: ui.ImageByteFormat.png);
setImageBytes(imageBytes!);
capture.dispose();
}
void setImageBytes(ByteData imageBytes) {
List
photo;
photo = img.decodeImage(values)!;
}
}
int abgrToArgb(int argbColor) {
int r = (argbColor >> 16) & 0xFF;
int b = argbColor & 0xFF;
return (argbColor & 0xFF00FF00) | (b << 16) | r;
}
以上代码中,主要的部分是一个名为`_MyAppState`的Widget类,它是`MyApp`类的子类。在`_MyAppState`中,首先定义了一些变量,如`imagePath`表示图片的路径,`imageKey`和`paintKey`表示图片的Key,`useSnapshot`表示是否使用快照等。然后在`initState`方法中初始化了一些变量。`build`方法返回了一个包含图片和颜色选择组件的界面。通过手势识别器,可以在图片上点击或滑动来选择像素点的颜色。`searchPixel`方法用于获取当前像素点的颜色,并通过`_stateController`向界面传递颜色信息。`loadImageBundleBytes`和`loadSnapshotBytes`方法用于加载图片的字节数据。`setImageBytes`方法将字节数据解码为图片,并保存在`photo`变量中。最后,`abgrToArgb`函数用于将图片的像素颜色格式从ABGR转换为ARGB。
通过以上代码,我们可以实现从图片中提取主要颜色的功能,用户可以通过界面上的交互操作来获取颜色信息。
问题原因:用户想要从图片中提取主要颜色。
解决方法:使用Flutter团队开发的"Palette Generator"插件。以下是如何使用该插件提取主要颜色的简单示例:
首先,导入所需的库:
import 'package:palette_generator/palette_generator.dart';
然后创建主应用程序类:
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
...
home: const HomePage(
title: 'Colors from image',
image: AssetImage('assets/images/artwork_default.png',),
imageSize: Size(256.0, 170.0),
...
),
);
}
}
在上面的代码中,将要从中提取主要颜色的图片放在image字段中。
接下来,创建HomePage类:
class HomePage extends StatefulWidget {
const HomePage({
Key key,
this.title,
this.image,
this.imageSize,
}) : super(key: key);
final String title; //应用程序标题
final ImageProvider image; //用于加载颜色的图片提供器
final Size imageSize; //图片尺寸
_HomePageState createState() {
return _HomePageState();
}
}
我们还需要创建_HomePageState:
class _HomePageState extends State<HomePage> {
Rect region;
PaletteGenerator paletteGenerator;
final GlobalKey imageKey = GlobalKey();
void initState() {
super.initState();
region = Offset.zero & widget.imageSize;
_updatePaletteGenerator(region);
}
Future<void> _updatePaletteGenerator(Rect newRegion) async {
paletteGenerator = await PaletteGenerator.fromImageProvider(
widget.image,
size: widget.imageSize,
region: newRegion,
maximumColorCount: 20,
);
setState(() {});
}
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: _kBackgroundColor,
appBar: AppBar(
title: Text(widget.title),
),
body: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
new AspectRatio(
aspectRatio: 15 / 15,
child: Image(
key: imageKey,
image: widget.image,
),
),
Expanded(child: Swatches(generator: paletteGenerator)),
],
),
);
}
}
上述代码只是布局了图片和Swatches类(在下面定义)。在initState中,我们首先选择一个从中提取颜色的区域,对于我们的案例来说,就是整个图片。
之后,我们创建了一个接收PaletteGenerator并绘制颜色样本的类Swatches:
class Swatches extends StatelessWidget {
const Swatches({Key key, this.generator}) : super(key: key);
final PaletteGenerator generator;
Widget build(BuildContext context) {
final List<Widget> swatches = <Widget>[];
if (generator == null || generator.colors.isEmpty) {
return Container();
}
for (Color color in generator.colors) {
swatches.add(PaletteSwatch(color: color));
}
return Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Wrap(
children: swatches,
),
Container(height: 30.0),
PaletteSwatch(label: 'Dominant', color: generator.dominantColor?.color),
PaletteSwatch(
label: 'Light Vibrant', color: generator.lightVibrantColor?.color),
PaletteSwatch(label: 'Vibrant', color: generator.vibrantColor?.color),
PaletteSwatch(
label: 'Dark Vibrant', color: generator.darkVibrantColor?.color),
PaletteSwatch(
label: 'Light Muted', color: generator.lightMutedColor?.color),
PaletteSwatch(label: 'Muted', color: generator.mutedColor?.color),
PaletteSwatch(
label: 'Dark Muted', color: generator.darkMutedColor?.color),
],
);
}
}
接下来,我们创建了一个PaletteSwatch类,它是一个带有可选标签的颜色方块:
class PaletteSwatch extends StatelessWidget {
const PaletteSwatch({
Key key,
this.color,
this.label,
}) : super(key: key);
final Color color;
final String label;
Widget build(BuildContext context) {
final HSLColor hslColor = HSLColor.fromColor(color ?? Colors.transparent);
final HSLColor backgroundAsHsl = HSLColor.fromColor(_kBackgroundColor);
final double colorDistance = math.sqrt(
math.pow(hslColor.saturation - backgroundAsHsl.saturation, 2.0) +
math.pow(hslColor.lightness - backgroundAsHsl.lightness, 2.0));
Widget swatch = Padding(
padding: const EdgeInsets.all(2.0),
child: color == null
? const Placeholder(
fallbackWidth: 34.0,
fallbackHeight: 20.0,
color: Color(0xff404040),
strokeWidth: 2.0,
)
: Container(
decoration: BoxDecoration(
color: color,
border: Border.all(
width: 1.0,
color: _kPlaceholderColor,
style: colorDistance < 0.2
? BorderStyle.solid
: BorderStyle.none,
)),
width: 34.0,
height: 20.0,
),
);
if (label != null) {
swatch = ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 130.0, minWidth: 130.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
swatch,
Container(width: 5.0),
Text(label),
],
),
);
}
return swatch;
}
}
希望这能帮到你,谢谢。
问题的出现原因:在使用Palette Generator库时,需要获取给定图像提供者的主要颜色,但官方文档中的示例代码较为复杂,给开发者带来了困扰。
解决方法:下面提供了一个简单的函数,使用Palette Generator库来获取给定图像提供者的主要颜色,并展示了如何使用FutureBuilder来构建一个Widget。
import 'package:palette_generator/palette_generator.dart';
// Calculate dominant color from ImageProvider
Future
final PaletteGenerator paletteGenerator = await PaletteGenerator
.fromImageProvider(imageProvider);
return paletteGenerator.dominantColor.color;
}
然后可以在输出上使用FutureBuilder来构建一个Widget。
感谢,这段代码简化了官方文档中复杂的部分。