770 lines
33 KiB
C#
770 lines
33 KiB
C#
|
||
using ImageMagick;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.IO;
|
||
using System.Reflection;
|
||
using System.Threading.Channels;
|
||
using System.Threading.Tasks;
|
||
using XPrint.Production.Business.ImageLogic.Enitites;
|
||
|
||
namespace XPrint.Production.Business.ImageLogic
|
||
{
|
||
public class BatchImageCombiner : IDisposable
|
||
{
|
||
private readonly string _outputPath;
|
||
private readonly uint _totalWidth;
|
||
private readonly uint _totalHeight;
|
||
private static string _tempDirectory = null!;
|
||
//private bool _isFirstImage = true;
|
||
private MagickImage canvas = null!;
|
||
|
||
|
||
public static void InitConfigure()
|
||
{
|
||
// 1. 配置全局临时目录(影响所有Magick操作)
|
||
_tempDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "MagickCombinerTemp");
|
||
DeleteTempDirectory();
|
||
|
||
Directory.CreateDirectory(_tempDirectory);
|
||
MagickNET.SetTempDirectory(_tempDirectory);
|
||
|
||
// 全局限制内存使用(旧版本通过环境变量或全局设置)
|
||
MagickNET.SetEnvironmentVariable("MAGICK_MEMORY_LIMIT", "4096MiB"); // 内存限制
|
||
MagickNET.SetEnvironmentVariable("MAGICK_MAP_LIMIT", "8192MiB"); // 内存映射限制
|
||
// 注意:需在创建第一个 MagickImage 前设置
|
||
// 1. 多维度设置线程数(兼容不同版本的 ImageMagick)
|
||
MagickNET.SetEnvironmentVariable("MAGICK_THREAD_LIMIT", Environment.ProcessorCount.ToString());
|
||
MagickNET.SetEnvironmentVariable("OMP_NUM_THREADS", Environment.ProcessorCount.ToString()); // OpenMP 线程
|
||
MagickNET.SetEnvironmentVariable("MAGICK_CORE_THREAD_LIMIT", Environment.ProcessorCount.ToString());
|
||
|
||
//通过设置环境变量来增加缓存大小
|
||
MagickNET.SetEnvironmentVariable("MAGICK_AREA_LIMIT", "2GB"); // 或其他适当的值
|
||
}
|
||
|
||
public static void DeleteTempDirectory()
|
||
{
|
||
if (Directory.Exists(_tempDirectory))
|
||
{
|
||
try
|
||
{
|
||
Directory.Delete(_tempDirectory, true);
|
||
}
|
||
catch(Exception ex)
|
||
{
|
||
Console.WriteLine(ex.Message);
|
||
}
|
||
}
|
||
}
|
||
|
||
public BatchImageCombiner(string outputPath, uint totalWidth, uint totalHeight)
|
||
{
|
||
_outputPath = outputPath;
|
||
_totalWidth = totalWidth;
|
||
_totalHeight = totalHeight;
|
||
}
|
||
|
||
///// <summary>
|
||
///// 追加一张小图到最终图像(基于磁盘操作)
|
||
///// </summary>
|
||
//public void AppendImage(string imagePath,
|
||
// int srcX, int srcY, uint srcWidth, uint srcHeight,
|
||
// int destX, int destY, uint destWidth, uint destHeight)
|
||
//{
|
||
// using (var image = new MagickImage(imagePath))
|
||
// {
|
||
// if (canvas == null)
|
||
// {
|
||
// canvas = new MagickImage(MagickColors.Transparent, _totalWidth, _totalHeight);
|
||
// CopyRegion(image, canvas, srcX, srcY, srcWidth, srcHeight, destX, destY, destWidth, destHeight);
|
||
// _isFirstImage = false;
|
||
// }
|
||
// else
|
||
// {
|
||
// // 后续处理:从临时文件加载当前画布
|
||
// if (canvas != null)
|
||
// {
|
||
// CopyRegion(image, canvas, srcX, srcY, srcWidth, srcHeight, destX, destY, destWidth, destHeight);
|
||
// }
|
||
// }
|
||
// }
|
||
//}
|
||
|
||
/// <summary>
|
||
/// 将源图像的指定区域拷贝到目标图像的指定位置,并可指定目标尺寸
|
||
/// </summary>
|
||
/// <param name="sourcePath">源图像路径</param>
|
||
/// <param name="destPath">目标图像路径(可为新图像路径)</param>
|
||
/// <param name="srcX">源图像起始X坐标</param>
|
||
/// <param name="srcY">源图像起始Y坐标</param>
|
||
/// <param name="srcWidth">源图像裁剪宽度</param>
|
||
/// <param name="srcHeight">源图像裁剪高度</param>
|
||
/// <param name="destX">目标图像起始X坐标</param>
|
||
/// <param name="destY">目标图像起始Y坐标</param>
|
||
/// <param name="destWidth">目标区域宽度(可缩放)</param>
|
||
/// <param name="destHeight">目标区域高度(可缩放)</param>
|
||
public static void CopyRegion(MagickImage sourceImage, MagickImage destImage,
|
||
int srcX, int srcY, uint srcWidth, uint srcHeight,
|
||
int destX, int destY, uint destWidth, uint destHeight, bool handlePngEdge = false)
|
||
{
|
||
|
||
if (srcX > 0 || srcY > 0 || srcWidth < sourceImage.Width || srcHeight < sourceImage.Height)
|
||
{
|
||
// 1. 裁剪源图像的指定区域
|
||
// 创建源区域几何信息:X,Y,Width,Height
|
||
var sourceGeometry = new MagickGeometry(srcX, srcY, srcWidth, srcHeight);
|
||
// 裁剪操作
|
||
sourceImage.Crop(sourceGeometry);
|
||
}
|
||
|
||
// 3. 设置要粘贴的目标区域尺寸(实现缩放效果)
|
||
var destGeometry = new MagickGeometry(0, 0, destWidth, destHeight);
|
||
// 确保目标尺寸正确应用
|
||
sourceImage.Resize(destGeometry);
|
||
|
||
sourceImage.Alpha(AlphaOption.On); // 确保alpha通道开启
|
||
//sourceImage.Threshold(new Percentage(50), Channels.Alpha); // alpha通道50%阈值
|
||
if (handlePngEdge)
|
||
{
|
||
//var mask = GetImageMask(sourceImage);
|
||
sourceImage = CompositeAsMaskedThreshold(sourceImage);
|
||
//mask.Dispose();
|
||
}
|
||
|
||
// 4. 将裁剪并缩放后的区域粘贴到目标图像
|
||
destImage.Composite(sourceImage, destX, destY, CompositeOperator.Over);
|
||
sourceImage.Dispose();
|
||
//Console.WriteLine($"已将源图像区域({srcX},{srcY},{srcWidth},{srcHeight}) " +
|
||
// $"拷贝到目标图像位置({destX},{destY}),尺寸为({destWidth},{destHeight})");
|
||
}
|
||
|
||
|
||
//private static MagickImage GetImageMask(MagickImage src)
|
||
//{
|
||
// // 1. 复制原图作为边缘检测的基础
|
||
// var edge = src.Clone();
|
||
|
||
// // 2. 转换为灰度图并检测边缘(突出边缘区域)
|
||
// edge.ColorSpace = ColorSpace.Gray;
|
||
// edge.Edge(1); // 检测1像素宽的边缘
|
||
// edge.Threshold(new Percentage(10)); // 强化边缘为黑白二值图(边缘为白色)
|
||
|
||
// //// 3. 将边缘图作为掩码,仅对边缘区域的Alpha通道应用阈值
|
||
// //var mask = edge.Clone();
|
||
// //// 掩码反转:让边缘区域为白色(需要处理的区域),内部为黑色(保留)
|
||
// //mask.Negate();
|
||
// // 对原图Alpha通道应用阈值,但仅影响掩码中的白色区域(边缘)
|
||
// //src.Threshold(new Percentage(50), Channels.Alpha, mask);
|
||
// return (edge as MagickImage)!;
|
||
//}
|
||
|
||
public static MagickImage CompositeAsMaskedThreshold(MagickImage src)//, MagickImage mask)
|
||
{
|
||
// 1. 确保图像有Alpha通道
|
||
if (!src.HasAlpha)
|
||
src.Alpha(AlphaOption.On);
|
||
|
||
// 2. 提取原始Alpha通道(强制转为MagickImage具体类)
|
||
var originalAlpha = (MagickImage)src.Clone();
|
||
originalAlpha.Alpha(AlphaOption.Extract);
|
||
//originalAlpha.Write(@"D:\67890.tif");
|
||
|
||
// 3. 生成边缘掩码(仅边缘过渡区域)
|
||
var edgeMask = CreateEdgeMask(originalAlpha);
|
||
|
||
//originalAlpha.Alpha(AlphaOption.On);
|
||
// ------------------- 核心修改:处理edgeMask为Alpha透明指示掩码 -------------------
|
||
// 3.1 反转edgeMask:白色边缘(255)→ 0(透明指示),黑色背景(0)→ 255(不透明指示)
|
||
//edgeMask.Negate(); // 官方Negate方法:反转颜色(255-x),一行实现
|
||
edgeMask.Depth = 8;// 3.2 确保edgeMask是8位灰度(与Alpha通道深度匹配,避免复合异常)
|
||
edgeMask.ColorSpace = ColorSpace.Gray;
|
||
// 2. 关键:开启Alpha通道(透明的前提,若已有则不影响)
|
||
edgeMask.Alpha(AlphaOption.On);
|
||
|
||
//// 3.2 二值化:确保只有纯白(255)和纯黑(0),避免灰色干扰
|
||
//edgeMask.Threshold(new Percentage(95)); // 95%阈值:>242的像素视为白色(255)
|
||
// // 3.3 反转Alpha掩码:白色(255)→0(透明),黑色(0)→255(不透明)
|
||
|
||
|
||
|
||
// 4. 对边缘区域的半透明像素进行阈值化
|
||
var edgeProcessedAlpha = (MagickImage)originalAlpha.Clone();
|
||
edgeProcessedAlpha.Threshold(new Percentage(50)); // 边缘半透明转透明
|
||
|
||
// 5. 仅在边缘掩码区域应用处理(手动控制替换范围)
|
||
ApplyMaskedComposite(originalAlpha, edgeProcessedAlpha, edgeMask);
|
||
|
||
//originalAlpha.Write(@"D:\12345.tif");
|
||
|
||
//edgeMask.Write(@"D:\edge_mask.tif");
|
||
// 6. 写回优化后的Alpha通道
|
||
src.Composite(edgeProcessedAlpha, CompositeOperator.CopyAlpha, Channels.Alpha);
|
||
|
||
edgeProcessedAlpha.Dispose();
|
||
edgeMask.Dispose();
|
||
originalAlpha.Dispose();
|
||
|
||
return src;
|
||
}
|
||
|
||
/// <summary>仅在掩码区域应用复合操作(优化版:批量像素+指针操作)</summary>
|
||
private static void ApplyMaskedComposite(MagickImage target, MagickImage source, MagickImage mask)
|
||
{
|
||
uint width = target.Width;
|
||
uint height = target.Height;
|
||
uint totalPixels = width * height; // 总像素数(避免循环内重复计算)
|
||
|
||
// 关键:一次性获取目标、源、掩码的整幅图像像素数组(ushort[])
|
||
using var targetPixels = target.GetPixels();
|
||
using var sourcePixels = source.GetPixels();
|
||
using var maskPixels = mask.GetPixels();
|
||
var targetArea = targetPixels.GetArea(0, 0, width, height); // 目标像素数组(16位)
|
||
var sourceArea = sourcePixels.GetArea(0, 0, width, height); // 源像素数组(16位)
|
||
var maskArea = maskPixels.GetArea(0, 0, width, height); // 掩码像素数组(16位,实际存0/255)
|
||
|
||
// 用unsafe指针直接操作内存(跳过托管API的安全检查和方法调用开销)
|
||
unsafe
|
||
{
|
||
fixed (ushort* pTarget = targetArea)
|
||
fixed (ushort* pSource = sourceArea)
|
||
fixed (ushort* pMask = maskArea)
|
||
{
|
||
ushort* targetPtr = pTarget;
|
||
ushort* sourcePtr = pSource;
|
||
ushort* maskPtr = pMask;
|
||
|
||
// 单循环遍历所有像素(替代嵌套x/y循环,减少计算量)
|
||
for (int i = 0; i < totalPixels; i++)
|
||
{
|
||
// 掩码为白色(255)的区域才替换(掩码是8位转16位,255保持不变)
|
||
if (*maskPtr == 255)
|
||
{
|
||
*targetPtr = *sourcePtr; // 直接复制源像素到目标
|
||
}
|
||
|
||
// 指针自增(按内存顺序连续访问,最大化CPU缓存命中率)
|
||
targetPtr++;
|
||
sourcePtr++;
|
||
maskPtr++;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 将修改后的像素数组写回目标图像
|
||
targetPixels.SetArea(0, 0, width, height, targetArea!);
|
||
}
|
||
|
||
/// <summary>生成边缘掩码:标记不透明→透明的过渡区域(修正版)</summary>
|
||
private static MagickImage CreateEdgeMask(MagickImage alphaChannel)
|
||
{
|
||
// 转换为8位灰度图(确保边缘检测精度)
|
||
using var alpha8 = ConvertTo8Bit(alphaChannel);
|
||
|
||
// 二值化:区分不透明(>5% Alpha)和透明区域(≤5%)
|
||
// 调整阈值:若边缘半透明较浅,可降低至3%(更敏感)
|
||
using var binaryAlpha = (MagickImage)alpha8.Clone();
|
||
binaryAlpha.Threshold(new Percentage(5));
|
||
|
||
// 保存二值化结果,验证是否有白色区域(不透明区域)
|
||
//binaryAlpha.Write(@"D:\binary_alpha.png");
|
||
|
||
//// 手动膨胀和腐蚀(生成边缘过渡带)
|
||
//// 膨胀/腐蚀半径1像素,确保边缘宽度合适
|
||
//using var dilated = ManualDilate(binaryAlpha);
|
||
//dilated.Write(@"D:\dilated.png"); // 保存膨胀结果
|
||
//using var eroded = ManualErode(binaryAlpha);
|
||
//eroded.Write(@"D:\eroded.png"); // 保存腐蚀结果
|
||
|
||
// 核心修正:用像素级减法计算边缘(膨胀 - 腐蚀 = 边缘)
|
||
|
||
using var dilated = MorphologyDilate((binaryAlpha.Clone() as MagickImage)!);
|
||
|
||
//dilated.Write(@"D:\dilated.png"); // 保存膨胀结果
|
||
|
||
using var eroded = MorphologyErode((binaryAlpha.Clone() as MagickImage)!);
|
||
//eroded.Write(@"D:\eroded.png"); // 保存腐蚀结果
|
||
|
||
var edgeMask = (MagickImage)dilated.Clone();
|
||
SubtractImages(edgeMask, eroded); // 替代自定义的SubtractImages(edgeMask, eroded)
|
||
|
||
|
||
//edgeMask.Write(@"D:\edge_mask.png");
|
||
// 增强边缘掩码:确保边缘为纯白(255),非边缘纯黑(0)
|
||
edgeMask.Threshold(new Percentage(50)); // 阈值50%过滤噪点
|
||
return edgeMask;
|
||
}
|
||
|
||
/// <summary>旧版本兼容:用IMorphologySettings实现腐蚀(Morphology方法需传设置对象)</summary>
|
||
private static MagickImage MorphologyErode(MagickImage input)
|
||
{
|
||
var eroded = (MagickImage)input.Clone();
|
||
|
||
// 1. 手动创建3x3矩形内核(适配无预定义内核的旧版本)
|
||
// 3x3全1矩阵:代表邻域内所有像素参与计算(与手动3x3邻域逻辑一致)
|
||
//double[][] kernelMatrix =
|
||
//[
|
||
// [1, 1, 1],
|
||
// [1, 1, 1],
|
||
// [1, 1, 1]
|
||
//];
|
||
//Kernel rectangleKernel = Kernel.Rectangle;// new Kernel(kernelMatrix, normalize: true); // normalize=true确保计算正确
|
||
|
||
// 2. 构造IMorphologySettings实例(用MorphologySettings实现类)
|
||
IMorphologySettings morphologySettings = new MorphologySettings
|
||
{
|
||
Method = MorphologyMethod.Erode, // 操作类型:腐蚀
|
||
Kernel = Kernel.Rectangle, // 核心:3x3矩形内核
|
||
Iterations = 1 // 迭代1次(腐蚀1像素)
|
||
};
|
||
|
||
// 3. 调用Morphology方法(传入设置对象)
|
||
eroded.Morphology(morphologySettings);
|
||
|
||
// 4. 确保输出格式正确(避免效果异常)
|
||
eroded.ColorSpace = ColorSpace.Gray; // 灰度单通道
|
||
eroded.Depth = 8; // 8位深度(与binaryAlpha匹配)
|
||
|
||
return eroded;
|
||
}
|
||
|
||
/// <summary>配套:用IMorphologySettings实现膨胀</summary>
|
||
private static MagickImage MorphologyDilate(MagickImage input)
|
||
{
|
||
var dilated = (MagickImage)input.Clone();
|
||
|
||
// 1. 同样创建3x3矩形内核
|
||
//double[][] kernelMatrix =
|
||
// [
|
||
// [1, 1, 1],
|
||
// [1, 1, 1],
|
||
// [1, 1, 1]
|
||
// ];
|
||
//Kernel rectangleKernel = Kernel.Rectangle;// new Kernel(kernelMatrix, normalize: true);
|
||
|
||
// 2. 构造设置对象(仅Method改为Dilate)
|
||
IMorphologySettings morphologySettings = new MorphologySettings
|
||
{
|
||
Method = MorphologyMethod.Dilate, // 操作类型:膨胀
|
||
Kernel = Kernel.Rectangle,
|
||
Iterations = 1
|
||
};
|
||
|
||
// 3. 调用Morphology
|
||
dilated.Morphology(morphologySettings);
|
||
|
||
// 4. 格式校准
|
||
dilated.ColorSpace = ColorSpace.Gray;
|
||
dilated.Depth = 8;
|
||
|
||
return dilated;
|
||
}
|
||
|
||
|
||
/// <summary>手动实现图像减法(a = a - b,确保边缘掩码正确)</summary>
|
||
private static void SubtractImages(MagickImage a, MagickImage b)
|
||
{
|
||
int width = (int)a.Width;
|
||
int height = (int)a.Height;
|
||
using var aPixels = a.GetPixels();
|
||
using var bPixels = b.GetPixels();
|
||
var aArea = aPixels.GetArea(0, 0, (uint)width, (uint)height);
|
||
var bArea = bPixels.GetArea(0, 0, (uint)width, (uint)height);
|
||
|
||
unsafe
|
||
{
|
||
fixed (ushort* pA = aArea, pB = bArea)
|
||
{
|
||
ushort* aPtr = pA;
|
||
ushort* bPtr = pB;
|
||
for (int i = 0; i < width * height; i++)
|
||
{
|
||
// 减法后取非负结果(确保边缘为正值)
|
||
*aPtr = (ushort)Math.Max(*aPtr - *bPtr, 0);
|
||
aPtr++;
|
||
bPtr++;
|
||
}
|
||
}
|
||
}
|
||
aPixels.SetArea(0, 0, (uint)width, (uint)height, aArea!);
|
||
}
|
||
|
||
/// <summary>将Alpha通道转为8位灰度(解决16位类型问题)</summary>
|
||
private static MagickImage ConvertTo8Bit(MagickImage input)
|
||
{
|
||
var output = (MagickImage)input.Clone();
|
||
output.ColorSpace = ColorSpace.Gray; // 确保灰度单通道
|
||
output.Depth = 8; // 强制8位深度
|
||
//output.Write(@"D:\alpha8_official.png");
|
||
return output;
|
||
}
|
||
|
||
|
||
//// 反射获取内部像素数组(复用之前的实现)
|
||
//private static ushort[]? GetInternalPixelArray(IPixelCollection<ushort> pixels)
|
||
//{
|
||
// try
|
||
// {
|
||
// var field = pixels.GetType().GetField("_pixels", BindingFlags.NonPublic | BindingFlags.Instance)
|
||
// ?? pixels.GetType().GetField("data", BindingFlags.NonPublic | BindingFlags.Instance)
|
||
// ?? pixels.GetType().GetField("m_pixels", BindingFlags.NonPublic | BindingFlags.Instance);
|
||
|
||
// return field?.GetValue(pixels) as ushort[];
|
||
// }
|
||
// catch
|
||
// {
|
||
// return null;
|
||
// }
|
||
//}
|
||
|
||
//// 1. 批量读取像素到缓冲区(unsafe指针版,零API调用)
|
||
//private static unsafe void ReadPixelsToBuffer(IPixelCollection<ushort> inputPixels, ushort[] inputBuffer, int width, int height)
|
||
//{
|
||
// // 反射获取内部像素数组(核心:绕过GetPixel)
|
||
// ushort[] internalPixels = GetInternalPixelArray(inputPixels)!;
|
||
// if (internalPixels == null)
|
||
// {
|
||
// FallbackReadPixels(inputPixels, inputBuffer, width, height); // 降级方案
|
||
// return;
|
||
// }
|
||
|
||
// // 固定内部数组和输入缓冲区,获取指针
|
||
// fixed (ushort* pInternal = internalPixels, pBuffer = inputBuffer)
|
||
// {
|
||
// ushort* srcPtr = pInternal; // 源:内部像素数组指针
|
||
// ushort* destPtr = pBuffer; // 目标:输入缓冲区指针
|
||
|
||
// // 按行复制(利用内存连续性,最大化缓存效率)
|
||
// int rowSize = width * sizeof(ushort); // 一行的字节数
|
||
// for (int y = 0; y < height; y++)
|
||
// {
|
||
// // 计算当前行的起始指针(直接地址偏移,无乘法运算)
|
||
// ushort* srcRow = srcPtr + y * width;
|
||
// ushort* destRow = destPtr + y * width;
|
||
|
||
// // 批量复制一行数据(比逐像素快3-5倍)
|
||
// Buffer.MemoryCopy(
|
||
// srcRow, // 源地址
|
||
// destRow, // 目标地址
|
||
// rowSize, // 目标剩余字节数
|
||
// rowSize // 复制字节数
|
||
// );
|
||
// }
|
||
// }
|
||
//}
|
||
|
||
//// 4. 批量写入输出缓冲区(unsafe指针版,零API调用)
|
||
//private static unsafe void WritePixelsFromBuffer(IPixelCollection<ushort> outputPixels, ushort[] outputBuffer, int width, int height)
|
||
//{
|
||
// // 反射获取内部像素数组
|
||
// ushort[] internalPixels = GetInternalPixelArray(outputPixels)!;
|
||
// if (internalPixels == null)
|
||
// {
|
||
// FallbackWritePixels(outputPixels, outputBuffer, width, height); // 降级方案
|
||
// return;
|
||
// }
|
||
|
||
// // 固定内部数组和输出缓冲区,获取指针
|
||
// fixed (ushort* pInternal = internalPixels, pBuffer = outputBuffer)
|
||
// {
|
||
// ushort* destPtr = pInternal; // 目标:内部像素数组指针
|
||
// ushort* srcPtr = pBuffer; // 源:输出缓冲区指针
|
||
|
||
// // 按行复制
|
||
// int rowSize = width * sizeof(ushort);
|
||
// for (int y = 0; y < height; y++)
|
||
// {
|
||
// ushort* srcRow = srcPtr + y * width;
|
||
// ushort* destRow = destPtr + y * width;
|
||
|
||
// // 批量复制一行数据
|
||
// Buffer.MemoryCopy(srcRow, destRow, rowSize, rowSize);
|
||
// }
|
||
// }
|
||
|
||
// // 同步数据到图像(部分版本需要)
|
||
// SyncPixelCollection(outputPixels);
|
||
//}
|
||
|
||
//// 同步数据(复用之前的实现)
|
||
//private static void SyncPixelCollection(IPixelCollection<ushort> pixels)
|
||
//{
|
||
// try
|
||
// {
|
||
// pixels.GetType().GetMethod("Sync", BindingFlags.NonPublic | BindingFlags.Instance)?.Invoke(pixels, null);
|
||
// }
|
||
// catch { }
|
||
//}
|
||
|
||
//// 降级方案(原优化版嵌套循环)
|
||
//private static void FallbackReadPixels(IPixelCollection<ushort> inputPixels, ushort[] inputBuffer, int width, int height)
|
||
//{
|
||
// for (int y = 0; y < height; y++)
|
||
// {
|
||
// int rowStartIndex = y * width;
|
||
// IPixel<ushort>? pixel = null;
|
||
// for (int x = 0; x < width; x++)
|
||
// {
|
||
// pixel = inputPixels.GetPixel(x, y);
|
||
// inputBuffer[rowStartIndex + x] = pixel[0];
|
||
// }
|
||
// }
|
||
//}
|
||
|
||
//private static void FallbackWritePixels(IPixelCollection<ushort> outputPixels, ushort[] outputBuffer, int width, int height)
|
||
//{
|
||
// for (int y = 0; y < height; y++)
|
||
// {
|
||
// int rowStartIndex = y * width;
|
||
// IPixel<ushort>? pixel = null;
|
||
// for (int x = 0; x < width; x++)
|
||
// {
|
||
// pixel = outputPixels.GetPixel(x, y);
|
||
// pixel[0] = outputBuffer[rowStartIndex + x];
|
||
// }
|
||
// }
|
||
//}
|
||
|
||
|
||
//// 自定义非泛型unsafe委托(处理byte*指针)
|
||
//private unsafe delegate byte NeighborhoodProcessor(byte* neighbors);
|
||
|
||
///// <summary>高性能邻域处理(带数据验证)</summary>
|
||
//private unsafe static MagickImage ProcessNeighborhood(MagickImage input, NeighborhoodProcessor processFunc)
|
||
//{
|
||
// var output = (MagickImage)input.Clone();
|
||
|
||
// int width = (int)input.Width;
|
||
// int height = (int)input.Height;
|
||
// int pixelCount = width * height;
|
||
|
||
// // 1. 明确指定泛型<ushort>,避免类型不匹配
|
||
// using var inputPixels = input.GetPixelsUnsafe();
|
||
// using var outputPixels = output.GetPixelsUnsafe();
|
||
// //// 预分配输入/输出缓冲区
|
||
// //ushort[] inputBuffer = new ushort[pixelCount];
|
||
// ushort[] outputBuffer = new ushort[pixelCount];
|
||
|
||
// //// 1. 关键:用 GetPixelsUnsafe<ushort>() 获取非托管像素集合(指定泛型ushort)
|
||
// //// 用 using 包裹,确保非托管内存自动释放
|
||
// //using var inputPixelCollection = input.GetPixelsUnsafe();
|
||
// //// 2. 调用 GetArea() 批量读取像素数据为 ushort[](无需提前 new 数组)
|
||
// var inputBuffer = inputPixels.GetArea(0, 0, input.Width, input.Height)!;
|
||
// // 6. 验证inputBuffer并写入
|
||
// //ValidateBuffer(inputBuffer, "inputBuffer", pixelCount); // 验证处理后有非0值
|
||
|
||
|
||
// // 3. 预计算3x3邻域偏移量(确保width是int)
|
||
// int[] offsets = new int[9];
|
||
// int offsetIndex = 0;
|
||
// for (int dy = -1; dy <= 1; dy++)
|
||
// {
|
||
// for (int dx = -1; dx <= 1; dx++)
|
||
// {
|
||
// offsets[offsetIndex++] = dy * width + dx;
|
||
// }
|
||
// }
|
||
|
||
// // 4. 栈上分配邻域内存
|
||
// byte* neighborsPtr = stackalloc byte[9];
|
||
|
||
// // 5. 指针操作与数据验证
|
||
// fixed (ushort* pInput = inputBuffer, pOutput = outputBuffer)
|
||
// {
|
||
// ushort* inputPtr = pInput;
|
||
// ushort* outputPtr = pOutput;
|
||
|
||
// for (int i = 0; i < pixelCount; i++)
|
||
// {
|
||
// int x = i % width;
|
||
// int y = i / width;
|
||
|
||
// // 填充邻域并验证
|
||
// FillNeighbors(neighborsPtr, inputPtr, i, offsets, x, y, width, height);
|
||
|
||
// // 处理并验证结果
|
||
// byte processResult = processFunc(neighborsPtr);
|
||
// //if (i < 10) // 打印前10个结果
|
||
// // Console.WriteLine($"i={i}, processResult={processResult}");
|
||
// *outputPtr++ = (ushort)processResult;
|
||
// }
|
||
// }
|
||
|
||
// outputPixels.SetArea(0, 0, (uint)width, (uint)height, outputBuffer);
|
||
// // 6. 验证outputBuffer并写入
|
||
// //ValidateBuffer(outputBuffer, "outputBuffer", pixelCount); // 验证处理后有非0值
|
||
|
||
// output.Write(@"D:\777888999.tif");
|
||
|
||
// return output;
|
||
//}
|
||
|
||
//// 辅助:验证缓冲区是否非全0
|
||
//private static void ValidateBuffer(ushort[] buffer, string bufferName, int pixelCount)
|
||
//{
|
||
// for (int i = 0; i < pixelCount; i++)
|
||
// {
|
||
// if (buffer[i] != 0)
|
||
// {
|
||
// Console.WriteLine($"{bufferName} 数据正常(第{i}个值:{buffer[i]})");
|
||
// return;
|
||
// }
|
||
// }
|
||
// throw new InvalidDataException($"{bufferName} 全为0!请检查上游数据传递");
|
||
//}
|
||
|
||
//// 辅助:填充邻域并打印调试信息
|
||
//private static unsafe void FillNeighbors(byte* neighborsPtr, ushort* inputPtr, int i, int[] offsets, int x, int y, int width, int height)
|
||
//{
|
||
// for (int k = 0; k < 9; k++)
|
||
// {
|
||
// int neighborIndex = i + offsets[k];
|
||
// int nx = x + (offsets[k] % width);
|
||
// int ny = y + (offsets[k] / width);
|
||
// bool isInBounds = nx >= 0 && nx < width && ny >= 0 && ny < height;
|
||
|
||
// if (isInBounds)
|
||
// {
|
||
// neighborsPtr[k] = (byte)inputPtr[neighborIndex];
|
||
// }
|
||
// else
|
||
// {
|
||
// neighborsPtr[k] = 0;
|
||
// }
|
||
|
||
// //// 打印前10个像素的邻域数据
|
||
// //if (i < 10)
|
||
// // Console.WriteLine($"i={i}, k={k}: neighborIndex={neighborIndex}, nx={nx}, ny={ny}, val={neighborsPtr[k]}");
|
||
// }
|
||
//}
|
||
|
||
//// 处理函数(复用)
|
||
//private unsafe static byte GetMaxNeighbor(byte* neighbors)
|
||
//{
|
||
// byte max = 0;
|
||
// for (int i = 0; i < 9; i++)
|
||
// if (neighbors[i] > max) max = neighbors[i];
|
||
// return max;
|
||
//}
|
||
|
||
//private unsafe static byte GetMinNeighbor(byte* neighbors)
|
||
//{
|
||
// byte min = 255;
|
||
// for (int i = 0; i < 9; i++)
|
||
// if (neighbors[i] < min) min = neighbors[i];
|
||
// return min;
|
||
//}
|
||
|
||
//// 调用方法
|
||
//private unsafe static MagickImage ManualDilate(MagickImage input) => ProcessNeighborhood(input, GetMaxNeighbor);
|
||
//private unsafe static MagickImage ManualErode(MagickImage input) => ProcessNeighborhood(input, GetMinNeighbor);
|
||
|
||
|
||
//// 取数组最大值
|
||
//private static byte Max(byte[] values)
|
||
//{
|
||
// byte max = 0;
|
||
// foreach (var v in values)
|
||
// if (v > max) max = v;
|
||
// return max;
|
||
//}
|
||
|
||
//// 取数组最小值
|
||
//private static byte Min(byte[] values)
|
||
//{
|
||
// byte min = 255;
|
||
// foreach (var v in values)
|
||
// if (v < min) min = v;
|
||
// return min;
|
||
//}
|
||
|
||
///// <summary>
|
||
///// 基于中心点旋转图像角度,保持原始尺寸
|
||
///// </summary>
|
||
///// <param name="original">原始图像对象</param>
|
||
///// <param name="degrees">旋转角度值(正值为顺时针)</param>
|
||
///// <param name="backgroundColor">旋转后空白区域的背景色</param>
|
||
///// <returns>旋转后的图像对象</returns>
|
||
//public static MagickImage RotateInCenter(MagickImage original, double degrees, MagickColor backgroundColor)
|
||
//{
|
||
// // 处理0度旋转的特殊情况,直接克隆原图避免不必要的处理
|
||
// if (degrees == 0)
|
||
// {
|
||
// return original;
|
||
// }
|
||
|
||
// // 获取原始图像尺寸和中心点
|
||
// uint width = original.Width;
|
||
// uint height = original.Height;
|
||
// int centerX = (int)width / 2;
|
||
// int centerY = (int)height / 2;
|
||
|
||
// // 计算旋转后所需的画布大小
|
||
// var (rotatedWidth, rotatedHeight) = CalculateRotatedDimensions1(width, height, degrees);
|
||
|
||
// // 创建临时画布,大小足以容纳旋转后的图像
|
||
// using (var tempCanvas = new MagickImage(backgroundColor, rotatedWidth, rotatedHeight))
|
||
// {
|
||
// // 将原始图像绘制到临时画布,使其中心点与临时画布中心对齐
|
||
// int tempOffsetX = (int)(rotatedWidth / 2 - centerX);
|
||
// int tempOffsetY = (int)(rotatedHeight / 2 - centerY);
|
||
// tempCanvas.Composite(original, tempOffsetX, tempOffsetY, CompositeOperator.Over);
|
||
|
||
// // 执行旋转操作
|
||
// tempCanvas.Rotate(degrees);
|
||
|
||
// // 创建最终图像(保持原始尺寸)
|
||
// var result = new MagickImage(backgroundColor, width, height);
|
||
|
||
// // 计算临时画布在最终图像中的居中位置
|
||
// int finalOffsetX = (int)(width / 2 - rotatedWidth / 2);
|
||
// int finalOffsetY = (int)(height / 2 - rotatedHeight / 2);
|
||
|
||
// // 将旋转后的内容合成到最终图像
|
||
// result.Composite(tempCanvas, finalOffsetX, finalOffsetY, CompositeOperator.Over);
|
||
|
||
// return result;
|
||
// }
|
||
//}
|
||
|
||
///// <summary>
|
||
///// 计算旋转后的图像尺寸
|
||
///// </summary>
|
||
//private static (uint Width, uint Height) CalculateRotatedDimensions1(uint width, uint height, double degrees)
|
||
//{
|
||
// double radians = Math.Abs(degrees * Math.PI / 180);
|
||
// double cos = Math.Cos(radians);
|
||
// double sin = Math.Sin(radians);
|
||
|
||
// // 计算旋转后所需的最小尺寸,确保内容不被截断
|
||
// uint rotatedWidth = (uint)Math.Ceiling(width * cos + height * sin);
|
||
// uint rotatedHeight = (uint)Math.Ceiling(width * sin + height * cos);
|
||
|
||
// // 确保尺寸为偶数,避免居中时出现偏移
|
||
// if (rotatedWidth % 2 != 0) rotatedWidth++;
|
||
// if (rotatedHeight % 2 != 0) rotatedHeight++;
|
||
|
||
// return (rotatedWidth, rotatedHeight);
|
||
//}
|
||
|
||
/// <summary>
|
||
/// 完成合并并保存最终结果
|
||
/// </summary>
|
||
public void Complete(int dpiX, int dpiY)
|
||
{
|
||
if (canvas != null)
|
||
{
|
||
canvas.Density = new Density(dpiX, dpiY);
|
||
canvas.Write(_outputPath); // 覆盖临时文件(磁盘操作)
|
||
}
|
||
}
|
||
|
||
public void Dispose()
|
||
{
|
||
if (canvas != null)
|
||
{
|
||
canvas.Dispose();
|
||
}
|
||
}
|
||
}
|
||
}
|