Files
desktop-plugin/lib/image/card_image.dart
T
dzc 0a78ff1b58 feat: Windows PavoApi ffi 直打后端(Phase 3 代码完成,待实机验收)
- lib/print/pavo/:PavoApiW_x64.dll 的 dart:ffi 绑定(结构体布局对齐 C#,W 后缀导出名回退兼容)
- pavo_printer.dart:枚举(UTF-8/UTF-16 自动解码)/状态/色带信息/单卡直打/忙碌轮询(3 分钟超时)/LayerHolder 非托管内存管理
- pavo_backend.dart:全部 ffi 调用与像素转换跑在 Isolate.run,主 isolate HTTP 服务零阻塞
- lib/image/card_image.dart:PNG data URL → 纵横比选卡面 → 手动 alpha 铺白底 → BGR24/Gray8 紧凑像素(image 包 compositeImage 对全透明像素非 src-over 语义,改为逐像素混合对齐 C#)
- windows/ 目录:从 Flutter SDK windows.tmpl 手动实例化(mac 上 flutter create 拒绝生成的绕过),窗口标题"饭卡打印助手"
- windows/vendor/:收录 SDK 的 PavoApiW_x64.dll/PavoCardSDK_x64.dll/HeatTB3.dll/heatthcd.bin,CMake POST_BUILD 自动拷到 exe 旁
- backend.dart 工厂接通 Windows 分支;analyze 零问题、单测 12 个全过
2026-09-21 12:19:07 +08:00

91 lines
3.2 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'dart:convert';
import 'dart:typed_data';
import 'package:image/image.dart';
/// PNG data URL 解析与像素层转换(Windows PavoApi 直打用)。
///
/// 卡面为 CR-80 @300dpi:横向 1014×642 / 纵向 642×1014,按源图纵横比自动选择;
/// 透明区域铺白,避免黑底(对齐 C# 版 Hiti/CardImage.cs)。
class CardImage {
CardImage._(this._canvas, this.width, this.height);
static const int landscapeWidth = 1014;
static const int landscapeHeight = 642;
final Image _canvas;
final int width;
final int height;
/// 宽 ≥ 高 = 横向卡面
bool get isLandscape => width >= height;
/// 解析 data:image/png;base64,…(或其他 data URL 形式),缩放到卡面画布并铺白底。
static CardImage fromDataUrl(String dataUrl) {
if (dataUrl.trim().isEmpty) {
throw ArgumentError('卡面图为空');
}
final comma = dataUrl.indexOf(',');
final base64Part = comma >= 0 ? dataUrl.substring(comma + 1) : dataUrl;
final bytes = base64.decode(base64Part.replaceAll(RegExp(r'\s'), ''));
final raw = decodeImage(bytes);
if (raw == null) {
throw ArgumentError('卡面图解码失败(不支持的图片格式)');
}
final landscape = raw.width >= raw.height;
final width = landscape ? landscapeWidth : landscapeHeight;
final height = landscape ? landscapeHeight : landscapeWidth;
final resized = copyResize(raw, width: width, height: height);
final canvas = Image(width: width, height: height);
// 手动 alpha 混合铺白底(与 C# 版逐像素语义一致):
// image 包的 compositeImage 对全透明 src 像素不是 src-over 语义,透明处会漏黑
for (var y = 0; y < height; y++) {
for (var x = 0; x < width; x++) {
final p = resized.getPixel(x, y);
final a = p.a.toInt() / 255.0;
canvas.setPixelRgba(
x,
y,
(255 * (1 - a) + p.r * a).round().clamp(0, 255),
(255 * (1 - a) + p.g * a).round().clamp(0, 255),
(255 * (1 - a) + p.b * a).round().clamp(0, 255),
255,
);
}
}
return CardImage._(canvas, width, height);
}
/// 转 BGR 24bit 紧凑像素(无行对齐填充,PavoApi BITMAP 用)。
Uint8List toBgr24() {
final packed = Uint8List(width * 3 * height);
for (var y = 0; y < height; y++) {
var offset = y * width * 3;
for (var x = 0; x < width; x++) {
final p = _canvas.getPixel(x, y);
packed[offset] = p.b.toInt() & 0xff;
packed[offset + 1] = p.g.toInt() & 0xff;
packed[offset + 2] = p.r.toInt() & 0xff;
offset += 3;
}
}
return packed;
}
/// 转 8bit 灰度(无行对齐)。树脂 K 层像素语义:0x00 = 转印黑色,
/// 因此前端 K 层 PNG 为白底黑字,灰度值可直接使用。
Uint8List toGray8() {
final gray = Uint8List(width * height);
for (var y = 0; y < height; y++) {
var offset = y * width;
for (var x = 0; x < width; x++) {
final p = _canvas.getPixel(x, y);
gray[offset + x] = (p.r.toInt() * 299 + p.g.toInt() * 587 + p.b.toInt() * 114) ~/ 1000 & 0xff;
}
}
return gray;
}
}