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

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

Vehicle Color Change

我尝试使用ColorFiltered小部件,但它似乎不适合特定的功能。我正在尝试Canvas,但是绘制需要着色的形状是不可行的,因为我有更多的车辆,并且我认为改变特定十六进制的方法应该是最优化的方法。

1个回答

17

经过一番尝试和错误,我找到了解决方案。源代码和资源文件可在Github存储库中获取。

所需的Pubspec包

# 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.
image: ^2.1.19

# Allows painting & displaying Scalable Vector Graphics 1.1 files
flutter_svg: ^0.19.3

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

栅格化方法

图像颜色切换小部件

import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:image/image.dart' as External;

class ImageColorSwitcher extends StatefulWidget {
 
  /// Holds the Image Path
  final String imagePath;

  /// Holds the MaterialColor
  final MaterialColor color;

  ImageColorSwitcher({this.imagePath, this.color});

  @override
  _ImageColorSwitcherState createState() => _ImageColorSwitcherState();
}

class _ImageColorSwitcherState extends State<ImageColorSwitcher> {
 
  /// Holds the Image in Byte Format
  Uint8List imageBytes;

  @override
  void initState() {
    rootBundle.load(widget.imagePath).then(
        (data) => setState(() => this.imageBytes = data.buffer.asUint8List()));

    super.initState();
  }

  /// A function that switches the image color.
  Future<Uint8List> switchColor(Uint8List bytes) async {
  
    // Decode the bytes to [Image] type
    final image = External.decodeImage(bytes);

    // Convert the [Image] to RGBA formatted pixels
    final pixels = image.getBytes(format: External.Format.rgba);

    // Get the Pixel Length
    final int length = pixels.lengthInBytes;

    for (var i = 0; i < length; i += 4) {
      ///           PIXELS
      /// =============================
      /// | i | i + 1 | i + 2 | i + 3 |
      /// =============================

      // pixels[i] represents Red
      // pixels[i + 1] represents Green
      // pixels[i + 2] represents Blue
      // pixels[i + 3] represents Alpha

      // Detect the light blue color & switch it with the desired color's RGB value.
      if (pixels[i] == 189 && pixels[i + 1] == 212 && pixels[i + 2] == 222) {
        pixels[i] = widget.color.shade300.red;
        pixels[i + 1] = widget.color.shade300.green;
        pixels[i + 2] = widget.color.shade300.blue;
      }
  
      // Detect the darkish blue shade & switch it with the desired color's RGB value.
      else if (pixels[i] == 63 && pixels[i + 1] == 87 && pixels[i + 2] == 101) {
        pixels[i] = widget.color.shade900.red;
        pixels[i + 1] = widget.color.shade900.green;
        pixels[i + 2] = widget.color.shade900.blue;
      }
    }
    return External.encodePng(image);
  }

  @override
  Widget build(BuildContext context) {
    return imageBytes == null
        ? Center(child: CircularProgressIndicator())
        : FutureBuilder(
            future: switchColor(imageBytes),
            builder: (_, AsyncSnapshot<Uint8List> snapshot) {
              return snapshot.hasData
                  ? Container(
                      width: MediaQuery.of(context).size.width * 0.9,
                      decoration: BoxDecoration(
                          image: DecorationImage(
                              image: Image.memory(
                        snapshot.data,
                      ).image)),
                    )
                  : CircularProgressIndicator();
            },
          );
  }
}
  • 我创建了一个有状态的widget,它使用构造函数接收图像路径和所需颜色。

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

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

  • build方法中,如果imageBytes尚未准备好,我显示一个CircularProgressIndicator,否则,FutureBuilder将调用switchColor并返回包含图像的容器。

颜色滑块小部件

import 'package:flutter/material.dart';

/// A Custom Slider that returns a selected color.

class ColorSlider extends StatelessWidget {
 
  /// Map holding the color name with its value
  final Map<String, Color> _colorMap = {
    'Red': Colors.red,
    'Green': Colors.green,
    'Blue': Colors.blue,
    'Light Blue': Colors.lightBlue,
    'Blue Grey': Colors.blueGrey,
    'Brown': Colors.brown,
    'Cyan': Colors.cyan,
    'Purple': Colors.purple,
    'Deep Purple': Colors.deepPurple,
    'Light Green': Colors.lightGreen,
    'Indigo': Colors.indigo,
    'Amber': Colors.amber,
    'Yellow': Colors.yellow,
    'Lime': Colors.lime,
    'Orange': Colors.orange,
    'Dark Orange': Colors.deepOrange,
    'Teal': Colors.teal,
    'Pink': Colors.pink,
    'Black': MaterialColor(
      Colors.black.value,
      {
        50: Colors.black38,
        100: Colors.black38,
        200: Colors.black38,
        300: Colors.grey.shade800,
        400: Colors.black38,
        500: Colors.black38,
        600: Colors.black38,
        700: Colors.black38,
        800: Colors.black38,
        900: Colors.black,
      },
    ),
    'White': MaterialColor(
      Colors.white.value,
      {
        50: Colors.white,
        100: Colors.white,
        200: Colors.white,
        300: Colors.white,
        400: Colors.white,
        500: Colors.white,
        600: Colors.white,
        700: Colors.white,
        800: Colors.white,
        900: Colors.grey.shade700,
      },
    ),
    'Grey': Colors.grey,
  };

