·
This commit is contained in:
parent
4969d78386
commit
db9a40846d
@ -15,5 +15,11 @@ namespace XPrint.Production.Business.Entities
|
|||||||
/// 订单状态
|
/// 订单状态
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public OrderStatus Status { get; set; } = OrderStatus.None;
|
public OrderStatus Status { get; set; } = OrderStatus.None;
|
||||||
|
|
||||||
|
[JsonPropertyName("startTime")]
|
||||||
|
public DateTime? StartTime { get; set; } = null;
|
||||||
|
|
||||||
|
[JsonPropertyName("endTime")]
|
||||||
|
public DateTime? EndTime { get; set; } = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,7 +11,7 @@ namespace XPrint.Production.Business.ImageLogic.Conf
|
|||||||
public string ProductionImageFolder { get; set; } = "";
|
public string ProductionImageFolder { get; set; } = "";
|
||||||
public int Dpi { get; set; } = 300;
|
public int Dpi { get; set; } = 300;
|
||||||
|
|
||||||
public float ProductionHeight { get; set; } = 650;//单位:MM
|
public float ProductionHeight { get; set; } = 590;//单位:MM
|
||||||
public LocalConfigure() { }
|
public LocalConfigure() { }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,6 +29,19 @@ namespace XPrint.Production.Business.ImageLogic.Enitites
|
|||||||
public float MaskHeight { get; set; }
|
public float MaskHeight { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
[Key("maskProductX")]
|
||||||
|
public float MaskProductX { get; set; }
|
||||||
|
|
||||||
|
[Key("maskProductY")]
|
||||||
|
public float MaskProductY { get; set; }
|
||||||
|
|
||||||
|
[Key("maskProductWidth")]
|
||||||
|
public float MaskProductWidth { get; set; }
|
||||||
|
|
||||||
|
[Key("maskProductHeight")]
|
||||||
|
public float MaskProductHeight { get; set; }
|
||||||
|
|
||||||
|
|
||||||
[Key("canvasWidth")]
|
[Key("canvasWidth")]
|
||||||
public uint CanvasWidth { get; set; }
|
public uint CanvasWidth { get; set; }
|
||||||
|
|
||||||
|
|||||||
@ -57,7 +57,7 @@ namespace XPrint.Production.Business.ImageLogic
|
|||||||
}
|
}
|
||||||
var printWidth = ImageTool.MMToPxWithDpi(413.91f, dpi); //打印正反面的宽度
|
var printWidth = ImageTool.MMToPxWithDpi(413.91f, dpi); //打印正反面的宽度
|
||||||
var printHeight = ImageTool.MMToPxWithDpi(productionHeight, dpi); //打印正反面的高度 原来是650
|
var printHeight = ImageTool.MMToPxWithDpi(productionHeight, dpi); //打印正反面的高度 原来是650
|
||||||
var printRuleHeight = ImageTool.MMToPxWithDpi(650, dpi); //打印正反面的高度 原来是650
|
//var printRuleHeight = ImageTool.MMToPxWithDpi(590, dpi); //打印正反面的高度 原来是650
|
||||||
var printSpace = ImageTool.MMToPxWithDpi(185.087f, dpi); //打印水平间距
|
var printSpace = ImageTool.MMToPxWithDpi(185.087f, dpi); //打印水平间距
|
||||||
|
|
||||||
var productCanvasWidth = (printWidth + printSpace) * 3; //三连张正反面
|
var productCanvasWidth = (printWidth + printSpace) * 3; //三连张正反面
|
||||||
@ -70,10 +70,12 @@ namespace XPrint.Production.Business.ImageLogic
|
|||||||
// ThreadCount = Environment.ProcessorCount
|
// ThreadCount = Environment.ProcessorCount
|
||||||
//};
|
//};
|
||||||
|
|
||||||
|
var horOffset = ImageTool.MMToPxWithDpi(7f, dpi); //打印正反面的宽度
|
||||||
var productCanvas = new MagickImage(MagickColors.Transparent, (uint)productCanvasWidth, (uint)productCanvasHeight);
|
var productCanvas = new MagickImage(MagickColors.Transparent, (uint)productCanvasWidth, (uint)productCanvasHeight);
|
||||||
//productCanvas.SetCompression(CompressionMethod.NoCompression);
|
//productCanvas.SetCompression(CompressionMethod.NoCompression);
|
||||||
//SetImageProperty(productCanvas);
|
//SetImageProperty(productCanvas);
|
||||||
//int currentY = 0;
|
//int currentY = 0;
|
||||||
|
//uint limitHandleSize = 30000;
|
||||||
//for (int i = 0; i < 3; i++)
|
//for (int i = 0; i < 3; i++)
|
||||||
//Parallel.For(0, info.CanvasInfos.Length, (idx) =>
|
//Parallel.For(0, info.CanvasInfos.Length, (idx) =>
|
||||||
Parallel.For(0, infos.Count, (idx) =>
|
Parallel.For(0, infos.Count, (idx) =>
|
||||||
@ -86,13 +88,36 @@ namespace XPrint.Production.Business.ImageLogic
|
|||||||
currentX = (printWidth + printSpace) * (currentIndx + printIndex);
|
currentX = (printWidth + printSpace) * (currentIndx + printIndex);
|
||||||
}
|
}
|
||||||
//var canvasInfo = info.CanvasInfos[idx];
|
//var canvasInfo = info.CanvasInfos[idx];
|
||||||
|
|
||||||
|
|
||||||
var canvasInfo = info.CanvasInfos[0];//正面测试
|
var canvasInfo = info.CanvasInfos[0];//正面测试
|
||||||
float scale = printRuleHeight / canvasInfo.MaskHeight;
|
|
||||||
float destOffsetX = (canvasInfo.MaskWidth - canvasInfo.CanvasWidth) * scale * 0.5f;
|
|
||||||
float destOffsetY = (canvasInfo.MaskHeight - canvasInfo.CanvasHeight) * scale * 0.5f;
|
|
||||||
|
|
||||||
using var drawCanvas = new MagickImage(MagickColors.Transparent, (uint)printWidth, (uint)printRuleHeight);
|
|
||||||
|
|
||||||
|
//float scaleX1 = canvasInfo.MaskWidth / canvasInfo.MaskProductWidth;
|
||||||
|
//float scaleY1 = canvasInfo.MaskHeight / canvasInfo.MaskProductHeight;
|
||||||
|
|
||||||
|
//float scaleX = printWidth / canvasInfo.MaskProductWidth;
|
||||||
|
float heightScale1 = printHeight / canvasInfo.MaskProductHeight;
|
||||||
|
float heightScale2 = printHeight / canvasInfo.MaskHeight;
|
||||||
|
|
||||||
|
float widthScale1 = printWidth / canvasInfo.MaskProductWidth;
|
||||||
|
float widthScale2 = printWidth / canvasInfo.MaskWidth;
|
||||||
|
|
||||||
|
|
||||||
|
//float offsetY = (canvasInfo.MaskProductHeight - canvasInfo.MaskHeight) * scaleY2;
|
||||||
|
float destOffsetX1 = (canvasInfo.MaskWidth - canvasInfo.CanvasWidth) * widthScale2 * 0.5f;
|
||||||
|
float destOffsetY1 = (canvasInfo.MaskHeight - canvasInfo.CanvasHeight) * heightScale2 * 0.5f;
|
||||||
|
|
||||||
|
//float destOffsetX2 = (canvasInfo.MaskProductWidth - canvasInfo.CanvasWidth) * heightScale2 * 0.5f;
|
||||||
|
//float destOffsetY2 = (canvasInfo.MaskProductHeight - canvasInfo.CanvasHeight) * heightScale2 * 0.5f;
|
||||||
|
|
||||||
|
int cropX = (int)((canvasInfo.MaskProductX - canvasInfo.MaskX) * widthScale2);
|
||||||
|
int cropY = (int)((canvasInfo.MaskProductY - canvasInfo.MaskY) * heightScale2);// (int)((canvasInfo.MaskProductY - canvasInfo.MaskY) * heightScale2);
|
||||||
|
|
||||||
|
uint cropWidth = (uint)(canvasInfo.MaskProductWidth * widthScale2);
|
||||||
|
uint cropHeight = (uint)(canvasInfo.MaskProductHeight * heightScale2);
|
||||||
|
|
||||||
|
using var drawCanvas = new MagickImage(MagickColors.Transparent, (uint)printWidth, (uint)printHeight);
|
||||||
//SetImageProperty(drawCanvas);
|
//SetImageProperty(drawCanvas);
|
||||||
|
|
||||||
//MagickImage? maskImage = null;
|
//MagickImage? maskImage = null;
|
||||||
@ -105,8 +130,36 @@ namespace XPrint.Production.Business.ImageLogic
|
|||||||
{
|
{
|
||||||
|
|
||||||
var imgInfo = canvasInfo.Images[j];
|
var imgInfo = canvasInfo.Images[j];
|
||||||
|
|
||||||
|
//uint resizeWidth = (uint)imgInfo.OriginWidth;
|
||||||
|
//uint resizeHeight = (uint)imgInfo.OriginHeight;
|
||||||
|
//if (resizeWidth > limitHandleSize || resizeHeight > limitHandleSize)
|
||||||
|
//{
|
||||||
|
// if (resizeWidth > resizeHeight)
|
||||||
|
// {
|
||||||
|
// resizeWidth = limitHandleSize;
|
||||||
|
// resizeHeight = (uint)(imgInfo.OriginHeight * (resizeWidth / (float)imgInfo.OriginWidth));
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// resizeHeight = limitHandleSize;
|
||||||
|
// resizeWidth = (uint)(imgInfo.OriginWidth * (resizeHeight / (float)imgInfo.OriginHeight));
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
||||||
|
//var settings = new MagickReadSettings
|
||||||
|
//{
|
||||||
|
// Width = resizeWidth, // 目标宽度
|
||||||
|
// Height = resizeHeight, // 目标高度
|
||||||
|
//};
|
||||||
|
|
||||||
|
//using var image = new MagickImage(imgInfo.DataBuffer, settings);
|
||||||
using var image = new MagickImage(imgInfo.DataBuffer);
|
using var image = new MagickImage(imgInfo.DataBuffer);
|
||||||
|
|
||||||
image.AutoOrient();//修复手机拍的图片的方向问题
|
image.AutoOrient();//修复手机拍的图片的方向问题
|
||||||
|
// 方法1:alpha通道阈值处理(消除半透明)
|
||||||
|
|
||||||
//SetImageProperty(image);
|
//SetImageProperty(image);
|
||||||
image.BackgroundColor = MagickColors.Transparent;
|
image.BackgroundColor = MagickColors.Transparent;
|
||||||
|
|
||||||
@ -167,10 +220,10 @@ namespace XPrint.Production.Business.ImageLogic
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
float destX = imgInfo.X * scale;
|
float destX = imgInfo.X * heightScale2;
|
||||||
float destY = imgInfo.Y * scale;
|
float destY = imgInfo.Y * heightScale2;
|
||||||
float destWidth = imgInfo.Width * scale;
|
float destWidth = imgInfo.Width * heightScale2;
|
||||||
float destHeight = imgInfo.Height * scale;
|
float destHeight = imgInfo.Height * heightScale2;
|
||||||
|
|
||||||
//float cropedWidth = destWidth;
|
//float cropedWidth = destWidth;
|
||||||
//float cropedHeight = destHeight;
|
//float cropedHeight = destHeight;
|
||||||
@ -185,23 +238,30 @@ namespace XPrint.Production.Business.ImageLogic
|
|||||||
//}
|
//}
|
||||||
|
|
||||||
BatchImageCombiner.CopyRegion(image, drawCanvas, 0, 0, image.Width, image.Height,
|
BatchImageCombiner.CopyRegion(image, drawCanvas, 0, 0, image.Width, image.Height,
|
||||||
(int)(destOffsetX + destX), (int)(destOffsetY + destY), (uint)destWidth, (uint)destHeight);
|
(int)(destOffsetX1 + destX), (int)(destOffsetY1 + destY), (uint)destWidth, (uint)destHeight);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
uint orginWidth = drawCanvas.Width;
|
uint orginWidth = drawCanvas.Width;
|
||||||
if (printHeight != printRuleHeight)
|
uint orginHeight = drawCanvas.Height;
|
||||||
{
|
|
||||||
drawCanvas.Scale((uint)(drawCanvas.Width * (printHeight / printRuleHeight)), (uint)printHeight);
|
drawCanvas.Crop(new MagickGeometry(cropX, cropY, cropWidth, cropHeight));
|
||||||
}
|
//drawCanvas.Resize(orginWidth, orginHeight);
|
||||||
//计算偏移,以便居中
|
int offset = 0;
|
||||||
uint offset = orginWidth - drawCanvas.Width;
|
//if (printHeight != printRuleHeight)
|
||||||
|
//{
|
||||||
|
// drawCanvas.Scale((uint)(drawCanvas.Width * (printHeight / printRuleHeight)), (uint)printHeight);
|
||||||
|
// //计算偏移,以便居中
|
||||||
|
// offset = (int)orginWidth - (int)drawCanvas.Width;
|
||||||
|
//}
|
||||||
|
|
||||||
//if (maskImage != null)
|
//if (maskImage != null)
|
||||||
//{
|
//{
|
||||||
// //maskImage.AutoOrient();
|
// //maskImage.AutoOrient();
|
||||||
// //if (maskWidth != 0 && maskHeight != 0)
|
// //if (maskProductWidth != 0 && maskProductHeight != 0)
|
||||||
// //{
|
// //{
|
||||||
// // drawCanvas.Crop((uint)(currentStartX + maskWidth), (uint)maskHeight);
|
// // drawCanvas.Crop((uint)(currentStartX + maskProductWidth), (uint)maskProductHeight);
|
||||||
// //}
|
// //}
|
||||||
|
|
||||||
// float destWidth = maskImage.Width * scale;
|
// float destWidth = maskImage.Width * scale;
|
||||||
@ -212,7 +272,7 @@ namespace XPrint.Production.Business.ImageLogic
|
|||||||
// maskImage.Dispose();
|
// maskImage.Dispose();
|
||||||
//}
|
//}
|
||||||
BatchImageCombiner.CopyRegion(drawCanvas, productCanvas, 0, 0, drawCanvas.Width, drawCanvas.Height,
|
BatchImageCombiner.CopyRegion(drawCanvas, productCanvas, 0, 0, drawCanvas.Width, drawCanvas.Height,
|
||||||
(int)(currentX + offset), 0, (uint)printWidth, (uint)printHeight);
|
(int)(currentX + offset - horOffset), 0, (uint)printWidth, (uint)printHeight, true);
|
||||||
//}
|
//}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,9 @@ using ImageMagick;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading.Channels;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using XPrint.Production.Business.ImageLogic.Enitites;
|
using XPrint.Production.Business.ImageLogic.Enitites;
|
||||||
|
|
||||||
namespace XPrint.Production.Business.ImageLogic
|
namespace XPrint.Production.Business.ImageLogic
|
||||||
@ -13,7 +16,7 @@ namespace XPrint.Production.Business.ImageLogic
|
|||||||
private readonly uint _totalWidth;
|
private readonly uint _totalWidth;
|
||||||
private readonly uint _totalHeight;
|
private readonly uint _totalHeight;
|
||||||
private static string _tempDirectory = null!;
|
private static string _tempDirectory = null!;
|
||||||
private bool _isFirstImage = true;
|
//private bool _isFirstImage = true;
|
||||||
private MagickImage canvas = null!;
|
private MagickImage canvas = null!;
|
||||||
|
|
||||||
|
|
||||||
@ -43,7 +46,14 @@ namespace XPrint.Production.Business.ImageLogic
|
|||||||
{
|
{
|
||||||
if (Directory.Exists(_tempDirectory))
|
if (Directory.Exists(_tempDirectory))
|
||||||
{
|
{
|
||||||
Directory.Delete(_tempDirectory, true);
|
try
|
||||||
|
{
|
||||||
|
Directory.Delete(_tempDirectory, true);
|
||||||
|
}
|
||||||
|
catch(Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine(ex.Message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,7 +105,7 @@ namespace XPrint.Production.Business.ImageLogic
|
|||||||
/// <param name="destHeight">目标区域高度(可缩放)</param>
|
/// <param name="destHeight">目标区域高度(可缩放)</param>
|
||||||
public static void CopyRegion(MagickImage sourceImage, MagickImage destImage,
|
public static void CopyRegion(MagickImage sourceImage, MagickImage destImage,
|
||||||
int srcX, int srcY, uint srcWidth, uint srcHeight,
|
int srcX, int srcY, uint srcWidth, uint srcHeight,
|
||||||
int destX, int destY, uint destWidth, uint destHeight)
|
int destX, int destY, uint destWidth, uint destHeight, bool handlePngEdge = false)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (srcX > 0 || srcY > 0 || srcWidth < sourceImage.Width || srcHeight < sourceImage.Height)
|
if (srcX > 0 || srcY > 0 || srcWidth < sourceImage.Width || srcHeight < sourceImage.Height)
|
||||||
@ -112,6 +122,15 @@ namespace XPrint.Production.Business.ImageLogic
|
|||||||
// 确保目标尺寸正确应用
|
// 确保目标尺寸正确应用
|
||||||
sourceImage.Resize(destGeometry);
|
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. 将裁剪并缩放后的区域粘贴到目标图像
|
// 4. 将裁剪并缩放后的区域粘贴到目标图像
|
||||||
destImage.Composite(sourceImage, destX, destY, CompositeOperator.Over);
|
destImage.Composite(sourceImage, destX, destY, CompositeOperator.Over);
|
||||||
sourceImage.Dispose();
|
sourceImage.Dispose();
|
||||||
@ -119,6 +138,545 @@ namespace XPrint.Production.Business.ImageLogic
|
|||||||
// $"拷贝到目标图像位置({destX},{destY}),尺寸为({destWidth},{destHeight})");
|
// $"拷贝到目标图像位置({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>
|
||||||
///// 基于中心点旋转图像角度,保持原始尺寸
|
///// 基于中心点旋转图像角度,保持原始尺寸
|
||||||
///// </summary>
|
///// </summary>
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
|
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@ -84,6 +84,12 @@ namespace XPrint.Production.Controls
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ResetQueueStatus()
|
||||||
|
{
|
||||||
|
Btn_JoinQueue.Text = "加入生产队列";
|
||||||
|
Btn_JoinQueue.Type = AntdUI.TTypeMini.Primary;
|
||||||
|
}
|
||||||
|
|
||||||
private void OrderItem_Load(object sender, EventArgs e)
|
private void OrderItem_Load(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
_ = InitControlsInfo();
|
_ = InitControlsInfo();
|
||||||
@ -145,15 +151,22 @@ namespace XPrint.Production.Controls
|
|||||||
|
|
||||||
public async Task RemoveSelf()
|
public async Task RemoveSelf()
|
||||||
{
|
{
|
||||||
ProductionQueueHandler.RemoveProductioningData(this);
|
try
|
||||||
ProductionQueueHandler.RemoveOrderItemData(this);
|
|
||||||
await ProductionQueueHandler.OrderInfoApi.ProductionBatchDelete([SyncInfo.Id.ToString()]);
|
|
||||||
await ProductionQueueHandler.RemoveItemOssFiles(SyncInfo);//移除OSS上的文件
|
|
||||||
await Invoke(async () =>
|
|
||||||
{
|
{
|
||||||
this.Dispose();
|
ProductionQueueHandler.RemoveProductioningData(this);
|
||||||
await ProductionQueueHandler.ReloadItems();
|
ProductionQueueHandler.RemoveOrderItemData(this);
|
||||||
});
|
await ProductionQueueHandler.OrderInfoApi.ProductionBatchDelete([SyncInfo.Id.ToString()]);
|
||||||
|
await ProductionQueueHandler.RemoveItemOssFiles(SyncInfo);//移除OSS上的文件
|
||||||
|
await Invoke(async () =>
|
||||||
|
{
|
||||||
|
this.Dispose();
|
||||||
|
await ProductionQueueHandler.ReloadItems();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch(Exception ex)
|
||||||
|
{
|
||||||
|
AntdUI.Message.error(ParentForm!, "移除异常:" + ex.Message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PicBox_SceneImage_Click(object sender, EventArgs e)
|
private void PicBox_SceneImage_Click(object sender, EventArgs e)
|
||||||
|
|||||||
30
XPrint.Production/MainForm.Designer.cs
generated
30
XPrint.Production/MainForm.Designer.cs
generated
@ -44,6 +44,8 @@
|
|||||||
Input_ProductionHeight = new AntdUI.InputNumber();
|
Input_ProductionHeight = new AntdUI.InputNumber();
|
||||||
Lbl_MM = new Label();
|
Lbl_MM = new Label();
|
||||||
Btn_Refresh = new AntdUI.Button();
|
Btn_Refresh = new AntdUI.Button();
|
||||||
|
DatePickerRange = new AntdUI.DatePickerRange();
|
||||||
|
Lbl_Date = new Label();
|
||||||
((System.ComponentModel.ISupportInitialize)PicBox_QRCode).BeginInit();
|
((System.ComponentModel.ISupportInitialize)PicBox_QRCode).BeginInit();
|
||||||
SuspendLayout();
|
SuspendLayout();
|
||||||
//
|
//
|
||||||
@ -199,8 +201,8 @@
|
|||||||
Input_ProductionHeight.Radius = 0;
|
Input_ProductionHeight.Radius = 0;
|
||||||
Input_ProductionHeight.Size = new Size(191, 41);
|
Input_ProductionHeight.Size = new Size(191, 41);
|
||||||
Input_ProductionHeight.TabIndex = 16;
|
Input_ProductionHeight.TabIndex = 16;
|
||||||
Input_ProductionHeight.Text = "650";
|
Input_ProductionHeight.Text = "590";
|
||||||
Input_ProductionHeight.Value = new decimal(new int[] { 650, 0, 0, 0 });
|
Input_ProductionHeight.Value = new decimal(new int[] { 590, 0, 0, 0 });
|
||||||
Input_ProductionHeight.ValueChanged += Input_ProductionHeight_ValueChanged;
|
Input_ProductionHeight.ValueChanged += Input_ProductionHeight_ValueChanged;
|
||||||
//
|
//
|
||||||
// Lbl_MM
|
// Lbl_MM
|
||||||
@ -224,12 +226,33 @@
|
|||||||
Btn_Refresh.Type = AntdUI.TTypeMini.Primary;
|
Btn_Refresh.Type = AntdUI.TTypeMini.Primary;
|
||||||
Btn_Refresh.Click += Btn_Refresh_Click;
|
Btn_Refresh.Click += Btn_Refresh_Click;
|
||||||
//
|
//
|
||||||
|
// DatePickerRange
|
||||||
|
//
|
||||||
|
DatePickerRange.Anchor = AnchorStyles.Top | AnchorStyles.Right;
|
||||||
|
DatePickerRange.Location = new Point(478, 41);
|
||||||
|
DatePickerRange.Name = "DatePickerRange";
|
||||||
|
DatePickerRange.Size = new Size(343, 56);
|
||||||
|
DatePickerRange.TabIndex = 20;
|
||||||
|
//
|
||||||
|
// Lbl_Date
|
||||||
|
//
|
||||||
|
Lbl_Date.Anchor = AnchorStyles.Top | AnchorStyles.Right;
|
||||||
|
Lbl_Date.AutoSize = true;
|
||||||
|
Lbl_Date.ForeColor = SystemColors.ControlLightLight;
|
||||||
|
Lbl_Date.Location = new Point(354, 57);
|
||||||
|
Lbl_Date.Name = "Lbl_Date";
|
||||||
|
Lbl_Date.Size = new Size(118, 24);
|
||||||
|
Lbl_Date.TabIndex = 21;
|
||||||
|
Lbl_Date.Text = "选择列表日期";
|
||||||
|
//
|
||||||
// MainForm
|
// MainForm
|
||||||
//
|
//
|
||||||
AutoScaleDimensions = new SizeF(11F, 24F);
|
AutoScaleDimensions = new SizeF(11F, 24F);
|
||||||
AutoScaleMode = AutoScaleMode.Font;
|
AutoScaleMode = AutoScaleMode.Font;
|
||||||
BackColor = Color.FromArgb(39, 41, 47);
|
BackColor = Color.FromArgb(39, 41, 47);
|
||||||
ClientSize = new Size(1898, 1024);
|
ClientSize = new Size(1898, 1024);
|
||||||
|
Controls.Add(Lbl_Date);
|
||||||
|
Controls.Add(DatePickerRange);
|
||||||
Controls.Add(Btn_Refresh);
|
Controls.Add(Btn_Refresh);
|
||||||
Controls.Add(Lbl_MM);
|
Controls.Add(Lbl_MM);
|
||||||
Controls.Add(Input_ProductionHeight);
|
Controls.Add(Input_ProductionHeight);
|
||||||
@ -246,6 +269,7 @@
|
|||||||
Controls.Add(Lbl_Production_Dict);
|
Controls.Add(Lbl_Production_Dict);
|
||||||
Controls.Add(PicBox_QRCode);
|
Controls.Add(PicBox_QRCode);
|
||||||
Controls.Add(Btn_Configure);
|
Controls.Add(Btn_Configure);
|
||||||
|
MinimumSize = new Size(1920, 1080);
|
||||||
Name = "MainForm";
|
Name = "MainForm";
|
||||||
Text = "生产端";
|
Text = "生产端";
|
||||||
FormClosed += MainForm_FormClosed;
|
FormClosed += MainForm_FormClosed;
|
||||||
@ -272,5 +296,7 @@
|
|||||||
private AntdUI.InputNumber Input_ProductionHeight;
|
private AntdUI.InputNumber Input_ProductionHeight;
|
||||||
private Label Lbl_MM;
|
private Label Lbl_MM;
|
||||||
private AntdUI.Button Btn_Refresh;
|
private AntdUI.Button Btn_Refresh;
|
||||||
|
private AntdUI.DatePickerRange DatePickerRange;
|
||||||
|
private Label Lbl_Date;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -54,7 +54,7 @@ namespace XPrint.Production
|
|||||||
|
|
||||||
InitQRCode();
|
InitQRCode();
|
||||||
ProductionQueueHandler.CheckProdcutionDirectoryExist(this);
|
ProductionQueueHandler.CheckProdcutionDirectoryExist(this);
|
||||||
listTimer.Interval = 5000;
|
listTimer.Interval = 10000;
|
||||||
listTimer.Elapsed += ListTimer_Elapsed;
|
listTimer.Elapsed += ListTimer_Elapsed;
|
||||||
listTimer.Start();
|
listTimer.Start();
|
||||||
|
|
||||||
@ -128,6 +128,10 @@ namespace XPrint.Production
|
|||||||
MessageBox.Show("配置文件错误,请重新配置存储路径等相关参数");
|
MessageBox.Show("配置文件错误,请重新配置存储路径等相关参数");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ProductionQueueHandler.LocalConfigure.ProductionHeight = (float)Input_ProductionHeight.Value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitQRCode()
|
private void InitQRCode()
|
||||||
@ -156,7 +160,7 @@ namespace XPrint.Production
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isLoadingListData)
|
if (isLoadingListData && !isForceRefresh)
|
||||||
{
|
{
|
||||||
Page_Order.Current = beforePage;
|
Page_Order.Current = beforePage;
|
||||||
return;
|
return;
|
||||||
@ -164,7 +168,10 @@ namespace XPrint.Production
|
|||||||
|
|
||||||
isLoadingListData = true;
|
isLoadingListData = true;
|
||||||
|
|
||||||
await Task.Delay(500);
|
//if (!isForceRefresh)
|
||||||
|
//{
|
||||||
|
// await Task.Delay(500);
|
||||||
|
//}
|
||||||
|
|
||||||
beforePage = page;
|
beforePage = page;
|
||||||
PageResult<OrderInfo>? infoResult = null;
|
PageResult<OrderInfo>? infoResult = null;
|
||||||
@ -174,12 +181,14 @@ namespace XPrint.Production
|
|||||||
{
|
{
|
||||||
Page = page,
|
Page = page,
|
||||||
PageSize = ProductionQueueHandler.PageSize,
|
PageSize = ProductionQueueHandler.PageSize,
|
||||||
Status = OrderStatus.Paid
|
Status = OrderStatus.Paid,
|
||||||
|
StartTime = (DatePickerRange.Value != null && DatePickerRange.Value!.Length > 0) ? DatePickerRange.Value[0] : null,
|
||||||
|
EndTime = (DatePickerRange.Value != null && DatePickerRange.Value!.Length > 1) ? DatePickerRange.Value[1].AddDays(1).AddMicroseconds(-1) : null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
AntdUI.Message.error(this, "ɾ³ý×ÊԴʧ°Ü:" + ex.Message);
|
AntdUI.Message.error(this, "GetOrderInfoList Error:" + ex.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (infoResult == null && !isForceRefresh)
|
if (infoResult == null && !isForceRefresh)
|
||||||
@ -206,18 +215,15 @@ namespace XPrint.Production
|
|||||||
{
|
{
|
||||||
Page_Order.Current = 1;
|
Page_Order.Current = 1;
|
||||||
}
|
}
|
||||||
else
|
for (int i = 0; i < infoResult?.Data.Count; i++)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < infoResult?.Data.Count; i++)
|
var info = infoResult.Data[i];
|
||||||
{
|
var orderItem = new OrderItem(info);
|
||||||
var info = infoResult.Data[i];
|
ProductionQueueHandler.Enqueue(ProductionQueueHandler.ProductionQueue, orderItem);
|
||||||
var orderItem = new OrderItem(info);
|
|
||||||
ProductionQueueHandler.Enqueue(ProductionQueueHandler.ProductionQueue, orderItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
Page_Order.Total = (int)infoResult?.Total!;
|
|
||||||
ReloadOrderItems(infoResult.PageSize, page, isForceRefresh);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Page_Order.Total = (int)infoResult?.Total!;
|
||||||
|
ReloadOrderItems(infoResult.PageSize, page, isForceRefresh);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@ -329,7 +335,7 @@ namespace XPrint.Production
|
|||||||
}
|
}
|
||||||
await SyncImage(page);
|
await SyncImage(page);
|
||||||
}
|
}
|
||||||
catch(Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Console.WriteLine("Disposed,Error:", ex.Message);
|
Console.WriteLine("Disposed,Error:", ex.Message);
|
||||||
}
|
}
|
||||||
@ -360,13 +366,16 @@ namespace XPrint.Production
|
|||||||
{
|
{
|
||||||
ListPanel_Orders.Controls.Remove(removeItems[i]);
|
ListPanel_Orders.Controls.Remove(removeItems[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
items.Reverse();
|
||||||
for (int i = 0; i < items.Count; i++)
|
for (int i = 0; i < items.Count; i++)
|
||||||
{
|
{
|
||||||
if (ListPanel_Orders.Controls.Find(items[i].Name, true).Length == 0)
|
if (ListPanel_Orders.Controls.Find(items[i].Name, true).Length == 0)
|
||||||
{
|
{
|
||||||
var item = items[i];
|
var item = items[i];
|
||||||
|
|
||||||
ListPanel_Orders.Controls.Add(item);
|
ListPanel_Orders.Controls.Add(item);
|
||||||
ListPanel_Orders.Controls.SetChildIndex(item, 0);
|
//ListPanel_Orders.Controls.SetChildIndex(item, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//ListPanel_Orders.ScrollBar!.ValueY = beforeScrollTop;
|
//ListPanel_Orders.ScrollBar!.ValueY = beforeScrollTop;
|
||||||
@ -405,7 +414,13 @@ namespace XPrint.Production
|
|||||||
|
|
||||||
private async void Btn_Refresh_Click(object sender, EventArgs e)
|
private async void Btn_Refresh_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
await SyncImage(1, true);
|
if (isLoadingListData)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await Task.Delay(500);
|
||||||
|
//Page_Order.Current = 1;
|
||||||
|
await SyncImage(Page_Order.Current, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -229,16 +229,17 @@ namespace XPrint.Production
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
imageCreation.Create(infos, LocalConfigure.Dpi, productionImageFile, LocalConfigure.ProductionHeight, printIndex);//暂时只支持tif
|
imageCreation.Create(infos, LocalConfigure.Dpi, productionImageFile, LocalConfigure.ProductionHeight, printIndex);//暂时只支持tif
|
||||||
|
|
||||||
//await RemoveItemOssFiles(syncImageInfo);//移除OSS上的文件
|
//await RemoveItemOssFiles(syncImageInfo);//移除OSS上的文件
|
||||||
await form.Invoke(async () =>
|
form.Invoke(() =>
|
||||||
{
|
{
|
||||||
for (int i = 0; i < removeItems.Count; i++)
|
for (int i = 0; i < removeItems.Count; i++)
|
||||||
{
|
{
|
||||||
RemoveOrderItem(removeItems[i]);//从分页数据中移除
|
removeItems[i].ResetQueueStatus();
|
||||||
|
//RemoveOrderItem(removeItems[i]);//从分页数据中移除
|
||||||
}
|
}
|
||||||
AntdUI.Message.success(form, $"生产图 {productionImageFile} 生成成功!");
|
AntdUI.Message.success(form, $"生产图 {productionImageFile} 生成成功!");
|
||||||
await ReloadDataEvent?.Invoke()!;
|
//await ReloadDataEvent?.Invoke()!;
|
||||||
});
|
});
|
||||||
infos.Clear();//清空这组的生产队列
|
infos.Clear();//清空这组的生产队列
|
||||||
removeItems.Clear();
|
removeItems.Clear();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user