Explorar o código

優化ImageQuang,解決圖片位深,改採字節判斷,官方有問題

JAELYS %!s(int64=4) %!d(string=hai) anos
pai
achega
9dadd95477

+ 33 - 9
TEAMModelOS.SDK/Extension/Utils.cs

@@ -100,32 +100,56 @@ namespace TEAMModelOS.SDK.Extension
         /// </summary>
         /// <param name="stream">傳入Stream,注意請自行釋放</param>
         /// <returns></returns>
-        public static (bool, string) ImageValidateByStream(Stream stream)
+        public static (bool, string, int) ImageValidateByStream(Stream stream)
         {
-            int length = 20;
+            int length = 1024;
+            byte[] bytes = new byte[1024];
             BinaryReader br = new BinaryReader(stream);
             StringBuilder stringBuilder = new StringBuilder();
-            while (length > 0)
+            for (int i = 0; i < length; i++)
             {
                 byte tempByte = br.ReadByte();
+                bytes[i] = tempByte;
                 stringBuilder.Append(Convert.ToString(tempByte, 16));
                 stringBuilder.Append(",");
-                length--;
             }
             stream.Position = 0; //指針回歸為0
+
             string fileheader = stringBuilder.ToString().ToUpper();
             if (string.IsNullOrWhiteSpace(fileheader))
-                return (false, "");
+                return (false, "", 0);
 
             if (fileheader.StartsWith("FF,D8,") || fileheader.StartsWith("42,4D,"))
-                return (true, "jpg");
+            {
+                int i = 2;
+                int depth;
+                while (true)
+                {
+                    int marker = (bytes[i] & 0xff) << 8 | (bytes[i + 1] & 0xff);
+                    int size = (bytes[i + 2] & 0xff) << 8 | (bytes[i + 3] & 0xff);
+                    if (marker >= 0xffc0 && marker <= 0xffcf && marker != 0xffc4 && marker != 0xffc8)
+                    {
+                        depth = (bytes[i + 4] & 0xff) * (bytes[i + 9] & 0xff);
+                        break;
+                    }
+                    else i += size + 2;
+                }
+                return (true, "jpg", depth);
+            }
             if (fileheader.StartsWith("89,50,4E,47,D,A,1A,A,"))
-                return (true, "png");
+            {
+                int depth = bytes[24] & 0xff;
+                if ((bytes[25] & 0xff) == 2) depth *= 3;
+                else if ((bytes[25] & 0xff) == 6) depth *= 4;
+
+                return (true, "png", depth);
+            }
+
             if (fileheader.StartsWith("47,49,46,38,39,61,") || fileheader.StartsWith("47,49,46,38,37,61,"))
-                return (true, "gif");
+                return (true, "gif", 0);
             //if (fileheader.StartsWith("4D,4D") || fileheader.StartsWith("49,49") || fileheader.StartsWith("46,4F,52,4D"))
             //    return (true, "tif");
-            return (false, "");
+            return (false, "", 0);
 
         }
         #endregion

+ 0 - 9
TEAMModelOS.SDK/PngQuant/IWuQuantizer.cs

@@ -1,9 +0,0 @@
-using System.Drawing;
-
-namespace TEAMModelOS.SDK.PngQuant
-{
-    public interface IWuQuantizer
-    {
-        Image QuantizeImage(Bitmap image, int alphaThreshold, int alphaFader);
-    }
-}

+ 1 - 1
TEAMModelOS.SDK/PngQuant/PngQuantizer.cs

