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

如何在Flutter中绘制CIE色度图?

Flutter绘制CIE色度图的可行方案

核心思路

CIE 1931色度图的核心是光谱轨迹(马蹄形边界)和紫线(连接380nm与780nm的直线),实现逻辑和Python colour库一致,只需把绘图方式替换为Flutter的Canvas API,分为「预计算边界坐标点」和「用CustomPaint绘制路径」两部分。


1. 预计算色域边界坐标

实现波长转色度坐标

根据CIE 1931标准观察者的颜色匹配函数,将380nm~780nm的波长转换为XYZ三刺激值,再推导为(x,y)色度坐标:

x = X/(X+Y+Z)
y = Y/(X+Y+Z)

把你找到的公式直接翻译成Dart函数,遍历波长(步长取1~5nm,步长越小轨迹越平滑),收集所有(x,y)点。最后补充紫线:将780nm的点与380nm的点连接,闭合路径。

预计算优化

在initState中完成坐标计算,避免每次绘制重复运算:

late List<Offset> boundaryPoints;

@override
void initState() {
  super.initState();
  boundaryPoints = _generateCIEBoundary();
}

List<Offset> _generateCIEBoundary() {
  List<Offset> points = [];
  // 遍历380~780nm,计算每个波长的(x,y)
  for (int lambda = 380; lambda <= 780; lambda += 5) {
    double X = _calculateX(lambda);
    double Y = _calculateY(lambda);
    double Z = _calculateZ(lambda);
    double sum = X + Y + Z;
    if (sum == 0) continue;
    points.add(Offset(X / sum, Y / sum));
  }
  // 闭合紫线
  points.add(points.first);
  return points;
}

// 补充完整的X/Y/Z计算函数(对应你找到的CIE公式)
double _calculateX(int lambda) { /* 实现公式逻辑 */ }
double _calculateY(int lambda) { /* 实现公式逻辑 */ }
double _calculateZ(int lambda) { /* 实现公式逻辑 */ }

2. 用CustomPaint绘制轨迹

将预计算的(x,y)坐标映射到Flutter Canvas的像素坐标(注意Flutter坐标系y轴向下,需翻转y值),再绘制路径:

class CIECustomPainter extends CustomPainter {
  final List<Offset> boundaryPoints;
  final double scale;
  final Offset offset;

  CIECustomPainter(this.boundaryPoints, this.scale, this.offset);

  @override
  void paint(Canvas canvas, Size size) {
    // 绘制边界轨迹
    final strokePaint = Paint()
      ..color = Colors.black
      ..style = PaintingStyle.stroke
      ..strokeWidth = 2;

    Path path = Path();
    for (var point in boundaryPoints) {
      // 坐标转换:缩放 + 翻转y轴 + 平移居中
      double canvasX = point.dx * scale + offset.dx;
      double canvasY = size.height - point.dy * scale + offset.dy;
      path.isEmpty ? path.moveTo(canvasX, canvasY) : path.lineTo(canvasX, canvasY);
    }
    canvas.drawPath(path, strokePaint);

    // 绘制D65标准白点
    final whitePoint = Offset(0.3127, 0.3290);
    double wpX = whitePoint.dx * scale + offset.dx;
    double wpY = size.height - whitePoint.dy * scale + offset.dy;
    canvas.drawCircle(Offset(wpX, wpY), 4, Paint()..color = Colors.red);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}

适配Widget尺寸

用LayoutBuilder获取可用空间,计算缩放因子和平移量,让图表居中显示:

@override
Widget build(BuildContext context) {
  return LayoutBuilder(builder: (context, constraints) {
    double maxWidth = constraints.maxWidth;
    double maxHeight = constraints.maxHeight;
    // 按x轴(0~0.8)和y轴(0~0.9)的范围计算缩放比例
    double scale = min(maxWidth / 0.8, maxHeight / 0.9);
    // 平移到画布中心
    Offset offset = Offset((maxWidth - 0.8 * scale) / 2, (maxHeight - 0.9 * scale) / 2);
    return CustomPaint(
      painter: CIECustomPainter(boundaryPoints, scale, offset),
      size: Size(maxWidth, maxHeight),
    );
  });
}

3. 进阶优化

  • 色域填充:若需填充色域内部颜色,可将每个(x,y)转成RGB(超出sRGB范围的颜色需做色域映射),用Shader实现渐变填充。
  • 交互支持:如需点击查看颜色信息,可将Canvas坐标反向映射回(x,y),再转换为对应颜色值。
  • 性能优化:将边界坐标缓存为静态常量,避免重复计算。

内容的提问来源于stack exchange,提问作者Phong Nguyễn

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 19:55:37