flutter 从图片中选取主颜色

8zzbczxx  于 2022-12-05  发布在  Flutter
关注(0)|答案(3)|浏览(448)

我是Dart/Flutter框架的新手,我还在探索它们的可能性。
我知道在Android中可以通过编程方式拍摄一张图片并从中提取主颜色值。(Android example
我想知道,这将如何实现在纯 dart ?我希望它能兼容iOS和Android操作系统。

6g8kf2rb

6g8kf2rb1#

这里有一个简单的函数,它返回给定ImageProvider的主色。这显示了Palette Generator的基本用法,没有所有的样板。

import 'package:palette_generator/palette_generator.dart';

// Calculate dominant color from ImageProvider
Future<Color> getImagePalette (ImageProvider imageProvider) async {
  final PaletteGenerator paletteGenerator = await PaletteGenerator
      .fromImageProvider(imageProvider);
  return paletteGenerator.dominantColor.color;
}

然后在输出中使用FutureBuilder构建一个Widget。

polkgigr

polkgigr2#

我可能认为你得到了一个修复,但对于这个问题的未来搜索,我建议你检查flutter团队的Pallete Generator。我会尝试给予一个简单的解释代码如何工作,但一个详细的例子头部到插件的GitHub repo
下面的示例将获取一个图像,然后从中选择主色,然后显示颜色
首先,我们添加所需的导入

import 'package:palette_generator/palette_generator.dart';

然后创建主应用程序类。

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  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),
      ...

      ),
    );
  }
}

在上面的图像字段中,放置您要从中提取主色的图像,我使用的图像显示为here
接下来,我们创建HomePage类

@immutable
class HomePage extends StatefulWidget {
  /// Creates the home page.
  const HomePage({
    Key key,
    this.title,
    this.image,
    this.imageSize,
  }) : super(key: key);

  final String title; //App title
  final ImageProvider image; //Image provider to load the colors from
  final Size imageSize; //Image dimensions

  @override
  _HomePageState createState() {
    return _HomePageState();
  }
}

让我们也创建_HomePageState

class _HomePageState extends State<HomePage> {
  Rect region;
  PaletteGenerator paletteGenerator;

  final GlobalKey imageKey = GlobalKey();

  @override
  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(() {});
  }

  @override
  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,Swatches是下面定义的一个类。在initState中,我们首先选择一个区域,颜色将从该区域派生,在我们的例子中是整个图像。
之后,我们创建一个类Swatches,它接收一个PalleteGenerator并为它绘制色板。

class Swatches extends StatelessWidget {

  const Swatches({Key key, this.generator}) : super(key: key);

  // The PaletteGenerator that contains all of the swatches that we're going
  // to display.
  final PaletteGenerator generator;

  @override
  Widget build(BuildContext context) {
    final List<Widget> swatches = <Widget>[];
    //The generator field can be null, if so, we return an empty container
    if (generator == null || generator.colors.isEmpty) {
      return Container();
    }
    //Loop through the colors in the PaletteGenerator and add them to the list of swatches above
    for (Color color in generator.colors) {
      swatches.add(PaletteSwatch(color: color));
    }
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      mainAxisSize: MainAxisSize.min,
      crossAxisAlignment: CrossAxisAlignment.center,
      children: <Widget>[
        //All the colors,
        Wrap(
          children: swatches,
        ),
        //The colors with ranking
        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类。

@immutable
class PaletteSwatch extends StatelessWidget {
  // Creates a PaletteSwatch.
  //
  // If the [color] argument is omitted, then the swatch will show a
  // placeholder instead, to indicate that there is no color.
  const PaletteSwatch({
    Key key,
    this.color,
    this.label,
  }) : super(key: key);

  // The color of the swatch. May be null.
  final Color color;

  // The optional label to display next to the swatch.
  final String label;

  @override
  Widget build(BuildContext context) {
    // Compute the "distance" of the color swatch and the background color
    // so that we can put a border around those color swatches that are too
    // close to the background's saturation and lightness. We ignore hue for
    // the comparison.
    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;
  }
}

希望这对你有帮助,谢谢。

wlp8pajw

wlp8pajw3#

//////////////////////////////
//
// 2019, roipeker.com
// screencast - demo simple image:
// https://youtu.be/EJyRH4_pY8I
//
// screencast - demo snapshot:
// https://youtu.be/-LxPcL7T61E
//
//////////////////////////////

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);

  @override
  State<StatefulWidget> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  String imagePath = 'assets/5.jpg';
  GlobalKey imageKey = GlobalKey();
  GlobalKey paintKey = GlobalKey();

  // CHANGE THIS FLAG TO TEST BASIC IMAGE, AND SNAPSHOT.
  bool useSnapshot = true;

  // based on useSnapshot=true ? paintKey : imageKey ;
  // this key is used in this example to keep the code shorter.
  late GlobalKey currentKey;

  final StreamController<Color> _stateController = StreamController<Color>();
  //late img.Image photo ;
  img.Image? photo;

  @override
  void initState() {
    currentKey = useSnapshot ? paintKey : imageKey;
    super.initState();
  }

  @override
  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: <Widget>[
                  RepaintBoundary(
                    key: paintKey,
                    child: GestureDetector(
                      onPanDown: (details) {
                        searchPixel(details.globalPosition);
                      },
                      onPanUpdate: (details) {
                        searchPixel(details.globalPosition);
                      },
                      child: Center(
                        child: Image.asset(
                          imagePath,
                          key: imageKey,
                          //color: Colors.red,
                          //colorBlendMode: BlendMode.hue,
                          //alignment: Alignment.bottomRight,
                          fit: BoxFit.contain,
                          //scale: .8,
                        ),
                      ),
                    ),
                  ),
                  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;
      print(py);
      px = (px / widgetScale);
      py = (py / widgetScale);
    }

    int pixel32 = photo!.getPixelSafe(px.toInt(), py.toInt());
    int hex = abgrToArgb(pixel32);

    _stateController.add(Color(hex));
  }

  Future<void> loadImageBundleBytes() async {
    ByteData imageBytes = await rootBundle.load(imagePath);
    setImageBytes(imageBytes);
  }

  Future<void> loadSnapshotBytes() async {
    RenderRepaintBoundary boxPaint =
        paintKey.currentContext!.findRenderObject() as RenderRepaintBoundary;
    //RenderObject? boxPaint = paintKey.currentContext.findRenderObject();
    ui.Image capture = await boxPaint.toImage();

    ByteData? imageBytes =
        await capture.toByteData(format: ui.ImageByteFormat.png);
    setImageBytes(imageBytes!);
    capture.dispose();
  }

  void setImageBytes(ByteData imageBytes) {
    List<int> values = imageBytes.buffer.asUint8List();
    photo;
    photo = img.decodeImage(values)!;
  }
}

// image lib uses uses KML color format, convert #AABBGGRR to regular #AARRGGBB
int abgrToArgb(int argbColor) {
  int r = (argbColor >> 16) & 0xFF;
  int b = argbColor & 0xFF;
  return (argbColor & 0xFF00FF00) | (b << 16) | r;
}

相关问题