You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter中如何对比两种颜色的相似度与差异?

在Flutter中实现颜色对比与匹配工具

要开发类似颜色匹配工具的Flutter应用,核心是实现颜色色差计算,再搭配颜色选择、结果展示的UI组件即可。以下是具体实现步骤:

核心:颜色对比函数

专业的颜色对比通常采用CIEDE2000色差公式,它能准确反映人眼对颜色差异的感知,比简单的RGB差值更精准。下面是基于Dart实现的CIEDE2000计算函数:

import 'dart:math';

// 将RGB颜色转换为Lab色彩空间
List<double> rgbToLab(int r, int g, int b) {
  // 先转换为XYZ空间
  double rLinear = r / 255.0;
  double gLinear = g / 255.0;
  double bLinear = b / 255.0;

  rLinear = rLinear > 0.04045 ? pow((rLinear + 0.055) / 1.055, 2.4) : rLinear / 12.92;
  gLinear = gLinear > 0.04045 ? pow((gLinear + 0.055) / 1.055, 2.4) : gLinear / 12.92;
  bLinear = bLinear > 0.04045 ? pow((bLinear + 0.055) / 1.055, 2.4) : bLinear / 12.92;

  double x = rLinear * 0.4124 + gLinear * 0.3576 + bLinear * 0.1805;
  double y = rLinear * 0.2126 + gLinear * 0.7152 + bLinear * 0.0722;
  double z = rLinear * 0.0193 + gLinear * 0.1192 + bLinear * 0.9505;

  // 转换为Lab空间
  x /= 0.95047;
  y /= 1.0;
  z /= 1.08883;

  x = x > 0.008856 ? pow(x, 1/3) : (7.787 * x) + 16/116;
  y = y > 0.008856 ? pow(y, 1/3) : (7.787 * y) + 16/116;
  z = z > 0.008856 ? pow(z, 1/3) : (7.787 * z) + 16/116;

  double l = (116 * y) - 16;
  double a = 500 * (x - y);
  double b = 200 * (y - z);

  return [l, a, b];
}

// CIEDE2000色差计算函数,返回两个颜色的差值,值越小越相似
double calculateColorDifference(Color color1, Color color2) {
  List<double> lab1 = rgbToLab(color1.red, color1.green, color1.blue);
  List<double> lab2 = rgbToLab(color2.red, color2.green, color2.blue);

  double l1 = lab1[0], a1 = lab1[1], b1 = lab1[2];
  double l2 = lab2[0], a2 = lab2[1], b2 = lab2[2];

  double c1 = sqrt(pow(a1, 2) + pow(b1, 2));
  double c2 = sqrt(pow(a2, 2) + pow(b2, 2));
  double cAvg = (c1 + c2) / 2;

  double g = 0.5 * (1 - sqrt(pow(cAvg, 7) / (pow(cAvg, 7) + pow(25, 7))));
  double a1Prime = a1 * (1 + g);
  double a2Prime = a2 * (1 + g);

  double c1Prime = sqrt(pow(a1Prime, 2) + pow(b1, 2));
  double c2Prime = sqrt(pow(a2Prime, 2) + pow(b2, 2));

  double h1Prime = atan2(b1, a1Prime);
  h1Prime = h1Prime < 0 ? h1Prime + 2 * pi : h1Prime;
  double h2Prime = atan2(b2, a2Prime);
  h2Prime = h2Prime < 0 ? h2Prime + 2 * pi : h2Prime;

  double deltaLPrime = l2 - l1;
  double deltaCPrime = c2Prime - c1Prime;

  double deltaHPrime;
  if (c1Prime * c2Prime == 0) {
    deltaHPrime = 0;
  } else {
    if (abs(h2Prime - h1Prime) <= pi) {
      deltaHPrime = h2Prime - h1Prime;
    } else {
      deltaHPrime = h2Prime - h1Prime > 0 ? h2Prime - h1Prime - 2 * pi : h2Prime - h1Prime + 2 * pi;
    }
  }

  double deltaHPrimeSquared = pow(deltaHPrime, 2);
  double deltaH = 2 * sqrt(c1Prime * c2Prime) * sin(deltaHPrime / 2);

  double lAvgPrime = (l1 + l2) / 2;
  double cAvgPrime = (c1Prime + c2Prime) / 2;

  double hAvgPrime;
  if (c1Prime * c2Prime == 0) {
    hAvgPrime = h1Prime + h2Prime;
  } else {
    if (abs(h1Prime - h2Prime) <= pi) {
      hAvgPrime = (h1Prime + h2Prime) / 2;
    } else {
      hAvgPrime = (h1Prime + h2Prime) / 2 > pi ? (h1Prime + h2Prime) / 2 - pi : (h1Prime + h2Prime) / 2 + pi;
    }
  }

  double t = 1 - 0.17 * cos(hAvgPrime - pi/6) + 0.24 * cos(2 * hAvgPrime) + 0.32 * cos(3 * hAvgPrime + pi/30) - 0.20 * cos(4 * hAvgPrime - 63 * pi/180);

  double deltaTheta = 30 * pi/180 * exp(-pow((hAvgPrime - 275 * pi/180) / (25 * pi/180), 2));
  double rC = 2 * sqrt(pow(cAvgPrime, 7) / (pow(cAvgPrime, 7) + pow(25, 7)));
  double rT = -sin(2 * deltaTheta) * rC;

  double sl = 1 + (0.015 * pow(lAvgPrime - 50, 2)) / sqrt(20 + pow(lAvgPrime - 50, 2));
  double sc = 1 + 0.045 * cAvgPrime;
  double sh = 1 + 0.015 * cAvgPrime * t;

  double deltaE = sqrt(
    pow(deltaLPrime / sl, 2) +
    pow(deltaCPrime / sc, 2) +
    pow(deltaH / sh, 2) +
    rT * (deltaCPrime / sc) * (deltaH / sh)
  );

  return deltaE;
}

