Flutter Delaunay三角剖分插件delaunay的使用
Flutter Delaunay三角剖分插件delaunay的使用
简介
delaunay
是一个用于Dart开发者的Delaunay三角剖分库。该实现改编自 Delaunator JavaScript 库。
使用方法
基本用法
以下是一个简单的使用示例:
import 'package:delaunay/delaunay.dart';
void main() {
// 定义点集
Float32List points = Float32List.fromList(<double>[
143.0, 178.5,
50.2, -100.7,
// 添加更多点...
]);
// 创建Delaunay对象
Delaunay delaunay = Delaunay(points);
// 更新三角剖分
delaunay.update();
// 遍历三角形
for (int i = 0; i < delaunay.triangles.length; i += 3) {
int a = delaunay.triangles[i];
int b = delaunay.triangles[i + 1];
int c = delaunay.triangles[i + 2];
double ax = delaunay.coords[2 * a];
double ay = delaunay.coords[2 * a + 1];
double bx = delaunay.coords[2 * b];
double by = delaunay.coords[2 * b + 1];
double cx = delaunay.coords[2 * c];
double cy = delaunay.coords[2 * c + 1];
// 处理三角形顶点
print('Triangle vertices: ($ax, $ay), ($bx, $by), ($cx, $cy)');
}
// 修改点集并重新更新三角剖分
points[0] = 140.0;
delaunay.update();
}
特性和Bug
请在 GitHub issue tracker 上提交功能请求和Bug报告。
示例代码
以下是一个更复杂的示例,该示例从输入图像中提取颜色,并生成一个包含随机点的Delaunay三角剖分的PNG文件。
示例代码
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.12
// 这个程序创建一个PNG文件,其中包含从输入图像中提取颜色的随机点的Delaunay三角剖分。
//
// 运行 'dart delaunay_example.dart --help' 获取详细信息。
import 'dart:async';
import 'dart:io';
import 'dart:math';
import 'package:args/args.dart';
import 'package:delaunay/delaunay.dart';
import 'package:image/image.dart' as image;
const String description =
'delaunay_example.dart: 一个示例程序,创建一个包含从输入图像中提取颜色的随机点的Delaunay三角剖分的PNG文件。';
Future<int> main(List<String> args) async {
final ArgParser argParser = ArgParser()
..addFlag(
'help',
abbr: 'h',
help: '打印帮助信息',
negatable: false,
)
..addFlag(
'verbose',
abbr: 'v',
help: '详细输出',
negatable: false,
)
..addOption(
'input',
abbr: 'i',
help: '从中提取三角形颜色的输入图像',
)
..addOption(
'output',
abbr: 'o',
help: '输出文件路径',
defaultsTo: 'delaunay.png',
)
..addOption(
'points',
abbr: 'p',
help: '点的数量',
defaultsTo: '1000',
)
..addOption(
'seed',
abbr: 's',
help: '随机数生成器种子',
defaultsTo: '42',
);
final ArgResults argResults = argParser.parse(args);
final Options? options = Options.fromArgResults(argResults);
if (options == null || options.help) {
stderr.writeln(description);
stderr.writeln();
stderr.writeln(argParser.usage);
return options == null ? 1 : 0;
}
if (options.inputImage == null) {
return 1;
}
final image.Image inputImage = options.inputImage!;
final Random r = Random(options.seed);
const double minX = 0.0;
final double maxX = inputImage.width.toDouble();
const double minY = 0.0;
final double maxY = inputImage.height.toDouble();
final image.Image img = image.Image(
inputImage.width,
inputImage.height,
);
final int numPoints = options.points;
final List<Point<double>> points = <Point<double>>[];
for (int i = 0; i < numPoints; i++) {
points.add(Point<double>(r.nextDouble() * maxX, r.nextDouble() * maxY));
}
points.add(const Point<double>(minX, minY));
points.add(const Point<double>(minX, maxY));
points.add(const Point<double>(maxX, minY));
points.add(const Point<double>(maxX, maxY));
final Delaunay triangulator = Delaunay.from(points);
final Stopwatch sw = Stopwatch()..start();
triangulator.initialize();
if (options.verbose) {
print('Triangulator initialized in ${sw.elapsedMilliseconds}ms.');
}
sw.reset();
sw.start();
triangulator.processAllPoints();
if (options.verbose) {
print('Triangulated with ${triangulator.triangles.length ~/ 3} triangles '
'in ${sw.elapsedMilliseconds}ms');
}
sw.reset();
sw.start();
for (int i = 0; i < triangulator.triangles.length; i += 3) {
final Point<double> a = triangulator.getPoint(
triangulator.triangles[i],
);
final Point<double> b = triangulator.getPoint(
triangulator.triangles[i + 1],
);
final Point<double> c = triangulator.getPoint(
triangulator.triangles[i + 2],
);
final int color = inputImage.getPixel(
(a.x.toInt() + b.x.toInt() + c.x.toInt()) ~/ 3,
(a.y.toInt() + b.y.toInt() + c.y.toInt()) ~/ 3,
);
drawTriangle(
img,
a.x.round(), a.y.round(),
b.x.round(), b.y.round(),
c.x.round(), c.y.round(),
image.Color.fromRgb(0, 0, 0), // 黑色
color,
);
}
if (options.verbose) {
print('Image drawn in ${sw.elapsedMilliseconds}ms.');
}
sw.reset();
sw.start();
final List<int> imageData = image.encodePng(img, level: 2);
File(options.output).writeAsBytesSync(imageData);
sw.stop();
if (options.verbose) {
print('PNG文档写入完成 in ${sw.elapsedMilliseconds}ms.');
}
return 0;
}
class Options {
Options._(
this.output,
this.points,
this.seed,
this.verbose,
this.help,
this.inputImage,
);
static Options? fromArgResults(ArgResults results) {
final bool verbose = results['verbose']!;
final int? points = int.tryParse(results['points']!);
if (points == null || points <= 0) {
stderr.writeln('--points 必须是一个正整数');
return null;
}
final int? seed = int.tryParse(results['seed']!);
if (seed == null || seed <= 0) {
stderr.writeln('--seed 必须是一个正整数');
return null;
}
final bool help = results['help']!;
if (!results.wasParsed('input') && help) {
stderr.writeln('请使用 --input 标志提供图像。');
return null;
}
return Options._(
results['output']!,
points,
seed,
verbose,
results['help']!,
_imageFromArgResults(results, verbose),
);
}
static image.Image? _imageFromArgResults(ArgResults results, bool verbose) {
if (!results.wasParsed('input')) {
return null;
}
final String inputImagePath = results['input']!;
image.Image inputImage;
final File inputFile = File(inputImagePath);
if (!inputFile.existsSync()) {
stderr.writeln('--input 图像 "$inputImagePath" 不存在。');
return null;
}
final Stopwatch sw = Stopwatch();
sw.start();
final List<int> imageData = inputFile.readAsBytesSync();
if (verbose) {
final int kb = imageData.length >> 10;
print('Image data (${kb}KB) read in ${sw.elapsedMilliseconds}ms');
}
sw.reset();
inputImage = image.decodeImage(imageData)!;
sw.stop();
if (verbose) {
final int w = inputImage.width;
final int h = inputImage.height;
print('Image data ${w}x$h decoded in ${sw.elapsedMilliseconds}ms');
}
return inputImage;
}
final String output;
final int points;
final int seed;
final bool verbose;
final bool help;
final image.Image? inputImage;
}
void drawTriangle(
image.Image img,
int ax,
int ay,
int bx,
int by,
int cx,
int cy,
int lineColor,
int fillColor,
) {
void fillBottomFlat(int x1, int y1, int x2, int y2, int x3, int y3) {
final double slope1 = (x2 - x1).toDouble() / (y2 - y1).toDouble();
final double slope2 = (x3 - x1).toDouble() / (y3 - y1).toDouble();
double curx1 = x1.toDouble();
double curx2 = curx1;
for (int sy = y1; sy <= y2; sy++) {
final int cx1 = curx1.toInt();
final int cx2 = curx2.toInt();
image.drawLine(img, cx1, sy, cx2, sy, fillColor);
curx1 += slope1;
curx2 += slope2;
}
}
void fillTopFlat(int x1, int y1, int x2, int y2, int x3, int y3) {
final double slope1 = (x3 - x1).toDouble() / (y3 - y1).toDouble();
final double slope2 = (x3 - x2).toDouble() / (y3 - y2).toDouble();
double curx1 = x3.toDouble();
double curx2 = curx1;
for (int sy = y3; sy > y1; sy--) {
final int cx1 = curx1.toInt();
final int cx2 = curx2.toInt();
image.drawLine(img, cx1, sy, cx2, sy, fillColor);
curx1 -= slope1;
curx2 -= slope2;
}
}
// 按y坐标升序排序点
if (ay > cy) {
final int tmpx = ax, tmpy = ay;
ax = cx;
ay = cy;
cx = tmpx;
cy = tmpy;
}
if (ay > by) {
final int tmpx = ax, tmpy = ay;
ax = bx;
ay = by;
bx = tmpx;
by = tmpy;
}
if (by > cy) {
final int tmpx = bx, tmpy = by;
bx = cx;
by = cy;
cx = tmpx;
cy = tmpy;
}
if (by == cy) {
fillBottomFlat(ax, ay, bx, by, cx, cy);
} else if (ay == by) {
fillTopFlat(ax, ay, bx, by, cx, cy);
} else {
final int dy = by;
final int dx = ax +
(((by - ay).toDouble() / (cy - ay).toDouble()) * (cx - ax).toDouble())
.toInt();
fillBottomFlat(ax, ay, bx, by, dx, dy);
fillTopFlat(bx, by, dx, dy, cx, cy);
}
}
说明
-
命令行参数:
--help
或-h
: 打印帮助信息。--verbose
或-v
: 详细输出。--input
或-i
: 输入图像路径。--output
或-o
: 输出文件路径,默认为delaunay.png
。--points
或-p
: 随机点的数量,默认为 1000。--seed
或-s
: 随机数生成器种子,默认为 42。
-
主要逻辑:
- 从输入图像中读取数据。
- 生成指定数量的随机点。
- 使用
Delaunay
类进行三角剖分。 - 将三角形绘制到新图像中,并保存为PNG文件。
希望这个示例能帮助你理解和使用 delaunay
插件。如果有任何问题或需要进一步的帮助,请随时提问!
更多关于Flutter Delaunay三角剖分插件delaunay的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter Delaunay三角剖分插件delaunay的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,下面是一个关于如何在Flutter中使用delaunay
插件进行Delaunay三角剖分的示例代码。delaunay
插件用于在给定一组二维点的情况下生成Delaunay三角剖分。
首先,你需要在你的Flutter项目中添加delaunay
插件。你可以通过编辑pubspec.yaml
文件来添加依赖:
dependencies:
flutter:
sdk: flutter
delaunay: ^最新版本号 # 请替换为实际的最新版本号
然后,运行flutter pub get
来获取依赖。
接下来,我们编写一个示例Flutter应用,该应用将在屏幕上绘制一组随机点,并使用delaunay
插件生成Delaunay三角剖分。
import 'package:flutter/material.dart';
import 'package:delaunay/delaunay.dart';
import 'dart:math' as math;
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Delaunay Triangulation',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: Text('Delaunay Triangulation Example'),
),
body: DelaunayTriangulationExample(),
),
);
}
}
class DelaunayTriangulationExample extends StatefulWidget {
@override
_DelaunayTriangulationExampleState createState() => _DelaunayTriangulationExampleState();
}
class _DelaunayTriangulationExampleState extends State<DelaunayTriangulationExample> {
List<Offset> points = [];
List<Triangle> triangles = [];
@override
void initState() {
super.initState();
generateRandomPoints();
}
void generateRandomPoints() {
final random = math.Random();
points = List.generate(20, (index) => Offset(random.nextDouble() * 300, random.nextDouble() * 300));
triangles = delaunay(points).map((triangle) => Triangle(
Offset(triangle.a.x, triangle.a.y),
Offset(triangle.b.x, triangle.b.y),
Offset(triangle.c.x, triangle.c.y)
)).toList();
setState(() {});
}
@override
Widget build(BuildContext context) {
return CustomPaint(
size: Size(300, 300),
painter: DelaunayPainter(points, triangles),
);
}
}
class DelaunayPainter extends CustomPainter {
final List<Offset> points;
final List<Triangle> triangles;
DelaunayPainter(this.points, this.triangles);
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.black
..strokeWidth = 1.0
..style = PaintingStyle.stroke;
// Draw points
points.forEach((point) {
canvas.drawCircle(point.toPoint(), 3.0, Paint()..color = Colors.red);
});
// Draw triangles
paint.style = PaintingStyle.fill;
triangles.forEach((triangle) {
final path = Path()
..moveTo(triangle.a.dx, triangle.a.dy)
..lineTo(triangle.b.dx, triangle.b.dy)
..lineTo(triangle.c.dx, triangle.c.dy)
..close();
canvas.drawPath(path, Paint()..color = Colors.blue.withOpacity(0.5));
});
// Draw triangle edges
paint.style = PaintingStyle.stroke;
triangles.forEach((triangle) {
final path = Path()
..moveTo(triangle.a.dx, triangle.a.dy)
..lineTo(triangle.b.dx, triangle.b.dy)
..moveTo(triangle.b.dx, triangle.b.dy)
..lineTo(triangle.c.dx, triangle.c.dy)
..moveTo(triangle.c.dx, triangle.c.dy)
..lineTo(triangle.a.dx, triangle.a.dy);
canvas.drawPath(path, paint);
});
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return oldDelegate != this;
}
}
class Triangle {
final Offset a, b, c;
Triangle(this.a, this.b, this.c);
}
在这个示例中,我们做了以下工作:
- 使用
delaunay
插件的delaunay
函数生成Delaunay三角剖分。 - 使用
CustomPaint
和CustomPainter
来绘制点和三角形。 generateRandomPoints
方法生成随机点并计算三角剖分。DelaunayPainter
类负责在画布上绘制点和三角形。
这个示例代码展示了如何在Flutter中使用delaunay
插件,并展示了基本的Delaunay三角剖分结果。你可以根据需要进一步调整和扩展这个示例。