如何在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
相关产品推荐
相关产品推荐

