This commit is contained in:
wuyanchen 2025-12-22 16:12:13 +08:00
parent 4969d78386
commit db9a40846d
10 changed files with 748 additions and 55 deletions

View File

@ -15,5 +15,11 @@ namespace XPrint.Production.Business.Entities
/// 订单状态
/// </summary>
public OrderStatus Status { get; set; } = OrderStatus.None;
[JsonPropertyName("startTime")]
public DateTime? StartTime { get; set; } = null;
[JsonPropertyName("endTime")]
public DateTime? EndTime { get; set; } = null;
}
}

View File

@ -11,7 +11,7 @@ namespace XPrint.Production.Business.ImageLogic.Conf
public string ProductionImageFolder { get; set; } = "";
public int Dpi { get; set; } = 300;
public float ProductionHeight { get; set; } = 650;//单位MM
public float ProductionHeight { get; set; } = 590;//单位MM
public LocalConfigure() { }
}

View File

@ -29,6 +29,19 @@ namespace XPrint.Production.Business.ImageLogic.Enitites
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")]
public uint CanvasWidth { get; set; }

View File

@ -57,7 +57,7 @@ namespace XPrint.Production.Business.ImageLogic
}
var printWidth = ImageTool.MMToPxWithDpi(413.91f, dpi); //打印正反面的宽度
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 productCanvasWidth = (printWidth + printSpace) * 3; //三连张正反面
@ -70,10 +70,12 @@ namespace XPrint.Production.Business.ImageLogic
// ThreadCount = Environment.ProcessorCount
//};
var horOffset = ImageTool.MMToPxWithDpi(7f, dpi); //打印正反面的宽度
var productCanvas = new MagickImage(MagickColors.Transparent, (uint)productCanvasWidth, (uint)productCanvasHeight);
//productCanvas.SetCompression(CompressionMethod.NoCompression);
//SetImageProperty(productCanvas);
//int currentY = 0;
//uint limitHandleSize = 30000;
//for (int i = 0; i < 3; i++)
//Parallel.For(0, info.CanvasInfos.Length, (idx) =>
Parallel.For(0, infos.Count, (idx) =>
@ -86,13 +88,36 @@ namespace XPrint.Production.Business.ImageLogic
currentX = (printWidth + printSpace) * (currentIndx + printIndex);
}
//var canvasInfo = info.CanvasInfos[idx];
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);
//MagickImage? maskImage = null;
@ -105,8 +130,36 @@ namespace XPrint.Production.Business.ImageLogic
{
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);
image.AutoOrient();//修复手机拍的图片的方向问题
// 方法1alpha通道阈值处理消除半透明
//SetImageProperty(image);
image.BackgroundColor = MagickColors.Transparent;
@ -167,10 +220,10 @@ namespace XPrint.Production.Business.ImageLogic
}
float destX = imgInfo.X * scale;
float destY = imgInfo.Y * scale;
float destWidth = imgInfo.Width * scale;
float destHeight = imgInfo.Height * scale;
float destX = imgInfo.X * heightScale2;
float destY = imgInfo.Y * heightScale2;
float destWidth = imgInfo.Width * heightScale2;
float destHeight = imgInfo.Height * heightScale2;
//float cropedWidth = destWidth;
//float cropedHeight = destHeight;
@ -185,23 +238,30 @@ namespace XPrint.Production.Business.ImageLogic
//}
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;
if (printHeight != printRuleHeight)
{
drawCanvas.Scale((uint)(drawCanvas.Width * (printHeight / printRuleHeight)), (uint)printHeight);
}
//计算偏移,以便居中
uint offset = orginWidth - drawCanvas.Width;
uint orginHeight = drawCanvas.Height;
drawCanvas.Crop(new MagickGeometry(cropX, cropY, cropWidth, cropHeight));
//drawCanvas.Resize(orginWidth, orginHeight);
int offset = 0;
//if (printHeight != printRuleHeight)
//{
// drawCanvas.Scale((uint)(drawCanvas.Width * (printHeight / printRuleHeight)), (uint)printHeight);
// //计算偏移,以便居中
// offset = (int)orginWidth - (int)drawCanvas.Width;
//}
//if (maskImage != null)
//{
// //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;
@ -212,7 +272,7 @@ namespace XPrint.Production.Business.ImageLogic
// maskImage.Dispose();
//}
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);
//}
});

