logo和名称替换
This commit is contained in:
@@ -10,8 +10,8 @@ import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_native_image/flutter_native_image.dart';
|
||||
import 'package:image/image.dart' as img;
|
||||
import 'package:image_gallery_saver/image_gallery_saver.dart';
|
||||
import 'package:hgmj/tools_base/debug_log.dart';
|
||||
import 'package:hgmj/tools_base/toast.dart';
|
||||
import 'package:wydj/tools_base/debug_log.dart';
|
||||
import 'package:wydj/tools_base/toast.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:zxing2/qrcode.dart';
|
||||
@@ -23,7 +23,8 @@ class ImageUtil {
|
||||
static Future<String?> decodeQr(String path) async {
|
||||
var decodePath = path;
|
||||
try {
|
||||
final converted = await FlutterNativeImage.compressImage(path, percentage: 100, quality: 100);
|
||||
final converted = await FlutterNativeImage.compressImage(path,
|
||||
percentage: 100, quality: 100);
|
||||
decodePath = converted.path;
|
||||
} catch (_) {
|
||||
// 转换失败就拿原图碰运气(本就是 jpg/png 时不影响)
|
||||
@@ -39,7 +40,11 @@ class ImageUtil {
|
||||
final source = RGBLuminanceSource(
|
||||
image.width,
|
||||
image.height,
|
||||
image.convert(numChannels: 4).getBytes(order: img.ChannelOrder.rgba).buffer.asInt32List(),
|
||||
image
|
||||
.convert(numChannels: 4)
|
||||
.getBytes(order: img.ChannelOrder.rgba)
|
||||
.buffer
|
||||
.asInt32List(),
|
||||
);
|
||||
final bitmap = BinaryBitmap(HybridBinarizer(source));
|
||||
// tryHarder:相册照片有边框/轻微旋转/噪点,放开更费时但识别率更高
|
||||
@@ -54,7 +59,8 @@ class ImageUtil {
|
||||
/// 注:boundary 必须完整渲染在屏幕内,被滚动容器裁剪会截不全甚至失败
|
||||
static Future<bool> saveWidgetToAlbum(GlobalKey boundaryKey) async {
|
||||
await WidgetsBinding.instance.endOfFrame;
|
||||
final boundary = boundaryKey.currentContext?.findRenderObject() as RenderRepaintBoundary?;
|
||||
final boundary = boundaryKey.currentContext?.findRenderObject()
|
||||
as RenderRepaintBoundary?;
|
||||
if (boundary == null) return false;
|
||||
|
||||
Image? image;
|
||||
@@ -112,7 +118,8 @@ class ImageUtil {
|
||||
if (decoder == null) return null;
|
||||
decoder.startDecode(bytes); //只解头/块信息,不解像素,很便宜
|
||||
final total = decoder.numFrames();
|
||||
debugLog('saveToAlbum', '${decoder.runtimeType} frames=$total size=${bytes.length}');
|
||||
debugLog('saveToAlbum',
|
||||
'${decoder.runtimeType} frames=$total size=${bytes.length}');
|
||||
if (total <= 1) return null;
|
||||
|
||||
// 必须整体 decode:animated webp 的每帧只是个局部矩形,靠 dispose/blend 往画布上叠,
|
||||
@@ -133,11 +140,16 @@ class ImageUtil {
|
||||
var delay = 0;
|
||||
for (var i = 0; i < src.numFrames; i += step) {
|
||||
//frames[0] 就是 src 本身、挂着整条动画,copyResize 会连整条一起缩放,得先取单帧副本
|
||||
final f = i == 0 ? img.Image.from(src.frames[0], noAnimation: true) : src.frames[i];
|
||||
final f = i == 0
|
||||
? img.Image.from(src.frames[0], noAnimation: true)
|
||||
: src.frames[i];
|
||||
//必须指定 average:copyResize 默认是 nearest,降采样直接丢像素,出来全是锯齿
|
||||
final out = (f.width == outW && f.height == outH)
|
||||
? f
|
||||
: img.copyResize(f, width: outW, height: outH, interpolation: img.Interpolation.average);
|
||||
: img.copyResize(f,
|
||||
width: outW,
|
||||
height: outH,
|
||||
interpolation: img.Interpolation.average);
|
||||
sink.writeFromSync(out.getBytes(order: img.ChannelOrder.rgba));
|
||||
if (delay == 0 && f.frameDuration > 0) delay = f.frameDuration;
|
||||
count++;
|
||||
@@ -145,9 +157,15 @@ class ImageUtil {
|
||||
sink.closeSync();
|
||||
if (count < 2) return null;
|
||||
|
||||
debugLog('saveToAlbum', 'raw ${outW}x$outH frames=$count step=$step delay=$delay');
|
||||
debugLog('saveToAlbum',
|
||||
'raw ${outW}x$outH frames=$count step=$step delay=$delay');
|
||||
//抽帧后帧间隔要乘上 step,整体播放速度才不变;源没写时长就兜 80ms
|
||||
return {'w': outW, 'h': outH, 'n': count, 'delay': (delay > 0 ? delay : 80) * step};
|
||||
return {
|
||||
'w': outW,
|
||||
'h': outH,
|
||||
'n': count,
|
||||
'delay': (delay > 0 ? delay : 80) * step
|
||||
};
|
||||
} catch (e) {
|
||||
debugLog('saveToAlbum', 'decodeToRaw failed: $e');
|
||||
return null;
|
||||
@@ -155,17 +173,20 @@ class ImageUtil {
|
||||
}
|
||||
|
||||
/// RGBA 裸流 → H.264 mp4(x264 在 -gpl 变体里才有)
|
||||
static Future<Uint8List?> _rawToMp4(String rawPath, Map<String, int> meta) async {
|
||||
static Future<Uint8List?> _rawToMp4(
|
||||
String rawPath, Map<String, int> meta) async {
|
||||
final outPath = rawPath.replaceFirst(RegExp(r'\.rgba$'), '.mp4');
|
||||
final fps = (1000 / (meta['delay'] ?? 80)).clamp(1.0, 60.0);
|
||||
final cmd = '-y -f rawvideo -pixel_format rgba -video_size ${meta['w']}x${meta['h']} '
|
||||
final cmd =
|
||||
'-y -f rawvideo -pixel_format rgba -video_size ${meta['w']}x${meta['h']} '
|
||||
'-framerate ${fps.toStringAsFixed(2)} -i "$rawPath" '
|
||||
'-c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p -movflags +faststart "$outPath"';
|
||||
final out = File(outPath);
|
||||
try {
|
||||
final session = await FFmpegKit.execute(cmd);
|
||||
if (!ReturnCode.isSuccess(await session.getReturnCode())) {
|
||||
debugLog('saveToAlbum', 'ffmpeg failed: ${await session.getAllLogsAsString()}');
|
||||
debugLog('saveToAlbum',
|
||||
'ffmpeg failed: ${await session.getAllLogsAsString()}');
|
||||
return null;
|
||||
}
|
||||
return await out.readAsBytes();
|
||||
@@ -196,10 +217,13 @@ class ImageUtil {
|
||||
// 而那条分支用 URL(string:) 解析,裸路径没 scheme 会被 PhotoKit 拒,得传 file:// 形式
|
||||
final isVideo = ext == 'mp4';
|
||||
final result = await ImageGallerySaver.saveFile(
|
||||
(Platform.isIOS && !isVideo) ? Uri.file(file.path).toString() : file.path,
|
||||
(Platform.isIOS && !isVideo)
|
||||
? Uri.file(file.path).toString()
|
||||
: file.path,
|
||||
name: fileName,
|
||||
isReturnPathOfIOS: !isVideo,
|
||||
).timeout(const Duration(seconds: 20), onTimeout: () => null); //插件在拿不到 fullSizeImageURL 时不回调,兜底防卡死
|
||||
).timeout(const Duration(seconds: 20),
|
||||
onTimeout: () => null); //插件在拿不到 fullSizeImageURL 时不回调,兜底防卡死
|
||||
return result is Map && result["isSuccess"] == true;
|
||||
} catch (e) {
|
||||
debugLog('saveRawToAlbum 写入相册失败', e);
|
||||
|
||||
Reference in New Issue
Block a user