这个函数接收两个Color对象,返回CIEDE2000色差数值:

  • 差值≤1:人眼几乎无法区分
  • 1<差值≤3:轻微差异
  • 3<差值≤10:明显差异
  • 差值>10:显著差异

UI实现示例

结合颜色选择器和结果展示,快速搭建核心界面:

  1. 先在pubspec.yaml中添加颜色选择器依赖:
dependencies:
  flutter:
    sdk: flutter
  flutter_colorpicker: ^1.0.3
  1. 实现主界面:
import 'package:flutter/material.dart';
import 'package:flutter_colorpicker/flutter_colorpicker.dart';

void main() => runApp(const ColorMatcherApp());

class ColorMatcherApp extends StatelessWidget {
  const ColorMatcherApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '颜色匹配工具',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const ColorMatcherPage(),
    );
  }
}

class ColorMatcherPage extends StatefulWidget {
  const ColorMatcherPage({super.key});

  @override
  State<ColorMatcherPage> createState() => _ColorMatcherPageState();
}

class _ColorMatcherPageState extends State<ColorMatcherPage> {
  Color _color1 = Colors.red;
  Color _color2 = Colors.blue;
  double _colorDifference = 0;

  @override
  void initState() {
    super.initState();
    _updateDifference();
  }

  void _updateDifference() {
    setState(() {
      _colorDifference = calculateColorDifference(_color1, _color2);
    });
  }

  void _showColorPicker(Color currentColor, Function(Color) onChanged) {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('选择颜色'),
        content: SingleChildScrollView(
          child: ColorPicker(
            pickerColor: currentColor,
            onColorChanged: onChanged,
            showLabel: true,
            pickerAreaHeightPercent: 0.8,
          ),
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: const Text('确定'),
          ),
        ],
      ),
    ).then((_) => _updateDifference());
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('颜色匹配工具')),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            // 颜色展示区域
            Row(
              children: [
                Expanded(
                  child: GestureDetector(
                    onTap: () => _showColorPicker(_color1, (color) => setState(() => _color1 = color)),
                    child: Container(
                      height: 150,
                      color: _color1,
                      child: Center(
                        child: Text(
                          '颜色1\n#${_color1.value.toRadixString(16).substring(2).toUpperCase()}',
                          style: TextStyle(
                            color: _color1.computeLuminance() > 0.5 ? Colors.black : Colors.white,
                            fontWeight: FontWeight.bold,
                          ),
                          textAlign: TextAlign.center,
                        ),
                      ),
                    ),
                  ),
                ),
                const SizedBox(width: 16),
                Expanded(
                  child: GestureDetector(
                    onTap: () => _showColorPicker(_color2, (color) => setState(() => _color2 = color)),
                    child: Container(
                      height: 150,
                      color: _color2,
                      child: Center(
                        child: Text(
                          '颜色2\n#${_color2.value.toRadixString(16).substring(2).toUpperCase()}',
                          style: TextStyle(
                            color: _color2.computeLuminance() > 0.5 ? Colors.black : Colors.white,
                            fontWeight: FontWeight.bold,
                          ),
                          textAlign: TextAlign.center,
                        ),
                      ),
                    ),
                  ),
                ),
              ],
            ),
            const SizedBox(height: 32),
            // 色差结果展示
            Card(
              elevation: 4,
              child: Padding(
                padding: const EdgeInsets.all(16.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    const Text('色差分析', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
                    const SizedBox(height: 8),
                    Text('CIEDE2000差值: ${_colorDifference.toStringAsFixed(2)}'),
                    const SizedBox(height: 8),
                    Text(_getDifferenceDescription(_colorDifference)),
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }

  String _getDifferenceDescription(double deltaE) {
    if (deltaE <= 1) return '人眼几乎无法区分这两种颜色';
    if (deltaE <= 3) return '两种颜色存在轻微差异';
    if (deltaE <= 10) return '两种颜色差异明显';
    return '两种颜色差异显著';
  }
}

扩展功能建议

  • 添加颜色和谐方案:计算互补色、类似色、分裂互补色等,可基于HSL色彩空间实现
  • 支持颜色格式转换:在RGB、HEX、HSL之间切换显示
  • 增加对比度检测:判断文本在背景色上的可读性(符合WCAG标准)
  • 保存配色方案:允许用户收藏常用的颜色组合

内容的提问来源于stack exchange,提问作者Michal Ferrari

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 04:00:27