@@ -4,7 +4,7 @@ using System.Drawing;
 
 namespace TEAMModelOS.SDK.PngQuant
 {
-    public class PngQuantizer : PngQuantizerBase, IWuQuantizer
+    public class PngQuantizer : PngQuantizerBase
     {
         protected override QuantizedPalette GetQuantizedPalette(int colorCount, ColorData data, IEnumerable<Box> cubes, int alphaThreshold)
         {

+ 59 - 44
TEAMModelOS.SDK/PngQuant/PngQuantizerBase.cs

@@ -25,31 +25,46 @@ namespace TEAMModelOS.SDK.PngQuant
         /// <param name="Width"></param>
         /// <param name="height"></param>
         /// <returns></returns>
-        public Image QuantizeImageFromBase64(Stream base64, int Width = 0, int height = 0)
-        {            
-            var oldbmp = new Bitmap(base64);
-            
-            if (Width > 0 && height > 0)
+        public Image QuantizeImage(Stream base64, int width = 0, int height = 0, int alphaThreshold = 10, int alphaFader = 70)
+        {
+            Bitmap oldbmp = new Bitmap(base64);
+            Bitmap newbmp = null;
+            try
             {
-                var newbmp = new Bitmap(oldbmp, new Size(Width, height)); //縮圖時自動保存為Format32bppArgb無須另外處理
-                oldbmp.Dispose();
-                return QuantizeImage(newbmp, 10, 70);
+                int neww = width;
+                int newh = height;
+                //先處理縮圖計算,指定宽,高按比例
+                if (width == 0 || height == 0)
+                {
+                    neww = oldbmp.Width > 1024 ? 1024 : oldbmp.Width;
+                    newh = Convert.ToInt32(oldbmp.Height * neww / oldbmp.Width);
+                }              
+
+                //縮圖時自動保存為Format32bppArgb無須另外處理
+                newbmp = new Bitmap(oldbmp, new Size(neww, newh));
+                return QuantizeImage(newbmp, alphaThreshold, alphaFader);
+
+                //TODO 判斷圖片是否非32位元和8位元,處理轉換,這個不准,已改為取字節判斷                
+                //var d = Image.GetPixelFormatSize(oldbmp.PixelFormat); 
+
+                //TODO 此段碼為轉換32位元,但上面縮圖保存時以自帶,後面研究能否解決8位元保存問題
+                //    newbmp = new Bitmap(neww, newh, PixelFormat.Format8bppIndexed);
+                //    using (var gr = Graphics.FromImage(newbmp))
+                //        gr.DrawImage(oldbmp, new Rectangle(0, 0, oldbmp.Width, oldbmp.Height));
+                //    return QuantizeImage(newbmp, alphaThreshold, alphaFader);
             }
-            else 
+            catch 
             {
-                if (oldbmp.PixelFormat != PixelFormat.Format32bppArgb) //不縮圖時,要處理保存為Format32bppArgb
-                {
-                    var newbmp = new Bitmap(oldbmp.Width, oldbmp.Height, PixelFormat.Format32bppArgb);
-                    using (var gr = Graphics.FromImage(newbmp))
-                        gr.DrawImage(oldbmp, new Rectangle(0, 0, oldbmp.Width, oldbmp.Height));
-                    oldbmp.Dispose();
-                    oldbmp = newbmp;
-                }
-                return QuantizeImage(oldbmp, 10, 70);
+                return null;
             }
-        }        
+            finally
+            {
+                oldbmp.Dispose();
+                newbmp.Dispose();
+            }
+        }
 
-        public Image QuantizeImage(Bitmap image, int alphaThreshold = 10, int alphaFader = 70)
+        private Image QuantizeImage(Bitmap image, int alphaThreshold, int alphaFader)
         {
             var colorCount = MaxColor;
             var data = BuildHistogram(image, alphaThreshold, alphaFader);
@@ -57,7 +72,7 @@ namespace TEAMModelOS.SDK.PngQuant
             var cubes = SplitData(ref colorCount, data);
             var palette = GetQuantizedPalette(colorCount, data, cubes, alphaThreshold);
             return ProcessImagePixels(image, palette);
-        }        
+        }
 
         private static Bitmap ProcessImagePixels(Image sourceImage, QuantizedPalette palette)
         {
@@ -102,7 +117,7 @@ namespace TEAMModelOS.SDK.PngQuant
             }
             finally
             {
-                if(targetData != null)
+                if (targetData != null)
                     result.UnlockBits(targetData);
             }
 
@@ -116,7 +131,7 @@ namespace TEAMModelOS.SDK.PngQuant
 
             BitmapData data = sourceImage.LockBits(
                 Rectangle.FromLTRB(0, 0, bitmapWidth, bitmapHeight),
-                ImageLockMode.ReadOnly, 
+                ImageLockMode.ReadOnly,
                 sourceImage.PixelFormat);
             ColorData colorData = new ColorData(MaxSideIndex, bitmapWidth, bitmapHeight);
 
@@ -132,7 +147,7 @@ namespace TEAMModelOS.SDK.PngQuant
                 var value = new Byte[byteCount];
 
                 Marshal.Copy(data.Scan0, buffer, 0, buffer.Length);
-               
+
                 for (int y = 0; y < bitmapHeight; y++)
                 {
                     var index = 0;
@@ -162,15 +177,15 @@ namespace TEAMModelOS.SDK.PngQuant
                             colorData.MomentsGreen[indexAlpha, indexRed, indexGreen, indexBlue] += value[Green];
                             colorData.MomentsBlue[indexAlpha, indexRed, indexGreen, indexBlue] += value[Blue];
                             colorData.MomentsAlpha[indexAlpha, indexRed, indexGreen, indexBlue] += value[Alpha];
-                            colorData.Moments[indexAlpha, indexRed, indexGreen, indexBlue] += (value[Alpha]*value[Alpha]) +
-                                                                                              (value[Red]*value[Red]) +
-                                                                                              (value[Green]*value[Green]) +
-                                                                                              (value[Blue]*value[Blue]);
+                            colorData.Moments[indexAlpha, indexRed, indexGreen, indexBlue] += (value[Alpha] * value[Alpha]) +
+                                                                                              (value[Red] * value[Red]) +
+                                                                                              (value[Green] * value[Green]) +
+                                                                                              (value[Blue] * value[Blue]);
                         }
 
                         colorData.AddPixel(
                             new Pixel(value[Alpha], value[Red], value[Green], value[Blue]),
-                            BitConverter.ToInt32 (new[] { indexAlpha, indexRed, indexGreen, indexBlue }, 0));
+                            BitConverter.ToInt32(new[] { indexAlpha, indexRed, indexGreen, indexBlue }, 0));
                         index += bitDepth;
                     }
 
