From db9a40846d91f02fe4d395402773ee9f3c303c9a Mon Sep 17 00:00:00 2001 From: wuyanchen <307378529@qq.com> Date: Mon, 22 Dec 2025 16:12:13 +0800 Subject: [PATCH] =?UTF-8?q?=C2=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Entities/PageOrderStatusData.cs | 6 + .../ImageLogic/Conf/LocalConfigure.cs | 2 +- .../Enitites/ProductionCanvasInfo.cs | 13 + .../ImageLogic/ProductionImageCreation.cs | 98 ++- .../ImageLogic/RegionImageComposer.cs | 564 +++++++++++++++++- .../XPrint.Production.Business.csproj | 1 + XPrint.Production/Controls/OrderItem.cs | 29 +- XPrint.Production/MainForm.Designer.cs | 30 +- XPrint.Production/MainForm.cs | 51 +- XPrint.Production/ProductionQueueHandler.cs | 9 +- 10 files changed, 748 insertions(+), 55 deletions(-) diff --git a/XPrint.Production.Business/Entities/PageOrderStatusData.cs b/XPrint.Production.Business/Entities/PageOrderStatusData.cs index dddf3e5..cb1cb71 100644 --- a/XPrint.Production.Business/Entities/PageOrderStatusData.cs +++ b/XPrint.Production.Business/Entities/PageOrderStatusData.cs @@ -15,5 +15,11 @@ namespace XPrint.Production.Business.Entities /// 订单状态 /// public OrderStatus Status { get; set; } = OrderStatus.None; + + [JsonPropertyName("startTime")] + public DateTime? StartTime { get; set; } = null; + + [JsonPropertyName("endTime")] + public DateTime? EndTime { get; set; } = null; } } diff --git a/XPrint.Production.Business/ImageLogic/Conf/LocalConfigure.cs b/XPrint.Production.Business/ImageLogic/Conf/LocalConfigure.cs index 28ebeea..e1fa111 100644 --- a/XPrint.Production.Business/ImageLogic/Conf/LocalConfigure.cs +++ b/XPrint.Production.Business/ImageLogic/Conf/LocalConfigure.cs @@ -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() { } } diff --git a/XPrint.Production.Business/ImageLogic/Enitites/ProductionCanvasInfo.cs b/XPrint.Production.Business/ImageLogic/Enitites/ProductionCanvasInfo.cs index daecb82..62d95f2 100644 --- a/XPrint.Production.Business/ImageLogic/Enitites/ProductionCanvasInfo.cs +++ b/XPrint.Production.Business/ImageLogic/Enitites/ProductionCanvasInfo.cs @@ -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; } diff --git a/XPrint.Production.Business/ImageLogic/ProductionImageCreation.cs b/XPrint.Production.Business/ImageLogic/ProductionImageCreation.cs index a065cf9..af63444 100644 --- a/XPrint.Production.Business/ImageLogic/ProductionImageCreation.cs +++ b/XPrint.Production.Business/ImageLogic/ProductionImageCreation.cs @@ -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();//修复手机拍的图片的方向问题 + // 方法1:alpha通道阈值处理(消除半透明) + //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); //} }); diff --git a/XPrint.Production.Business/ImageLogic/RegionImageComposer.cs b/XPrint.Production.Business/ImageLogic/RegionImageComposer.cs index e4a02f7..3de5e11 100644 --- a/XPrint.Production.Business/ImageLogic/RegionImageComposer.cs +++ b/XPrint.Production.Business/ImageLogic/RegionImageComposer.cs @@ -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 /// 目标区域高度(可缩放) 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; + } + + /// 仅在掩码区域应用复合操作(优化版:批量像素+指针操作) + 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!); + } + + /// 生成边缘掩码:标记不透明→透明的过渡区域(修正版) + 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; + } + + /// 旧版本兼容:用IMorphologySettings实现腐蚀(Morphology方法需传设置对象) + 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; + } + + /// 配套:用IMorphologySettings实现膨胀 + 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; + } + + + /// 手动实现图像减法(a = a - b,确保边缘掩码正确) + 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!); + } + + /// 将Alpha通道转为8位灰度(解决16位类型问题) + 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 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 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 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 pixels) + //{ + // try + // { + // pixels.GetType().GetMethod("Sync", BindingFlags.NonPublic | BindingFlags.Instance)?.Invoke(pixels, null); + // } + // catch { } + //} + + //// 降级方案(原优化版嵌套循环) + //private static void FallbackReadPixels(IPixelCollection inputPixels, ushort[] inputBuffer, int width, int height) + //{ + // for (int y = 0; y < height; y++) + // { + // int rowStartIndex = y * width; + // IPixel? pixel = null; + // for (int x = 0; x < width; x++) + // { + // pixel = inputPixels.GetPixel(x, y); + // inputBuffer[rowStartIndex + x] = pixel[0]; + // } + // } + //} + + //private static void FallbackWritePixels(IPixelCollection outputPixels, ushort[] outputBuffer, int width, int height) + //{ + // for (int y = 0; y < height; y++) + // { + // int rowStartIndex = y * width; + // IPixel? 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); + + ///// 高性能邻域处理(带数据验证) + //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. 明确指定泛型,避免类型不匹配 + // using var inputPixels = input.GetPixelsUnsafe(); + // using var outputPixels = output.GetPixelsUnsafe(); + // //// 预分配输入/输出缓冲区 + // //ushort[] inputBuffer = new ushort[pixelCount]; + // ushort[] outputBuffer = new ushort[pixelCount]; + + // //// 1. 关键:用 GetPixelsUnsafe() 获取非托管像素集合(指定泛型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; + //} + ///// ///// 基于中心点旋转图像角度,保持原始尺寸 ///// diff --git a/XPrint.Production.Business/XPrint.Production.Business.csproj b/XPrint.Production.Business/XPrint.Production.Business.csproj index 2e22664..a9d448e 100644 --- a/XPrint.Production.Business/XPrint.Production.Business.csproj +++ b/XPrint.Production.Business/XPrint.Production.Business.csproj @@ -4,6 +4,7 @@ net8.0 enable enable + True diff --git a/XPrint.Production/Controls/OrderItem.cs b/XPrint.Production/Controls/OrderItem.cs index 0bedc14..0a60a2b 100644 --- a/XPrint.Production/Controls/OrderItem.cs +++ b/XPrint.Production/Controls/OrderItem.cs @@ -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) diff --git a/XPrint.Production/MainForm.Designer.cs b/XPrint.Production/MainForm.Designer.cs index 9731ad5..4a58e97 100644 --- a/XPrint.Production/MainForm.Designer.cs +++ b/XPrint.Production/MainForm.Designer.cs @@ -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; } } diff --git a/XPrint.Production/MainForm.cs b/XPrint.Production/MainForm.cs index 8a376de..a4fb203 100644 --- a/XPrint.Production/MainForm.cs +++ b/XPrint.Production/MainForm.cs @@ -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? 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); } } } diff --git a/XPrint.Production/ProductionQueueHandler.cs b/XPrint.Production/ProductionQueueHandler.cs index 46f30a1..b50f998 100644 --- a/XPrint.Production/ProductionQueueHandler.cs +++ b/XPrint.Production/ProductionQueueHandler.cs @@ -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();