View File

@ -3,6 +3,9 @@ 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
@ -13,7 +16,7 @@ namespace XPrint.Production.Business.ImageLogic
private readonly uint _totalWidth;
private readonly uint _totalHeight;
private static string _tempDirectory = null!;
private bool _isFirstImage = true;
//private bool _isFirstImage = true;
private MagickImage canvas = null!;
@ -43,7 +46,14 @@ namespace XPrint.Production.Business.ImageLogic
{
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>
public static void CopyRegion(MagickImage sourceImage, MagickImage destImage,
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)
@ -112,6 +122,15 @@ namespace XPrint.Production.Business.ImageLogic
// 确保目标尺寸正确应用
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();
@ -119,6 +138,545 @@ namespace XPrint.Production.Business.ImageLogic
// $"拷贝到目标图像位置({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>

View File

@ -4,6 +4,7 @@
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>

View File

@ -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)
{
_ = InitControlsInfo();
@ -145,15 +151,22 @@ namespace XPrint.Production.Controls
public async Task RemoveSelf()
{
ProductionQueueHandler.RemoveProductioningData(this);
ProductionQueueHandler.RemoveOrderItemData(this);
await ProductionQueueHandler.OrderInfoApi.ProductionBatchDelete([SyncInfo.Id.ToString()]);
await ProductionQueueHandler.RemoveItemOssFiles(SyncInfo);//移除OSS上的文件
await Invoke(async () =>
try
{
this.Dispose();
await ProductionQueueHandler.ReloadItems();
});
ProductionQueueHandler.RemoveProductioningData(this);
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)

View File

@ -44,6 +44,8 @@
Input_ProductionHeight = new AntdUI.InputNumber();
Lbl_MM = new Label();
Btn_Refresh = new AntdUI.Button();
DatePickerRange = new AntdUI.DatePickerRange();
Lbl_Date = new Label();
((System.ComponentModel.ISupportInitialize)PicBox_QRCode).BeginInit();
SuspendLayout();
//
@ -199,8 +201,8 @@
Input_ProductionHeight.Radius = 0;
Input_ProductionHeight.Size = new Size(191, 41);
Input_ProductionHeight.TabIndex = 16;
Input_ProductionHeight.Text = "650";
Input_ProductionHeight.Value = new decimal(new int[] { 650, 0, 0, 0 });
Input_ProductionHeight.Text = "590";
Input_ProductionHeight.Value = new decimal(new int[] { 590, 0, 0, 0 });
Input_ProductionHeight.ValueChanged += Input_ProductionHeight_ValueChanged;
//
// Lbl_MM
@ -224,12 +226,33 @@
Btn_Refresh.Type = AntdUI.TTypeMini.Primary;
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
//
AutoScaleDimensions = new SizeF(11F, 24F);
AutoScaleMode = AutoScaleMode.Font;
BackColor = Color.FromArgb(39, 41, 47);
ClientSize = new Size(1898, 1024);
Controls.Add(Lbl_Date);
Controls.Add(DatePickerRange);
Controls.Add(Btn_Refresh);
Controls.Add(Lbl_MM);
Controls.Add(Input_ProductionHeight);
@ -246,6 +269,7 @@
Controls.Add(Lbl_Production_Dict);
Controls.Add(PicBox_QRCode);
Controls.Add(Btn_Configure);
MinimumSize = new Size(1920, 1080);
Name = "MainForm";
Text = "生产端";
FormClosed += MainForm_FormClosed;
@ -272,5 +296,7 @@
private AntdUI.InputNumber Input_ProductionHeight;
private Label Lbl_MM;
private AntdUI.Button Btn_Refresh;
private AntdUI.DatePickerRange DatePickerRange;
private Label Lbl_Date;
}
}

