2023-09-21 03:56:30 +00:00
|
|
|
import 'dart:typed_data';
|
|
|
|
import 'dart:ui' as ui;
|
|
|
|
|
2023-09-21 22:37:12 +00:00
|
|
|
import 'package:dartterm/algorithms/geometry.dart' as geo;
|
2023-09-21 03:56:30 +00:00
|
|
|
import 'package:flutter/services.dart';
|
|
|
|
|
|
|
|
class Bitmap<T> {
|
|
|
|
// This idiosyncratic usage of "bitmap" comes from some other technology.
|
|
|
|
// What I'm saying is "don't blame me"
|
2023-09-21 22:37:12 +00:00
|
|
|
final geo.Size size;
|
2023-09-21 03:56:30 +00:00
|
|
|
final List<T> data;
|
|
|
|
|
2023-09-21 22:37:12 +00:00
|
|
|
geo.Rect get rect => geo.Rect(0, 0, size.dx, size.dy);
|
|
|
|
|
|
|
|
Bitmap(this.size, this.data) {
|
|
|
|
assert(data.length == size.dx * size.dy);
|
|
|
|
}
|
|
|
|
|
|
|
|
static Bitmap<T> blankWith<T>(int dx, int dy, T Function(int, int) lt) {
|
|
|
|
var data = [
|
|
|
|
for (var y = 0; y < dy; y++)
|
|
|
|
for (var x = 0; x < dx; x++) lt(x, y)
|
|
|
|
];
|
|
|
|
return Bitmap(geo.Size(dx, dy), data);
|
|
|
|
}
|
|
|
|
|
|
|
|
static Bitmap<T> blank<T>(int dx, int dy, T lt) {
|
|
|
|
var data = [
|
|
|
|
for (var y = 0; y < dy; y++)
|
|
|
|
for (var x = 0; x < dx; x++) lt
|
|
|
|
];
|
|
|
|
return Bitmap(geo.Size(dx, dy), data);
|
2023-09-21 03:56:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static Future<Bitmap<T>> load<T>(String name, T Function(int) cb) async {
|
|
|
|
final assetImageByteData = await rootBundle.load(name);
|
|
|
|
final codec =
|
|
|
|
await ui.instantiateImageCodec(assetImageByteData.buffer.asUint8List());
|
|
|
|
final image = (await codec.getNextFrame()).image;
|
|
|
|
|
|
|
|
final bytedata =
|
|
|
|
(await image.toByteData(format: ui.ImageByteFormat.rawStraightRgba))!;
|
|
|
|
|
|
|
|
final sx = image.width;
|
|
|
|
final sy = image.height;
|
|
|
|
|
|
|
|
final List<T> data = [];
|
|
|
|
for (var i = 0; i < sx * sy; i++) {
|
|
|
|
var pixel = bytedata.getUint32(i * 4, Endian.big);
|
|
|
|
data.add(cb(pixel));
|
|
|
|
}
|
|
|
|
|
2023-09-21 22:37:12 +00:00
|
|
|
return Bitmap(geo.Size(sx, sy), data);
|
|
|
|
}
|
|
|
|
|
|
|
|
void clearWith(T Function(int, int) t) {
|
|
|
|
for (var y = 0; y < size.dy; y++) {
|
|
|
|
for (var x = 0; x < size.dx; x++) {
|
|
|
|
data[y * size.dx + x] = t(x, y);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void clear(T t) {
|
|
|
|
for (var y = 0; y < size.dy; y++) {
|
|
|
|
for (var x = 0; x < size.dx; x++) {
|
|
|
|
data[y * size.dx + x] = t;
|
|
|
|
}
|
|
|
|
}
|
2023-09-21 03:56:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
T? get(int x, int y) {
|
2023-09-21 22:37:12 +00:00
|
|
|
if (x < 0 || y < 0 || x >= size.dx || y >= size.dy) {
|
2023-09-21 03:56:30 +00:00
|
|
|
return null;
|
|
|
|
}
|
2023-09-21 22:37:12 +00:00
|
|
|
return data[y * size.dx + x];
|
|
|
|
}
|
|
|
|
|
|
|
|
T unsafeGet(int x, int y) {
|
|
|
|
assert(x < 0 || y < 0 || x >= size.dx || y >= size.dy);
|
|
|
|
return data[y * size.dx + x];
|
|
|
|
}
|
|
|
|
|
|
|
|
void set(int x, int y, T value) {
|
2023-09-22 00:33:48 +00:00
|
|
|
assert(!(x < 0 || y < 0 || x >= size.dx || y >= size.dy));
|
2023-09-21 22:37:12 +00:00
|
|
|
data[y * size.dx + x] = value;
|
|
|
|
}
|
|
|
|
|
|
|
|
void blitFrom(Bitmap<T> other, int dx, int dy) {
|
|
|
|
assert(rect.containsRect(geo.Rect(dx, dy, other.size.dx, other.size.dy)));
|
|
|
|
var x0 = other.rect.x0;
|
|
|
|
var y0 = other.rect.x0;
|
|
|
|
var x1 = other.rect.x1;
|
|
|
|
var y1 = other.rect.y1;
|
|
|
|
var myW = size.dx;
|
|
|
|
var otW = other.size.dx;
|
|
|
|
for (var x = x0; x < x1; x++) {
|
|
|
|
for (var y = y0; y < y1; y++) {
|
|
|
|
data[(y + dy) * myW + (x + dx)] = other.data[y * otW + x];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-22 00:33:48 +00:00
|
|
|
void blitFromWith(Bitmap<T> other, int dx, int dy, T Function(T, T) merge) {
|
|
|
|
assert(rect.containsRect(geo.Rect(dx, dy, other.size.dx, other.size.dy)));
|
|
|
|
var x0 = other.rect.x0;
|
|
|
|
var y0 = other.rect.x0;
|
|
|
|
var x1 = other.rect.x1;
|
|
|
|
var y1 = other.rect.y1;
|
|
|
|
var myW = size.dx;
|
|
|
|
var otW = other.size.dx;
|
|
|
|
for (var x = x0; x < x1; x++) {
|
|
|
|
for (var y = y0; y < y1; y++) {
|
|
|
|
data[(y + dy) * myW + (x + dx)] =
|
|
|
|
merge(data[(y + dy) * myW + (x + dx)], other.data[y * otW + x]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-21 22:37:12 +00:00
|
|
|
Bitmap<T> flip() {
|
|
|
|
var geo.Size(:dx, :dy) = size;
|
|
|
|
|
|
|
|
List<T> data2 = [
|
|
|
|
for (var y = 0; y < size.dy; y++)
|
|
|
|
for (var x = size.dx - 1; x >= 0; x--) data[y * dx + x]
|
|
|
|
];
|
|
|
|
|
|
|
|
return Bitmap(geo.Size(dx, dy), data2);
|
|
|
|
}
|
|
|
|
|
|
|
|
Bitmap<T> rotateRight() {
|
|
|
|
var geo.Size(:dx, :dy) = size;
|
|
|
|
|
|
|
|
List<T> data2 = [
|
|
|
|
for (var x = 0; x < dx; x++)
|
|
|
|
for (var y = 0; y < dy; y++) data[(dy - 1 - y) * dx + x]
|
|
|
|
];
|
|
|
|
|
|
|
|
return Bitmap(geo.Size(dy, dx), data2);
|
|
|
|
}
|
|
|
|
|
|
|
|
Bitmap<T> rotateLeft() {
|
|
|
|
var geo.Size(:dx, :dy) = size;
|
|
|
|
|
|
|
|
List<T> data2 = [
|
|
|
|
for (var x = dx - 1; x >= 0; x++)
|
|
|
|
for (var y = dy - 1; y >= 0; y++) data[y * dx + (dx - 1 - x)]
|
|
|
|
];
|
|
|
|
|
|
|
|
return Bitmap(geo.Size(dy, dx), data2);
|
2023-09-21 03:56:30 +00:00
|
|
|
}
|
|
|
|
}
|