  /// Triggers when tapped on a color
  final Function(Color) onColorSelected;

  ColorSlider({@required this.onColorSelected});

  @override
  Widget build(BuildContext context) {
    return ListView(
      scrollDirection: Axis.horizontal,
      children: [
        ..._colorMap.entries.map((MapEntry<String, Color> colorEntry) {
          return InkWell(
            borderRadius: BorderRadius.circular(50.0),
            onTap: () => onColorSelected(colorEntry.value),
            child: Container(
                height: 80,
                width: 80,
                margin: EdgeInsets.all(5.0),
                decoration: BoxDecoration(
                  color: colorEntry.value,
                  shape: BoxShape.circle,
                  boxShadow: [
                    BoxShadow(
                      color: colorEntry.value.withOpacity(0.8),
                      offset: Offset(1.0, 2.0),
                      blurRadius: 3.0,
                    ),
                  ],
                ),
                child: Center(
                    child:
                        // If the color is Black, change font color to white
                        colorEntry.key == 'Black'
                            ? Text(colorEntry.key.toUpperCase(),
                                style: TextStyle(
                                    fontSize: 8.75,
                                    fontWeight: FontWeight.bold,
                                    color: Colors.white))
                            : Text(colorEntry.key.toUpperCase(),
                                style: TextStyle(
                                    fontSize: 8.75,
                                    fontWeight: FontWeight.bold)))),
          );
        })
      ],
    );
  }
}
  • 我声明了一个Map<String, Color> _colorMap,它将保存颜色名称和Color值。

  • build方法中,我创建了一个基于_colorMap条目的ListView

  • 我使用BoxShape.circle将每个colorEntry包装在圆形容器中。

  • 为了点击每种颜色,我使用InkWell小部件来包装每个容器。

  • onTap函数中,我返回所选的映射条目,即Color值。

光栅代码执行

import 'package:flutter/material.dart';
import 'package:image_color_switcher/widgets/color_slider.dart';
import 'package:image_color_switcher/widgets/image_color_switcher.dart';

void main() {
  runApp(MyApp());

  /// Hide the debug banner on the top right corner
  WidgetsApp.debugAllowBannerOverride = false;
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  
  // Holds the Color value returned from [ColorSlider]
  Color colorCode;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Image Color Switcher',
        home: Scaffold(
            body: SafeArea(
                child: Column(children: [
          Expanded(
              child: ImageColorSwitcher(
              imagePath: 'assets/bike.png',
              color: colorCode ?? Colors.red,
          )),
          Expanded(
              child: ColorSlider(
            onColorSelected: (color) => setState(() => colorCode = color),
          )),
        ]))));
  }
}
  • 为了将ColorSliderImageColorSwitcher集成,我声明了一个Color变量ColorCode,并将其赋值为来自ColorSlideronColorSelected回调函数的值。

  • 为避免null值,我将红色设置为默认选定颜色。

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

栅格图像着色

矢量方法

SVG颜色滑块小部件

import 'package:flutter/material.dart';

/// A Custom Slider that returns SVG colors and shades.
class SVGColorSlider extends StatelessWidget {

  /// Map holding the Theme.color:shade with its value
  final _colorMap = {
    'Red.indianred:darkred': Color.fromARGB(255, 255, 0, 0),
    'Green.#22b14c:#004000': Colors.green,
    'Blue.lightskyblue:darkblue': Color.fromARGB(255, 0, 0, 255),
    'Navy.#0000CD:#000080': Color.fromARGB(255, 0, 0, 128),
    'Magenta.#FF00FF:#8B008B': Color.fromARGB(255, 255, 0, 255),
    'Indigo.#9370DB:#4B0082': Color.fromARGB(255, 75, 0, 130),
    'Orange.#FFA500:#FF8C00': Color.fromARGB(255, 255, 165, 0),
    'Turquoise.#40E0D0:#00CED1': Color.fromARGB(255, 64, 224, 208),
    'Purple.#9370DB:#6A0DAD': Colors.purple,
    'Bronze.#CD7F32:#524741': Color.fromARGB(255, 82, 71, 65),
    'Yellow.#FFFF19:#E0E200': Color.fromARGB(255, 255, 255, 0),
    'Burgundy.#9D2735:#800020': Color.fromARGB(255, 128, 0, 32),
    'Brown.chocolate:brown': Color.fromARGB(255, 165, 42, 42),
    'Beige.beige:#d9b382': Color.fromARGB(255, 245, 245, 220),
    'Maroon.#800000:#450000': Color.fromARGB(255, 128, 0, 0),
    'Gold.goldenrod:darkgoldenrod': Color.fromARGB(255, 255, 215, 0),
    'Grey.grey:darkgrey': Color.fromARGB(255, 128, 128, 128),
    'Black.black:#1B1B1B:': Color.fromARGB(255, 0, 0, 0),
    'Silver.#8B8B8B:silver': Color.fromARGB(255, 192, 192, 192),
    // Multiple Options: antiquewhite,floralwhite,ghostwite
    'White.ghostwhite:black': Color.fromARGB(255, 255, 255, 255),
    'Slate.#708090:#284646': Color.fromARGB(255, 47, 79, 79),
  };