View File

@ -54,7 +54,7 @@ namespace XPrint.Production
InitQRCode();
ProductionQueueHandler.CheckProdcutionDirectoryExist(this);
listTimer.Interval = 5000;
listTimer.Interval = 10000;
listTimer.Elapsed += ListTimer_Elapsed;
listTimer.Start();
@ -128,6 +128,10 @@ namespace XPrint.Production
MessageBox.Show("配置文件错误,请重新配置存储路径等相关参数");
}
}
else
{
ProductionQueueHandler.LocalConfigure.ProductionHeight = (float)Input_ProductionHeight.Value;
}
}
private void InitQRCode()
@ -156,7 +160,7 @@ namespace XPrint.Production
return;
}
if (isLoadingListData)
if (isLoadingListData && !isForceRefresh)
{
Page_Order.Current = beforePage;
return;
@ -164,7 +168,10 @@ namespace XPrint.Production
isLoadingListData = true;
await Task.Delay(500);
//if (!isForceRefresh)
//{
// await Task.Delay(500);
//}
beforePage = page;
PageResult<OrderInfo>? infoResult = null;
@ -174,12 +181,14 @@ namespace XPrint.Production
{
Page = page,
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)
{
AntdUI.Message.error(this, "ɾ³ý×ÊԴʧ°Ü:" + ex.Message);
AntdUI.Message.error(this, "GetOrderInfoList Error:" + ex.Message);
}
if (infoResult == null && !isForceRefresh)
@ -206,18 +215,15 @@ namespace XPrint.Production
{
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);
ProductionQueueHandler.Enqueue(ProductionQueueHandler.ProductionQueue, orderItem);
}
Page_Order.Total = (int)infoResult?.Total!;
ReloadOrderItems(infoResult.PageSize, page, isForceRefresh);
var info = infoResult.Data[i];
var orderItem = new OrderItem(info);
ProductionQueueHandler.Enqueue(ProductionQueueHandler.ProductionQueue, orderItem);
}
Page_Order.Total = (int)infoResult?.Total!;
ReloadOrderItems(infoResult.PageSize, page, isForceRefresh);
});
}
catch (Exception ex)
@ -329,7 +335,7 @@ namespace XPrint.Production
}
await SyncImage(page);
}
catch(Exception ex)
catch (Exception ex)
{
Console.WriteLine("Disposed,Error:", ex.Message);
}
@ -360,13 +366,16 @@ namespace XPrint.Production
{
ListPanel_Orders.Controls.Remove(removeItems[i]);
}
items.Reverse();
for (int i = 0; i < items.Count; i++)
{
if (ListPanel_Orders.Controls.Find(items[i].Name, true).Length == 0)
{
var item = items[i];
ListPanel_Orders.Controls.Add(item);
ListPanel_Orders.Controls.SetChildIndex(item, 0);
//ListPanel_Orders.Controls.SetChildIndex(item, 0);
}
}
//ListPanel_Orders.ScrollBar!.ValueY = beforeScrollTop;
@ -405,7 +414,13 @@ namespace XPrint.Production
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);
}
}
}

View File

@ -229,16 +229,17 @@ namespace XPrint.Production
try
{
imageCreation.Create(infos, LocalConfigure.Dpi, productionImageFile, LocalConfigure.ProductionHeight, printIndex);//暂时只支持tif
//await RemoveItemOssFiles(syncImageInfo);//移除OSS上的文件
await form.Invoke(async () =>
form.Invoke(() =>
{
for (int i = 0; i < removeItems.Count; i++)
{
RemoveOrderItem(removeItems[i]);//从分页数据中移除
removeItems[i].ResetQueueStatus();
//RemoveOrderItem(removeItems[i]);//从分页数据中移除
}
AntdUI.Message.success(form, $"生产图 {productionImageFile} 生成成功!");
await ReloadDataEvent?.Invoke()!;
//await ReloadDataEvent?.Invoke()!;
});
infos.Clear();//清空这组的生产队列
removeItems.Clear();