@@ -390,7 +405,7 @@ namespace TEAMModelOS.SDK.PngQuant
             return new CubeCut(cutPoint, result);
         }
 
-        private bool Cut(ColorData data, ref Box first,ref Box second)
+        private bool Cut(ColorData data, ref Box first, ref Box second)
         {
             int direction;
             var wholeAlpha = Volume(first, data.MomentsAlpha);
@@ -399,10 +414,10 @@ namespace TEAMModelOS.SDK.PngQuant
             var wholeBlue = Volume(first, data.MomentsBlue);
             var wholeWeight = Volume(first, data.Weights);
 
-            var maxAlpha = Maximize(data, first, Alpha, (byte) (first.AlphaMinimum + 1), first.AlphaMaximum, wholeAlpha, wholeRed, wholeGreen, wholeBlue, wholeWeight);
-            var maxRed = Maximize(data, first, Red, (byte) (first.RedMinimum + 1), first.RedMaximum, wholeAlpha, wholeRed, wholeGreen, wholeBlue, wholeWeight);
-            var maxGreen = Maximize(data, first, Green, (byte) (first.GreenMinimum + 1), first.GreenMaximum, wholeAlpha, wholeRed, wholeGreen, wholeBlue, wholeWeight);
-            var maxBlue = Maximize(data, first, Blue, (byte) (first.BlueMinimum + 1), first.BlueMaximum, wholeAlpha, wholeRed, wholeGreen, wholeBlue, wholeWeight);
+            var maxAlpha = Maximize(data, first, Alpha, (byte)(first.AlphaMinimum + 1), first.AlphaMaximum, wholeAlpha, wholeRed, wholeGreen, wholeBlue, wholeWeight);
+            var maxRed = Maximize(data, first, Red, (byte)(first.RedMinimum + 1), first.RedMaximum, wholeAlpha, wholeRed, wholeGreen, wholeBlue, wholeWeight);
+            var maxGreen = Maximize(data, first, Green, (byte)(first.GreenMinimum + 1), first.GreenMaximum, wholeAlpha, wholeRed, wholeGreen, wholeBlue, wholeWeight);
+            var maxBlue = Maximize(data, first, Blue, (byte)(first.BlueMinimum + 1), first.BlueMaximum, wholeAlpha, wholeRed, wholeGreen, wholeBlue, wholeWeight);
 
             if ((maxAlpha.Value >= maxRed.Value) && (maxAlpha.Value >= maxGreen.Value) && (maxAlpha.Value >= maxBlue.Value))
             {
@@ -427,28 +442,28 @@ namespace TEAMModelOS.SDK.PngQuant
             switch (direction)
             {
                 case Alpha:
-                    second.AlphaMinimum = first.AlphaMaximum = (byte) maxAlpha.Position;
+                    second.AlphaMinimum = first.AlphaMaximum = (byte)maxAlpha.Position;
                     second.RedMinimum = first.RedMinimum;
                     second.GreenMinimum = first.GreenMinimum;
                     second.BlueMinimum = first.BlueMinimum;
                     break;
 
                 case Red:
-                    second.RedMinimum = first.RedMaximum = (byte) maxRed.Position;
+                    second.RedMinimum = first.RedMaximum = (byte)maxRed.Position;
                     second.AlphaMinimum = first.AlphaMinimum;
                     second.GreenMinimum = first.GreenMinimum;
                     second.BlueMinimum = first.BlueMinimum;
                     break;
 
                 case Green:
-                    second.GreenMinimum = first.GreenMaximum = (byte) maxGreen.Position;
+                    second.GreenMinimum = first.GreenMaximum = (byte)maxGreen.Position;
                     second.AlphaMinimum = first.AlphaMinimum;
                     second.RedMinimum = first.RedMinimum;
                     second.BlueMinimum = first.BlueMinimum;
                     break;
 
                 case Blue:
-                    second.BlueMinimum = first.BlueMaximum = (byte) maxBlue.Position;
+                    second.BlueMinimum = first.BlueMaximum = (byte)maxBlue.Position;
                     second.AlphaMinimum = first.AlphaMinimum;
                     second.RedMinimum = first.RedMinimum;
                     second.GreenMinimum = first.GreenMinimum;
@@ -584,12 +599,12 @@ namespace TEAMModelOS.SDK.PngQuant
                 if (weight <= 0) continue;
 
                 var lookup = new Lookup
-                    {
-                        Alpha = (int) (Volume(cube, data.MomentsAlpha)/weight),
-                        Red = (int) (Volume(cube, data.MomentsRed)/weight),
-                        Green = (int) (Volume(cube, data.MomentsGreen)/weight),
-                        Blue = (int) (Volume(cube, data.MomentsBlue)/weight)
-                    };
+                {
+                    Alpha = (int)(Volume(cube, data.MomentsAlpha) / weight),
+                    Red = (int)(Volume(cube, data.MomentsRed) / weight),
+                    Green = (int)(Volume(cube, data.MomentsGreen) / weight),
+                    Blue = (int)(Volume(cube, data.MomentsBlue) / weight)
+                };
                 lookups.Lookups.Add(lookup);
             }
             return lookups;

+ 37 - 23
TEAMModelOS/Controllers/Core/CoreController.cs

@@ -33,6 +33,7 @@ namespace TEAMModelOS.Controllers.Core
         /// <param name="request"></param>
         /// <returns></returns>
         [HttpPost("image-quant")]
+        [RequestSizeLimit(100_000_000)] //最大100m左右
         public async Task<IActionResult> ImageQuang(List<ImageQuangRequest> request)
         {
             if (request.Count < 1) return BadRequest();
@@ -42,36 +43,49 @@ namespace TEAMModelOS.Controllers.Core
             var quantizer = new PngQuantizer();
             foreach (var item in request)
             {
-                string trim = Regex.Replace(item.base64, @"\s", ""); //移除空白
+                //正則處理,移除空白
+                string trim = Regex.Replace(item.base64, @"\s", ""); 
                 Match match = rex.Match(trim);
-                string oldtype = match.Groups["key1"].Value;
-                string oldbase64 = match.Groups["key2"].Value;
-                byte[] oldbase64data = Convert.FromBase64String(oldbase64);
-                using var oldbase64ms = new MemoryStream(oldbase64data);
-                var (isimg, type) = Utils.ImageValidateByStream(oldbase64ms); //驗證圖片格式
-                //oldbase64ms.Position = 0;
-                if (isimg)
+                string otype = match.Groups["key1"].Value;
+                string obase64 = match.Groups["key2"].Value;
+                byte[] obase64data = Convert.FromBase64String(obase64);
+                using var obase64ms = new MemoryStream(obase64data);
+                //驗證圖片格式及位深,判斷是否要處理量化演算
+                var (isimg, type, dupe) = Utils.ImageValidateByStream(obase64ms);                
+                if (isimg && (type.Equals("png") || type.Equals("jpg")) && dupe > 8)
                 {
-                    string imgname = string.Empty;
-                    if (!string.IsNullOrWhiteSpace(item.blob)) //檢查是否需要保存原圖到blob
+                    string img = string.Empty;                    
+                    using var quantized = quantizer.QuantizeImage(obase64ms, item.width, item.height); //JPEG,PNG輕量化
+                    if (quantized != null)
                     {
-                        imgname = $"{Guid.NewGuid():N}.{type}"; //重新命名原始圖片名稱
-                        string Containername = item.blob.Substring(0, item.blob.IndexOf("/"));
-
-                        string filepath = $"{item.blob[(item.blob.Trim('/').IndexOf("/") + 1)..]}/{imgname}"; //處理路徑,避免多餘的字符
-                        await _azureStorage.GetBlobContainerClient(Containername).GetBlobClient(filepath).UploadAsync(oldbase64ms, new BlobHttpHeaders { ContentType = oldtype });
+                        //檢查是否需要保存原圖到blob
+                        if (!string.IsNullOrWhiteSpace(item.blob))
+                        {
+                            img = $"{Guid.NewGuid():N}.{type}"; //命名新圖片名稱
+                            string Container = item.blob.Substring(0, item.blob.IndexOf("/")); //取得容器名稱
+                            string blobpath = $"{item.blob[(item.blob.Trim('/').IndexOf("/") + 1)..]}/{img}"; //處理路徑,避免多餘的字符
+                            await _azureStorage.GetBlobContainerClient(Container).GetBlobClient(blobpath).UploadAsync(obase64ms, new BlobHttpHeaders { ContentType = otype });
+                        }
+                        using var nbase64ms = new MemoryStream(); 
+                        quantized.Save(nbase64ms, ImageFormat.Png); //保存為PNG格式
+                        byte[] data = nbase64ms.ToArray();
+                        string base64 = $"data:image/png;base64,{Convert.ToBase64String(data)}"; //創建新的img src base64
+                        respons.Add(new { base64 = base64, blob = img });
+                        
+                        // TODO 下面註解,無法解決保存時,真正改變Format32bppArgb為Format8bppIndexed,需要再研究
+                        //ImageCodecInfo imageCodecInfo = ImageCodecInfo.GetImageEncoders().FirstOrDefault(info => info.MimeType == "image/png");
+                        //var parameters = new EncoderParameters(1);
+                        //parameters.Param[0] = new EncoderParameter(Encoder.ColorDepth, 8);
+                        //quantized.Save(nbase64ms, imageCodecInfo, parameters); //保存為PNG格式
+                    }
+                    else
+                    {
+                        respons.Add(new { base64 = obase64, blob = "" }); //異常的圖片,原Base64返回
                     }
-                    using var quantized = quantizer.QuantizeImageFromBase64(oldbase64ms, item.width, item.height); //PNG輕量化
-                    using var newbase64ms = new MemoryStream();
-                    quantized.Save(newbase64ms, ImageFormat.Png); //保存為PNG格式
-                    byte[] data = newbase64ms.ToArray();
-                    string base64 = Convert.ToBase64String(data);
-                    string newbase64 = $"data:image/png;base64,{base64}"; //創建新的img src base64
-                    respons.Add(new { base64 = newbase64, blob = imgname });
                 }
                 else
                 {
-                    respons.Add(new { base64 = "", blob = "" });
+                    respons.Add(new { base64 = obase64, blob = "" }); //不符合的圖片,原Base64返回
                 }
             }
             return Ok(respons);