如何在 Flutter 中切换图像的特定颜色

Any*_*han 11 canvas paint colors dart flutter

任务是简单地获取车辆图像的默认十六进制颜色(先前已知 - 在本例中为#bdd4de)并将其动态切换为用户选择的颜色。对于阴影,我可以简单地重复此过程并将其更改为所选颜色的较暗版本。

车辆颜色改变

我尝试过使用 ColorFiltered 小部件,但它似乎不适合提到的特定功能。我正在考虑尝试画布,但是绘制需要着色的形状是不可行的,因为我有更多的车辆,并且我认为更改特定六角形的方法应该是最佳方法。

Any*_*han 18

经过反复试验,我找到了解决方案。源代码和资产文件可在Github 存储库上获取。

\n

所需的 Pubspec 包

\n
# Provides server & web apps with the ability to load, manipulate and save images with various image file formats PNG, JPEG, GIF, BMP, WebP, TIFF, TGA, PSD, PVR, and OpenEXR.\nimage: ^2.1.19\n\n# Allows painting & displaying Scalable Vector Graphics 1.1 files\nflutter_svg: ^0.19.3\n
Run Code Online (Sandbox Code Playgroud)\n

以下是我在研究过程中发现的两种方法。

\n

光栅方法

\n

图像颜色切换器小部件

\n
# Provides server & web apps with the ability to load, manipulate and save images with various image file formats PNG, JPEG, GIF, BMP, WebP, TIFF, TGA, PSD, PVR, and OpenEXR.\nimage: ^2.1.19\n\n# Allows painting & displaying Scalable Vector Graphics 1.1 files\nflutter_svg: ^0.19.3\n
Run Code Online (Sandbox Code Playgroud)\n
    \n
  • 我创建了一个有状态小部件,它将使用构造函数获取图像路径和所需的颜色。

    \n
  • \n
  • 在该initState方法中,我加载图像并imageBytes使用该setState函数将原始字节分配给变量。

    \n
  • \n
  • 接下来,我创建了一个自定义异步函数switchColor,它将Uint8List字节作为参数,检测 RGB 值,将其切换为所需的颜色并返回编码的 png 图像。

    \n
  • \n
  • 在方法内部build,如果imageBytes尚未准备好,我会显示一个CircularProgressIndicatorelse,aFutureBuilder将调用switchColor 并返回容器化图像。

    \n
  • \n
\n

颜色滑块小部件

\n
import \'dart:typed_data\';\nimport \'package:flutter/material.dart\';\nimport \'package:flutter/services.dart\';\nimport \'package:image/image.dart\' as External;\n\nclass ImageColorSwitcher extends StatefulWidget {\n \n  /// Holds the Image Path\n  final String imagePath;\n\n  /// Holds the MaterialColor\n  final MaterialColor color;\n\n  ImageColorSwitcher({this.imagePath, this.color});\n\n  @override\n  _ImageColorSwitcherState createState() => _ImageColorSwitcherState();\n}\n\nclass _ImageColorSwitcherState extends State<ImageColorSwitcher> {\n \n  /// Holds the Image in Byte Format\n  Uint8List imageBytes;\n\n  @override\n  void initState() {\n    rootBundle.load(widget.imagePath).then(\n        (data) => setState(() => this.imageBytes = data.buffer.asUint8List()));\n\n    super.initState();\n  }\n\n  /// A function that switches the image color.\n  Future<Uint8List> switchColor(Uint8List bytes) async {\n  \n    // Decode the bytes to [Image] type\n    final image = External.decodeImage(bytes);\n\n    // Convert the [Image] to RGBA formatted pixels\n    final pixels = image.getBytes(format: External.Format.rgba);\n\n    // Get the Pixel Length\n    final int length = pixels.lengthInBytes;\n\n    for (var i = 0; i < length; i += 4) {\n      ///           PIXELS\n      /// =============================\n      /// | i | i + 1 | i + 2 | i + 3 |\n      /// =============================\n\n      // pixels[i] represents Red\n      // pixels[i + 1] represents Green\n      // pixels[i + 2] represents Blue\n      // pixels[i + 3] represents Alpha\n\n      // Detect the light blue color & switch it with the desired color\'s RGB value.\n      if (pixels[i] == 189 && pixels[i + 1] == 212 && pixels[i + 2] == 222) {\n        pixels[i] = widget.color.shade300.red;\n        pixels[i + 1] = widget.color.shade300.green;\n        pixels[i + 2] = widget.color.shade300.blue;\n      }\n  \n      // Detect the darkish blue shade & switch it with the desired color\'s RGB value.\n      else if (pixels[i] == 63 && pixels[i + 1] == 87 && pixels[i + 2] == 101) {\n        pixels[i] = widget.color.shade900.red;\n        pixels[i + 1] = widget.color.shade900.green;\n        pixels[i + 2] = widget.color.shade900.blue;\n      }\n    }\n    return External.encodePng(image);\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return imageBytes == null\n        ? Center(child: CircularProgressIndicator())\n        : FutureBuilder(\n            future: switchColor(imageBytes),\n            builder: (_, AsyncSnapshot<Uint8List> snapshot) {\n              return snapshot.hasData\n                  ? Container(\n                      width: MediaQuery.of(context).size.width * 0.9,\n                      decoration: BoxDecoration(\n                          image: DecorationImage(\n                              image: Image.memory(\n                        snapshot.data,\n                      ).image)),\n                    )\n                  : CircularProgressIndicator();\n            },\n          );\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n
    \n
  • 我声明了一个Map<String, Color> _colorMap保存颜色名称和颜色值的对象。

    \n
  • \n
  • 在该build方法内部,我ListView根据_colorMap.

    \n
  • \n
  • colorEntry用 . 将每个包裹在一个圆形容器中BoxShape.circle

    \n
  • \n
  • 为了点击每种颜色,我将每个容器包装在InkWell小部件中。

    \n
  • \n
  • 在函数内部onTap,我返回了选定的映射条目,即值Color

    \n
  • \n
\n

光栅代码执行

\n
import \'package:flutter/material.dart\';\n\n/// A Custom Slider that returns a selected color.\n\nclass ColorSlider extends StatelessWidget {\n \n  /// Map holding the color name with its value\n  final Map<String, Color> _colorMap = {\n    \'Red\': Colors.red,\n    \'Green\': Colors.green,\n    \'Blue\': Colors.blue,\n    \'Light Blue\': Colors.lightBlue,\n    \'Blue Grey\': Colors.blueGrey,\n    \'Brown\': Colors.brown,\n    \'Cyan\': Colors.cyan,\n    \'Purple\': Colors.purple,\n    \'Deep Purple\': Colors.deepPurple,\n    \'Light Green\': Colors.lightGreen,\n    \'Indigo\': Colors.indigo,\n    \'Amber\': Colors.amber,\n    \'Yellow\': Colors.yellow,\n    \'Lime\': Colors.lime,\n    \'Orange\': Colors.orange,\n    \'Dark Orange\': Colors.deepOrange,\n    \'Teal\': Colors.teal,\n    \'Pink\': Colors.pink,\n    \'Black\': MaterialColor(\n      Colors.black.value,\n      {\n        50: Colors.black38,\n        100: Colors.black38,\n        200: Colors.black38,\n        300: Colors.grey.shade800,\n        400: Colors.black38,\n        500: Colors.black38,\n        600: Colors.black38,\n        700: Colors.black38,\n        800: Colors.black38,\n        900: Colors.black,\n      },\n    ),\n    \'White\': MaterialColor(\n      Colors.white.value,\n      {\n        50: Colors.white,\n        100: Colors.white,\n        200: Colors.white,\n        300: Colors.white,\n        400: Colors.white,\n        500: Colors.white,\n        600: Colors.white,\n        700: Colors.white,\n        800: Colors.white,\n        900: Colors.grey.shade700,\n      },\n    ),\n    \'Grey\': Colors.grey,\n  };\n\n  /// Triggers when tapped on a color\n  final Function(Color) onColorSelected;\n\n  ColorSlider({@required this.onColorSelected});\n\n  @override\n  Widget build(BuildContext context) {\n    return ListView(\n      scrollDirection: Axis.horizontal,\n      children: [\n        ..._colorMap.entries.map((MapEntry<String, Color> colorEntry) {\n          return InkWell(\n            borderRadius: BorderRadius.circular(50.0),\n            onTap: () => onColorSelected(colorEntry.value),\n            child: Container(\n                height: 80,\n                width: 80,\n                margin: EdgeInsets.all(5.0),\n                decoration: BoxDecoration(\n                  color: colorEntry.value,\n                  shape: BoxShape.circle,\n                  boxShadow: [\n                    BoxShadow(\n                      color: colorEntry.value.withOpacity(0.8),\n                      offset: Offset(1.0, 2.0),\n                      blurRadius: 3.0,\n                    ),\n                  ],\n                ),\n                child: Center(\n                    child:\n                        // If the color is Black, change font color to white\n                        colorEntry.key == \'Black\'\n                            ? Text(colorEntry.key.toUpperCase(),\n                                style: TextStyle(\n                                    fontSize: 8.75,\n                                    fontWeight: FontWeight.bold,\n                                    color: Colors.white))\n                            : Text(colorEntry.key.toUpperCase(),\n                                style: TextStyle(\n                                    fontSize: 8.75,\n                                    fontWeight: FontWeight.bold)))),\n          );\n        })\n      ],\n    );\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n
    \n
  • 为了将ColorSlider与 集成ImageColorSwitcher,我声明了一个Color变量ColorCode并为其分配了来自回调函数的值ColorSlider\xe2\x80\x99s onColorSelected

    \n
  • \n
  • 为了避免null值,我将红色设置为默认选择的颜色。

    \n
  • \n
  • 最后,我将这两个自定义小部件包装在一个Column小部件中。

    \n
  • \n
\n

光栅图像着色

\n

矢量方法

\n

SVG 颜色滑块小部件

\n
import \'package:flutter/material.dart\';\nimport \'package:image_color_switcher/widgets/color_slider.dart\';\nimport \'package:image_color_switcher/widgets/image_color_switcher.dart\';\n\nvoid main() {\n  runApp(MyApp());\n\n  /// Hide the debug banner on the top right corner\n  WidgetsApp.debugAllowBannerOverride = false;\n}\n\nclass MyApp extends StatefulWidget {\n  @override\n  _MyAppState createState() => _MyAppState();\n}\n\nclass _MyAppState extends State<MyApp> {\n  \n  // Holds the Color value returned from [ColorSlider]\n  Color colorCode;\n\n  @override\n  Widget build(BuildContext context) {\n    return MaterialApp(\n        title: \'Image Color Switcher\',\n        home: Scaffold(\n            body: SafeArea(\n                child: Column(children: [\n          Expanded(\n              child: ImageColorSwitcher(\n              imagePath: \'assets/bike.png\',\n              color: colorCode ?? Colors.red,\n          )),\n          Expanded(\n              child: ColorSlider(\n            onColorSelected: (color) => setState(() => colorCode = color),\n          )),\n        ]))));\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n
    \n
  • 我声明了 aMap<String, Color> _colorMap将保存 a String& aColor值。

    \n
  • \n
  • 在地图键内,我Theme.color:shade同样定义了一个编码字符串:\n\xe2\x98\x85 主题:主题的名称。\n\xe2\x98\x85 颜色:颜色的名称或十六进制值。\n\xe2\ x98\x85 阴影:阴影的名称或十六进制值。

    \n
  • \n
  • 在地图值中,我使用了Color.fromARGB constructor.

    \n
  • \n
  • 在该build方法内部,我将_colorMap条目转换为用ListView.

    \n
  • \n
  • 为了显示容器\xe2\x80\x99s的背景颜色,我使用了mapEntry值。

    \n
  • \n
  • 点击该onTap函数后,我返回了选定的mapEntry键(编码字符串)而不是Color 值。

    \n
  • \n
\n

自行车画家小部件

\n
import \'package:flutter/material.dart\';\n\n/// A Custom Slider that returns SVG colors and shades.\nclass SVGColorSlider extends StatelessWidget {\n\n  /// Map holding the Theme.color:shade with its value\n  final _colorMap = {\n    \'Red.indianred:darkred\': Color.fromARGB(255, 255, 0, 0),\n    \'Green.#22b14c:#004000\': Colors.green,\n    \'Blue.lightskyblue:darkblue\': Color.fromARGB(255, 0, 0, 255),\n    \'Navy.#0000CD:#000080\': Color.fromARGB(255, 0, 0, 128),\n    \'Magenta.#FF00FF:#8B008B\': Color.fromARGB(255, 255, 0, 255),\n    \'Indigo.#9370DB:#4B0082\': Color.fromARGB(255, 75, 0, 130),\n    \'Orange.#FFA500:#FF8C00\': Color.fromARGB(255, 255, 165, 0),\n    \'Turquoise.#40E0D0:#00CED1\': Color.fromARGB(255, 64, 224, 208),\n    \'Purple.#9370DB:#6A0DAD\': Colors.purple,\n    \'Bronze.#CD7F32:#524741\': Color.fromARGB(255, 82, 71, 65),\n    \'Yellow.#FFFF19:#E0E200\': Color.fromARGB(255, 255, 255, 0),\n    \'Burgundy.#9D2735:#800020\': Color.fromARGB(255, 128, 0, 32),\n    \'Brown.chocolate:brown\': Color.fromARGB(255, 165, 42, 42),\n    \'Beige.beige:#d9b382\': Color.fromARGB(255, 245, 245, 220),\n    \'Maroon.#800000:#450000\': Color.fromARGB(255, 128, 0, 0),\n    \'Gold.goldenrod:darkgoldenrod\': Color.fromARGB(255, 255, 215, 0),\n    \'Grey.grey:darkgrey\': Color.fromARGB(255, 128, 128, 128),\n    \'Black.black:#1B1B1B:\': Color.fromARGB(255, 0, 0, 0),\n    \'Silver.#8B8B8B:silver\': Color.fromARGB(255, 192, 192, 192),\n    // Multiple Options: antiquewhite,floralwhite,ghostwite\n    \'White.ghostwhite:black\': Color.fromARGB(255, 255, 255, 255),\n    \'Slate.#708090:#284646\': Color.fromARGB(255, 47, 79, 79),\n  };\n\n  /// Triggers when tapped on a color\n  final Function(String) onColorSelected;\n\n  SVGColorSlider({@required this.onColorSelected});\n\n  @override\n  Widget build(BuildContext context) {\n    return ListView(\n      scrollDirection: Axis.horizontal,\n      children: [\n        ..._colorMap.entries.map((MapEntry<String, Color> mapEntry) {\n          return InkWell(\n            borderRadius: BorderRadius.circular(50.0),\n            onTap: () => onColorSelected(mapEntry.key),\n            child: Container(\n                height: 80,\n                width: 80,\n                margin: EdgeInsets.all(5.0),\n                decoration: BoxDecoration(\n                  color: mapEntry.value,\n                  shape: BoxShape.circle,\n                  boxShadow: [\n                    BoxShadow(\n                      color: mapEntry.value,\n                      offset: Offset(1.0, 2.0),\n                    ),\n                  ],\n                ),\n                child: Center(\n                    child:\n\n                        /// Change The Font To Black For These Colors\n                        mapEntry.key.contains(\'White\') ||\n                                mapEntry.key.contains(\'Beige\') ||\n                                mapEntry.key.contains(\'Yellow\')\n                            ? Text(\n                                mapEntry.key\n                                    .split(\':\')[0]\n                                    .split(\'.\')[0]\n                                    .toUpperCase(),\n                                style: TextStyle(\n                                  fontSize: 8.75,\n                                  fontWeight: FontWeight.bold,\n                                ))\n                            :\n\n                            /// Else Let The Font Be white\n                            Text(\n                                mapEntry.key\n                                    .split(\':\')[0]\n                                    .split(\'.\')[0]\n                                    .toUpperCase(),\n                                style: TextStyle(\n                                    fontSize: 8.75,\n                                    fontWeight: FontWeight.bold,\n                                    color: Colors.white)))),\n          );\n        })\n      ],\n    );\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n
    \n
  • 我声明了两个String变量color&shade并将它们传递给Bike_Painter\xe2\x80\x99s构造函数。

    \n
  • \n
  • 在该方法内部,我声明了一个用于保存 SVG 代码的build私有变量。_bytes

    \n
  • \n
  • 点击ctrl+H搜索十六进制值并将其替换为变量color& shade

    \n
  • \n
  • 最后,我将_bytes变量传递给SvgPicture.string构造函数。

    \n
  • \n
\n

SVG 代码执行

\n
import \'package:flutter/material.dart\';\nimport \'package:flutter_svg/svg.dart\';\n\nclass BikePainter extends StatelessWidget {\n  final String color, shade;\n\n  BikePainter({@required this.color, @required this.shade});\n\n  @override\n  Widget build(BuildContext context) {\n    final _bytes =\n        \'\'\'The code is too long, please visit https://gist.githubusercontent.com/Zujaj/2bad1cb88a5b44e95a6a87a89dd23922/raw/68e9597b0b3ab7dfe68a54154c920c335ed1ae18/bike_painter.dart\'\'\';\n\n    return SvgPicture.string(_bytes);\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

我将BikePainter&SVGColorSlider小部件集成到文件中main.dart

\n

矢量图像着色

\n

结果比较

\n

下图说明了两种方法的差异。

\n

结果比较

\n

参考

\n

1:Flutter 中的 ImageColorSwitcher:第 1 部分光栅图像着色

\n

2:Flutter 中的 ImageColorSwitcher:第 2 部分矢量图像着色

\n