  /// Triggers when tapped on a color
  final Function(String) onColorSelected;

  SVGColorSlider({@required this.onColorSelected});

  @override
  Widget build(BuildContext context) {
    return ListView(
      scrollDirection: Axis.horizontal,
      children: [
        ..._colorMap.entries.map((MapEntry<String, Color> mapEntry) {
          return InkWell(
            borderRadius: BorderRadius.circular(50.0),
            onTap: () => onColorSelected(mapEntry.key),
            child: Container(
                height: 80,
                width: 80,
                margin: EdgeInsets.all(5.0),
                decoration: BoxDecoration(
                  color: mapEntry.value,
                  shape: BoxShape.circle,
                  boxShadow: [
                    BoxShadow(
                      color: mapEntry.value,
                      offset: Offset(1.0, 2.0),
                    ),
                  ],
                ),
                child: Center(
                    child:

                        /// Change The Font To Black For These Colors
                        mapEntry.key.contains('White') ||
                                mapEntry.key.contains('Beige') ||
                                mapEntry.key.contains('Yellow')
                            ? Text(
                                mapEntry.key
                                    .split(':')[0]
                                    .split('.')[0]
                                    .toUpperCase(),
                                style: TextStyle(
                                  fontSize: 8.75,
                                  fontWeight: FontWeight.bold,
                                ))
                            :

                            /// Else Let The Font Be white
                            Text(
                                mapEntry.key
                                    .split(':')[0]
                                    .split('.')[0]
                                    .toUpperCase(),
                                style: TextStyle(
                                    fontSize: 8.75,
                                    fontWeight: FontWeight.bold,
                                    color: Colors.white)))),
          );
        })
      ],
    );
  }
}
我声明了一个Map<String, Color> _colorMap,它将保存一个String和一个Color值。 在映射键内,我定义了一个编码字符串Theme.color:shade: ★ Theme: 主题名称。 ★ Color: 颜色名称或十六进制值。 ★ Shade: 阴影名称或十六进制值。 在映射值内,我使用了Color.fromARGB构造函数。 在build方法内,我将_colorMap条目转换为包含在ListView中的圆形容器。 为了显示容器的背景颜色,我使用mapEntry值。 在点击onTap函数时,我返回选定的mapEntry键(编码字符串)而不是Color值。 Bike Painter Widget
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';

class BikePainter extends StatelessWidget {
  final String color, shade;

  BikePainter({@required this.color, @required this.shade});

  @override
  Widget build(BuildContext context) {
    final _bytes =
        '''The code is too long, please visit https://gist.githubusercontent.com/Zujaj/2bad1cb88a5b44e95a6a87a89dd23922/raw/68e9597b0b3ab7dfe68a54154c920c335ed1ae18/bike_painter.dart''';

    return SvgPicture.string(_bytes);
  }
}
  • 我声明了两个String变量,colorshade并将它们传递给了Bike_Painter的构造函数。

  • build方法内,我声明了一个私有变量_bytes来保存SVG代码。

  • 按下ctrl+H搜索十六进制值,然后用变量colorshade替换它们。

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

执行SVG代码

import 'package:flutter/material.dart';
import 'package:image_color_switcher/widgets/bike_painter.dart';
import 'package:image_color_switcher/widgets/svg_color_slider.dart';

void main() {
  runApp(MyApp());

  /// Hide the debug banner on the top right corner
  WidgetsApp.debugAllowBannerOverride = false;
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  // Holds the encoded color string value returned from [SVGColorSlider]
  String colorCode = '';

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Image Color Switcher',
        home: Scaffold(
            body: SafeArea(
                child: Column(children: [
          Expanded(
              child: BikePainter(
                  color: colorCode.isNotEmpty
                      ? colorCode.split('.')[1].split(':')[0]
                      : '#bdd4de',
                  shade: colorCode.isNotEmpty
                      ? colorCode.split('.')[1].split(':')[1]
                      : '#3f5765')),
          Expanded(
              child: SVGColorSlider(
            onColorSelected: (color) => setState(() => colorCode = color),
          )),
        ]))));
  }
}

我将BikePainterSVGColorSlider小部件集成到main.dart文件中。

矢量图像着色

结果比较

下面的图表说明了从两种方法获得的不同之处。

结果比较

参考文献

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

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


Vector对我来说非常有效 - Samira Huber

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接