浏览代码

add png ImageQuang in core controller
add utils function from image format valid

JAELYS 4 年之前
父节点
当前提交
bc1c332cd2

+ 61 - 3
TEAMModelOS.SDK/Extension/Utils.cs

@@ -2,7 +2,10 @@ using Microsoft.AspNetCore.Cryptography.KeyDerivation;
 using Microsoft.IdentityModel.Tokens;
 using System;
 using System.Collections.Generic;
+using System.Drawing;
+using System.Drawing.Imaging;
 using System.IdentityModel.Tokens.Jwt;
+using System.IO;
 using System.Security.Claims;
 using System.Security.Cryptography;
 using System.Text;
@@ -73,7 +76,7 @@ namespace TEAMModelOS.SDK.Extension
         {
             int value = CreatSaltInt(max - min) + min;
             return value;
-        }        
+        }
 
         /// <summary>
         /// 剖析連接字串
@@ -83,14 +86,67 @@ namespace TEAMModelOS.SDK.Extension
         public static Dictionary<string, string> ParseConnectionString(string connectionString)
         {
             var d = new Dictionary<string, string>();
-            foreach (var item in connectionString.Split(';',StringSplitOptions.RemoveEmptyEntries))
+            foreach (var item in connectionString.Split(';', StringSplitOptions.RemoveEmptyEntries))
             {
                 var a = item.IndexOf('=');
                 d.Add(item.Substring(0, a), item.Substring(a + 1));
             }
             return d;
-        }    
+        }
+
+        #region 圖片處理
+        /// <summary>
+        /// 判斷圖片格式
+        /// </summary>
+        /// <param name="fileStream"></param>
+        /// <returns></returns>
+        public static (bool, ImageType) ImageValidateByStream(Stream fileStream)
+        {
+            using (BinaryReader br = new BinaryReader(fileStream))
+            {
+                int length = 20;
+                StringBuilder stringBuilder = new StringBuilder();
+                while (length > 0)
+                {
+                    byte tempByte = br.ReadByte();
+                    stringBuilder.Append(Convert.ToString(tempByte, 16));
+                    stringBuilder.Append(",");
+                    length--;
+                }
+                string fileheader = stringBuilder.ToString().ToUpper();
+                if (string.IsNullOrWhiteSpace(fileheader))
+                    return (false, ImageType.Error);
+
+                if (fileheader.StartsWith("FF,D8,") || fileheader.StartsWith("42,4D,"))
+                    return (true, ImageType.JPEG);
+                if (fileheader.StartsWith("89,50,4E,47,D,A,1A,A,"))
+                    return (true, ImageType.PNG);
+                if (fileheader.StartsWith("47,49,46,38,39,61,") || fileheader.StartsWith("47,49,46,38,37,61,"))
+                    return (true, ImageType.GIF);
+                if (fileheader.StartsWith("4D,4D") || fileheader.StartsWith("49,49") || fileheader.StartsWith("46,4F,52,4D"))
+                    return (true, ImageType.TIFF);
+                return (false, ImageType.Empty);
+            }
+        }
+
+        public enum ImageType
+        {
+            Error,
+            Empty,
+            JPEG,
+            BMP,
+            PNG,
+            GIF,
+            TIFF,
+            IFF
+        }
+        
+        #endregion
 
+
+
+
+        #region private
         private static int SetRandomSeeds(int length)
         {
             decimal maxValue = (decimal)long.MaxValue;
@@ -99,6 +155,8 @@ namespace TEAMModelOS.SDK.Extension
             return (int)(Math.Abs(BitConverter.ToInt64(array, 0)) / maxValue * length);
         }
 
+        #endregion
+
 
     }
 }

+ 15 - 0
TEAMModelOS.SDK/PngQuant/Box.cs

@@ -0,0 +1,15 @@
+namespace TEAMModelOS.SDK.PngQuant
+{
+    public struct Box
+    {
+        public byte AlphaMinimum;
+        public byte AlphaMaximum;
+        public byte RedMinimum;
+        public byte RedMaximum;
+        public byte GreenMinimum;
+        public byte GreenMaximum;
+        public byte BlueMinimum;
+        public byte BlueMaximum;
+        public int Size;
+    }
+}

+ 45 - 0
TEAMModelOS.SDK/PngQuant/ColorData.cs

@@ -0,0 +1,45 @@
+using System;
+using System.Collections.Generic;
+
+namespace TEAMModelOS.SDK.PngQuant
+{
+    public class ColorData
+    {
+        public ColorData(int dataGranularity, int bitmapWidth, int bitmapHeight)
+        {
+            dataGranularity++;
+            Weights = new long[dataGranularity, dataGranularity, dataGranularity, dataGranularity];
+            MomentsAlpha = new long[dataGranularity, dataGranularity, dataGranularity, dataGranularity];
+            MomentsRed = new long[dataGranularity, dataGranularity, dataGranularity, dataGranularity];
+            MomentsGreen = new long[dataGranularity, dataGranularity, dataGranularity, dataGranularity];
+            MomentsBlue = new long[dataGranularity, dataGranularity, dataGranularity, dataGranularity];
+            Moments = new float[dataGranularity, dataGranularity, dataGranularity, dataGranularity];
+
+            pixelsCount = bitmapWidth*bitmapHeight;
+            pixels = new Pixel[pixelsCount];
+            quantizedPixels = new int[pixelsCount];
+        }
+
+        public long[, , ,] Weights { get; private set; }
+        public long[, , ,] MomentsAlpha { get; private set; }
+        public long[, , ,] MomentsRed { get; private set; }
+        public long[, , ,] MomentsGreen { get; private set; }
+        public long[, , ,] MomentsBlue { get; private set; }
+        public float[, , ,] Moments { get; private set; }
+
+        public IList<int> QuantizedPixels { get { return quantizedPixels; } }
+        public IList<Pixel> Pixels { get { return pixels; } }
+
+        public int PixelsCount { get { return pixels.Length; } }
+        public void AddPixel(Pixel pixel, int quantizedPixel)
+        {
+            pixels[pixelFillingCounter] = pixel;
+            quantizedPixels[pixelFillingCounter++] = quantizedPixel;
+        }
+
+        private Pixel[] pixels;
+        private int[] quantizedPixels;
+        private int pixelsCount;
+        private int pixelFillingCounter;
+    }
+}

+ 14 - 0
TEAMModelOS.SDK/PngQuant/CubeCut.cs

@@ -0,0 +1,14 @@
+namespace TEAMModelOS.SDK.PngQuant
+{
+    internal struct CubeCut
+    {
+        public readonly byte? Position;
+        public readonly float Value;
+
+        public CubeCut(byte? cutPoint, float result)
+        {
+            Position = cutPoint;
+            Value = result;
+        }
+    }
+}

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

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

+ 10 - 0
TEAMModelOS.SDK/PngQuant/Lookup.cs

@@ -0,0 +1,10 @@
+namespace TEAMModelOS.SDK.PngQuant
+{
+    public class Lookup
+    {
+        public int Alpha;
+        public int Red;
+        public int Green;
+        public int Blue;
+    }
+}

+ 16 - 0
TEAMModelOS.SDK/PngQuant/LookupData.cs

@@ -0,0 +1,16 @@
+using System.Collections.Generic;
+
+namespace TEAMModelOS.SDK.PngQuant
+{
+    public class LookupData
+    {
+        public LookupData(int granularity)
+        {
+            Lookups = new List<Lookup>();
+            Tags = new int[granularity, granularity, granularity, granularity];
+        }
+
+        public IList<Lookup> Lookups { get; private set; }
+        public int[, , ,] Tags { get; private set; }
+    }
+}

+ 21 - 0
TEAMModelOS.SDK/PngQuant/Pixel.cs

@@ -0,0 +1,21 @@
+namespace TEAMModelOS.SDK.PngQuant
+{
+    public struct Pixel
+    {
+        public Pixel(byte alpha, byte red, byte green, byte blue) : this()
+        {
+            Alpha = alpha;
+            Red = red;
+            Green = green;
+            Blue = blue;
+
+            Argb = alpha << 24 | red << 16 | green << 8 | blue;
+        }
+
+        public byte Alpha;
+        public byte Red;
+        public byte Green;
+        public byte Blue;
+        public int Argb;
+    }
+}

+ 98 - 0
TEAMModelOS.SDK/PngQuant/PngQuantizer.cs

@@ -0,0 +1,98 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+
+namespace TEAMModelOS.SDK.PngQuant
+{
+    public class PngQuantizer : PngQuantizerBase, IWuQuantizer
+    {
+        protected override QuantizedPalette GetQuantizedPalette(int colorCount, ColorData data, IEnumerable<Box> cubes, int alphaThreshold)
+        {
+            int imageSize = data.PixelsCount;
+            LookupData lookups = BuildLookups(cubes, data);
+
+            IList<int> quantizedPixels = data.QuantizedPixels;
+            for (var index = 0; index < imageSize; ++index)
+            {
+                var indexParts = BitConverter.GetBytes(quantizedPixels[index]);
+                quantizedPixels[index] = lookups.Tags[indexParts[Alpha], indexParts[Red], indexParts[Green], indexParts[Blue]];
+            }
+
+            var alphas = new int[colorCount + 1];
+            var reds = new int[colorCount + 1];
+            var greens = new int[colorCount + 1];
+            var blues = new int[colorCount + 1];
+            var sums = new int[colorCount + 1];
+            var palette = new QuantizedPalette(imageSize);
+
+            IList<Pixel> pixels = data.Pixels;
+            int pixelsCount = data.PixelsCount;
+            IList<Lookup> lookupsList = lookups.Lookups;
+            int lookupsCount = lookupsList.Count;
+
+            Dictionary<int, int> cachedMaches = new Dictionary<int, int>();
+
+            for (int pixelIndex = 0; pixelIndex < pixelsCount; pixelIndex++)
+            {
+                Pixel pixel = pixels[pixelIndex];
+                palette.PixelIndex[pixelIndex] = -1;
+                if (pixel.Alpha <= alphaThreshold)
+                    continue;
+
+                int argb = pixel.Argb;
+
+                if (!cachedMaches.TryGetValue(argb, out int bestMatch))
+                {
+                    int match = quantizedPixels[pixelIndex];
+                    bestMatch = match;
+                    int bestDistance = int.MaxValue;
+
+                    for (int lookupIndex = 0; lookupIndex < lookupsCount; lookupIndex++)
+                    {
+                        Lookup lookup = lookupsList[lookupIndex];
+                        var deltaAlpha = pixel.Alpha - lookup.Alpha;
+                        var deltaRed = pixel.Red - lookup.Red;
+                        var deltaGreen = pixel.Green - lookup.Green;
+                        var deltaBlue = pixel.Blue - lookup.Blue;
+
+                        int distance = deltaAlpha*deltaAlpha + deltaRed*deltaRed + deltaGreen*deltaGreen + deltaBlue*deltaBlue;
+
+                        if (distance >= bestDistance)
+                            continue;
+
+                        bestDistance = distance;
+                        bestMatch = lookupIndex;
+                    }
+
+                    cachedMaches[argb] = bestMatch;
+                }
+
+                alphas[bestMatch] += pixel.Alpha;
+                reds[bestMatch] += pixel.Red;
+                greens[bestMatch] += pixel.Green;
+                blues[bestMatch] += pixel.Blue;
+                sums[bestMatch]++;
+
+                palette.PixelIndex[pixelIndex] = bestMatch;
+            }
+
+            for (var paletteIndex = 0; paletteIndex < colorCount; paletteIndex++)
+            {
+                if (sums[paletteIndex] > 0)
+                {
+                    alphas[paletteIndex] /= sums[paletteIndex];
+                    reds[paletteIndex] /= sums[paletteIndex];
+                    greens[paletteIndex] /= sums[paletteIndex];
+                    blues[paletteIndex] /= sums[paletteIndex];
+                }
+
+                var color = Color.FromArgb(alphas[paletteIndex], reds[paletteIndex], greens[paletteIndex], blues[paletteIndex]);
+                palette.Colors.Add(color);
+            }
+
+            palette.Colors.Add(Color.FromArgb(0, 0, 0, 0));
+
+            return palette;
+        }
+    }
+}

+ 602 - 0
TEAMModelOS.SDK/PngQuant/PngQuantizerBase.cs

@@ -0,0 +1,602 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+
+namespace TEAMModelOS.SDK.PngQuant
+{
+    public abstract class PngQuantizerBase
+    {
+        private const int MaxColor = 256;
+        protected const int Alpha = 3;
+        protected const int Red = 2;
+        protected const int Green = 1;
+        protected const int Blue = 0;
+        private const int SideSize = 33;
+        private const int MaxSideIndex = 32;
+
+        /// <summary>
+        /// 處理Base64圖片,支持不同格式圖片統一轉換為8位元PNG
+        /// </summary>
+        /// <param name="base64"></param>
+        /// <param name="Width"></param>
+        /// <param name="height"></param>
+        /// <returns></returns>
+        public Image QuantizeImageFromBase64(string base64, int Width = 0, int height = 0)
+        {
+            byte[] arr = Convert.FromBase64String(base64);
+            MemoryStream ms = new MemoryStream(arr);
+            var oldbmp = new Bitmap(ms);
+            
+            if (Width > 0 && height > 0)
+            {
+                var newbmp = new Bitmap(oldbmp, new Size(Width, height)); //縮圖時自動保存為Format32bppArgb無須另外處理
+                oldbmp.Dispose();
+                return QuantizeImage(newbmp, 10, 70);
+            }
+            else 
+            {
+                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);
+            }
+        }        
+
+        public Image QuantizeImage(Bitmap image, int alphaThreshold = 10, int alphaFader = 70)
+        {
+            var colorCount = MaxColor;
+            var data = BuildHistogram(image, alphaThreshold, alphaFader);
+            data = CalculateMoments(data);
+            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)
+        {
+            var result = new Bitmap(sourceImage.Width, sourceImage.Height, PixelFormat.Format8bppIndexed);
+            var newPalette = result.Palette;
+            for (var index = 0; index < palette.Colors.Count; index++)
+                newPalette.Entries[index] = palette.Colors[index];
+            result.Palette = newPalette;
+
+            BitmapData targetData = null;
+            try
+            {
+                targetData = result.LockBits(Rectangle.FromLTRB(0, 0, result.Width, result.Height), ImageLockMode.WriteOnly, result.PixelFormat);
+                const byte targetBitDepth = 8;
+                var targetByteLength = targetData.Stride < 0 ? -targetData.Stride : targetData.Stride;
+                var targetByteCount = Math.Max(1, targetBitDepth >> 3);
+                var targetSize = targetByteLength * result.Height;
+                var targetOffset = 0;
+                var targetBuffer = new byte[targetSize];
+                var targetValue = new byte[targetByteCount];
+                var pixelIndex = 0;
+
+                for (var y = 0; y < result.Height; y++)
+                {
+                    var targetIndex = 0;
+                    for (var x = 0; x < result.Width; x++)
+                    {
+                        var targetIndexOffset = targetIndex >> 3;
+                        targetValue[0] = (byte)(palette.PixelIndex[pixelIndex] == -1 ? palette.Colors.Count - 1 : palette.PixelIndex[pixelIndex]);
+                        pixelIndex++;
+
+                        for (var valueIndex = 0; valueIndex < targetByteCount; valueIndex++)
+                            targetBuffer[targetOffset + valueIndex + targetIndexOffset] = targetValue[valueIndex];
+
+                        targetIndex += targetBitDepth;
+                    }
+
+                    targetOffset += targetByteLength;
+                }
+
+                Marshal.Copy(targetBuffer, 0, targetData.Scan0, targetSize);
+            }
+            finally
+            {
+                if(targetData != null)
+                    result.UnlockBits(targetData);
+            }
+
+            return result;
+        }
+
+        private static ColorData BuildHistogram(Bitmap sourceImage, int alphaThreshold, int alphaFader)
+        {
+            int bitmapWidth = sourceImage.Width;
+            int bitmapHeight = sourceImage.Height;
+
+            BitmapData data = sourceImage.LockBits(
+                Rectangle.FromLTRB(0, 0, bitmapWidth, bitmapHeight),
+                ImageLockMode.ReadOnly, 
+                sourceImage.PixelFormat);
+            ColorData colorData = new ColorData(MaxSideIndex, bitmapWidth, bitmapHeight);
+
+            try
+            {
+                var bitDepth = Image.GetPixelFormatSize(sourceImage.PixelFormat);
+                if (bitDepth != 32)
+                    throw new QuantizationException(string.Format("Thie image you are attempting to quantize does not contain a 32 bit ARGB palette. This image has a bit depth of {0} with {1} colors.", bitDepth, sourceImage.Palette.Entries.Length));
+                var byteLength = data.Stride < 0 ? -data.Stride : data.Stride;
+                var byteCount = Math.Max(1, bitDepth >> 3);
+                var offset = 0;
+                var buffer = new Byte[byteLength * sourceImage.Height];
+                var value = new Byte[byteCount];
+
+                Marshal.Copy(data.Scan0, buffer, 0, buffer.Length);
+               
+                for (int y = 0; y < bitmapHeight; y++)
+                {
+                    var index = 0;
+                    for (int x = 0; x < bitmapWidth; x++)
+                    {
+                        var indexOffset = index >> 3;
+
+                        for (var valueIndex = 0; valueIndex < byteCount; valueIndex++)
+                            value[valueIndex] = buffer[offset + valueIndex + indexOffset];
+
+                        var indexAlpha = (byte)((value[Alpha] >> 3) + 1);
+                        var indexRed = (byte)((value[Red] >> 3) + 1);
+                        var indexGreen = (byte)((value[Green] >> 3) + 1);
+                        var indexBlue = (byte)((value[Blue] >> 3) + 1);
+
+                        if (value[Alpha] > alphaThreshold)
+                        {
+                            if (value[Alpha] < 255)
+                            {
+                                var alpha = value[Alpha] + (value[Alpha] % alphaFader);
+                                value[Alpha] = (byte)(alpha > 255 ? 255 : alpha);
+                                indexAlpha = (byte)((value[Alpha] >> 3) + 1);
+                            }
+
+                            colorData.Weights[indexAlpha, indexRed, indexGreen, indexBlue]++;
+                            colorData.MomentsRed[indexAlpha, indexRed, indexGreen, indexBlue] += value[Red];
+                            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.AddPixel(
+                            new Pixel(value[Alpha], value[Red], value[Green], value[Blue]),
+                            BitConverter.ToInt32 (new[] { indexAlpha, indexRed, indexGreen, indexBlue }, 0));
+                        index += bitDepth;
+                    }
+
+                    offset += byteLength;
+                }
+            }
+            finally
+            {
+                sourceImage.UnlockBits(data);
+            }
+            return colorData;
+        }
+
+        private static ColorData CalculateMoments(ColorData data)
+        {
+            for (var alphaIndex = 1; alphaIndex <= MaxSideIndex; ++alphaIndex)
+            {
+                var xarea = new long[SideSize, SideSize, SideSize];
+                var xareaAlpha = new long[SideSize, SideSize, SideSize];
+                var xareaRed = new long[SideSize, SideSize, SideSize];
+                var xareaGreen = new long[SideSize, SideSize, SideSize];
+                var xareaBlue = new long[SideSize, SideSize, SideSize];
+                var xarea2 = new float[SideSize, SideSize, SideSize];
+                for (var redIndex = 1; redIndex <= MaxSideIndex; ++redIndex)
+                {
+                    var area = new long[SideSize];
+                    var areaAlpha = new long[SideSize];
+                    var areaRed = new long[SideSize];
+                    var areaGreen = new long[SideSize];
+                    var areaBlue = new long[SideSize];
+                    var area2 = new float[SideSize];
+                    for (var greenIndex = 1; greenIndex <= MaxSideIndex; ++greenIndex)
+                    {
+                        long line = 0;
+                        long lineAlpha = 0;
+                        long lineRed = 0;
+                        long lineGreen = 0;
+                        long lineBlue = 0;
+                        var line2 = 0.0f;
+                        for (var blueIndex = 1; blueIndex <= MaxSideIndex; ++blueIndex)
+                        {
+                            line += data.Weights[alphaIndex, redIndex, greenIndex, blueIndex];
+                            lineAlpha += data.MomentsAlpha[alphaIndex, redIndex, greenIndex, blueIndex];
+                            lineRed += data.MomentsRed[alphaIndex, redIndex, greenIndex, blueIndex];
+                            lineGreen += data.MomentsGreen[alphaIndex, redIndex, greenIndex, blueIndex];
+                            lineBlue += data.MomentsBlue[alphaIndex, redIndex, greenIndex, blueIndex];
+                            line2 += data.Moments[alphaIndex, redIndex, greenIndex, blueIndex];
+
+                            area[blueIndex] += line;
+                            areaAlpha[blueIndex] += lineAlpha;
+                            areaRed[blueIndex] += lineRed;
+                            areaGreen[blueIndex] += lineGreen;
+                            areaBlue[blueIndex] += lineBlue;
+                            area2[blueIndex] += line2;
+
+                            xarea[redIndex, greenIndex, blueIndex] = xarea[redIndex - 1, greenIndex, blueIndex] + area[blueIndex];
+                            xareaAlpha[redIndex, greenIndex, blueIndex] = xareaAlpha[redIndex - 1, greenIndex, blueIndex] + areaAlpha[blueIndex];
+                            xareaRed[redIndex, greenIndex, blueIndex] = xareaRed[redIndex - 1, greenIndex, blueIndex] + areaRed[blueIndex];
+                            xareaGreen[redIndex, greenIndex, blueIndex] = xareaGreen[redIndex - 1, greenIndex, blueIndex] + areaGreen[blueIndex];
+                            xareaBlue[redIndex, greenIndex, blueIndex] = xareaBlue[redIndex - 1, greenIndex, blueIndex] + areaBlue[blueIndex];
+                            xarea2[redIndex, greenIndex, blueIndex] = xarea2[redIndex - 1, greenIndex, blueIndex] + area2[blueIndex];
+
+                            data.Weights[alphaIndex, redIndex, greenIndex, blueIndex] = data.Weights[alphaIndex - 1, redIndex, greenIndex, blueIndex] + xarea[redIndex, greenIndex, blueIndex];
+                            data.MomentsAlpha[alphaIndex, redIndex, greenIndex, blueIndex] = data.MomentsAlpha[alphaIndex - 1, redIndex, greenIndex, blueIndex] + xareaAlpha[redIndex, greenIndex, blueIndex];
+                            data.MomentsRed[alphaIndex, redIndex, greenIndex, blueIndex] = data.MomentsRed[alphaIndex - 1, redIndex, greenIndex, blueIndex] + xareaRed[redIndex, greenIndex, blueIndex];
+                            data.MomentsGreen[alphaIndex, redIndex, greenIndex, blueIndex] = data.MomentsGreen[alphaIndex - 1, redIndex, greenIndex, blueIndex] + xareaGreen[redIndex, greenIndex, blueIndex];
+                            data.MomentsBlue[alphaIndex, redIndex, greenIndex, blueIndex] = data.MomentsBlue[alphaIndex - 1, redIndex, greenIndex, blueIndex] + xareaBlue[redIndex, greenIndex, blueIndex];
+                            data.Moments[alphaIndex, redIndex, greenIndex, blueIndex] = data.Moments[alphaIndex - 1, redIndex, greenIndex, blueIndex] + xarea2[redIndex, greenIndex, blueIndex];
+                        }
+                    }
+                }
+            }
+            return data;
+        }
+
+        private static long Top(Box cube, int direction, int position, long[,,,] moment)
+        {
+            switch (direction)
+            {
+                case Alpha:
+                    return (moment[position, cube.RedMaximum, cube.GreenMaximum, cube.BlueMaximum] -
+                            moment[position, cube.RedMaximum, cube.GreenMinimum, cube.BlueMaximum] -
+                            moment[position, cube.RedMinimum, cube.GreenMaximum, cube.BlueMaximum] +
+                            moment[position, cube.RedMinimum, cube.GreenMinimum, cube.BlueMaximum]) -
+                           (moment[position, cube.RedMaximum, cube.GreenMaximum, cube.BlueMinimum] -
+                            moment[position, cube.RedMaximum, cube.GreenMinimum, cube.BlueMinimum] -
+                            moment[position, cube.RedMinimum, cube.GreenMaximum, cube.BlueMinimum] +
+                            moment[position, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum]);
+
+                case Red:
+                    return (moment[cube.AlphaMaximum, position, cube.GreenMaximum, cube.BlueMaximum] -
+                            moment[cube.AlphaMaximum, position, cube.GreenMinimum, cube.BlueMaximum] -
+                            moment[cube.AlphaMinimum, position, cube.GreenMaximum, cube.BlueMaximum] +
+                            moment[cube.AlphaMinimum, position, cube.GreenMinimum, cube.BlueMaximum]) -
+                           (moment[cube.AlphaMaximum, position, cube.GreenMaximum, cube.BlueMinimum] -
+                            moment[cube.AlphaMaximum, position, cube.GreenMinimum, cube.BlueMinimum] -
+                            moment[cube.AlphaMinimum, position, cube.GreenMaximum, cube.BlueMinimum] +
+                            moment[cube.AlphaMinimum, position, cube.GreenMinimum, cube.BlueMinimum]);
+
+                case Green:
+                    return (moment[cube.AlphaMaximum, cube.RedMaximum, position, cube.BlueMaximum] -
+                            moment[cube.AlphaMaximum, cube.RedMinimum, position, cube.BlueMaximum] -
+                            moment[cube.AlphaMinimum, cube.RedMaximum, position, cube.BlueMaximum] +
+                            moment[cube.AlphaMinimum, cube.RedMinimum, position, cube.BlueMaximum]) -
+                           (moment[cube.AlphaMaximum, cube.RedMaximum, position, cube.BlueMinimum] -
+                            moment[cube.AlphaMaximum, cube.RedMinimum, position, cube.BlueMinimum] -
+                            moment[cube.AlphaMinimum, cube.RedMaximum, position, cube.BlueMinimum] +
+                            moment[cube.AlphaMinimum, cube.RedMinimum, position, cube.BlueMinimum]);
+
+                case Blue:
+                    return (moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMaximum, position] -
+                            moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMinimum, position] -
+                            moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMaximum, position] +
+                            moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMinimum, position]) -
+                           (moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMaximum, position] -
+                            moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMinimum, position] -
+                            moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMaximum, position] +
+                            moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, position]);
+
+                default:
+                    return 0;
+            }
+        }
+
+        private static long Bottom(Box cube, int direction, long[,,,] moment)
+        {
+            switch (direction)
+            {
+                case Alpha:
+                    return (-moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMaximum] +
+                            moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMaximum] +
+                            moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMaximum] -
+                            moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMaximum]) -
+                           (-moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMinimum] +
+                            moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMinimum] +
+                            moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMinimum] -
+                            moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum]);
+
+                case Red:
+                    return (-moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMaximum] +
+                            moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMaximum] +
+                            moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMaximum] -
+                            moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMaximum]) -
+                           (-moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMinimum] +
+                            moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum] +
+                            moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMinimum] -
+                            moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum]);
+
+                case Green:
+                    return (-moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMaximum] +
+                            moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMaximum] +
+                            moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMaximum] -
+                            moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMaximum]) -
+                           (-moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMinimum] +
+                            moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum] +
+                            moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMinimum] -
+                            moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum]);
+
+                case Blue:
+                    return (-moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMinimum] +
+                            moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMinimum] +
+                            moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMinimum] -
+                            moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum]) -
+                           (-moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMinimum] +
+                            moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMinimum] +
+                            moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMinimum] -
+                            moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum]);
+
+                default:
+                    return 0;
+            }
+        }
+
+        private static CubeCut Maximize(ColorData data, Box cube, int direction, byte first, byte last, long wholeAlpha, long wholeRed, long wholeGreen, long wholeBlue, long wholeWeight)
+        {
+            var bottomAlpha = Bottom(cube, direction, data.MomentsAlpha);
+            var bottomRed = Bottom(cube, direction, data.MomentsRed);
+            var bottomGreen = Bottom(cube, direction, data.MomentsGreen);
+            var bottomBlue = Bottom(cube, direction, data.MomentsBlue);
+            var bottomWeight = Bottom(cube, direction, data.Weights);
+
+            var result = 0.0f;
+            byte? cutPoint = null;
+
+            for (var position = first; position < last; ++position)
+            {
+                var halfAlpha = bottomAlpha + Top(cube, direction, position, data.MomentsAlpha);
+                var halfRed = bottomRed + Top(cube, direction, position, data.MomentsRed);
+                var halfGreen = bottomGreen + Top(cube, direction, position, data.MomentsGreen);
+                var halfBlue = bottomBlue + Top(cube, direction, position, data.MomentsBlue);
+                var halfWeight = bottomWeight + Top(cube, direction, position, data.Weights);
+
+                if (halfWeight == 0) continue;
+
+                var halfDistance = halfAlpha * halfAlpha + halfRed * halfRed + halfGreen * halfGreen + halfBlue * halfBlue;
+                var temp = halfDistance / halfWeight;
+
+                halfAlpha = wholeAlpha - halfAlpha;
+                halfRed = wholeRed - halfRed;
+                halfGreen = wholeGreen - halfGreen;
+                halfBlue = wholeBlue - halfBlue;
+                halfWeight = wholeWeight - halfWeight;
+
+                if (halfWeight != 0)
+                {
+                    halfDistance = halfAlpha * halfAlpha + halfRed * halfRed + halfGreen * halfGreen + halfBlue * halfBlue;
+                    temp += halfDistance / halfWeight;
+
+                    if (temp > result)
+                    {
+                        result = temp;
+                        cutPoint = position;
+                    }
+                }
+            }
+
+            return new CubeCut(cutPoint, result);
+        }
+
+        private bool Cut(ColorData data, ref Box first,ref Box second)
+        {
+            int direction;
+            var wholeAlpha = Volume(first, data.MomentsAlpha);
+            var wholeRed = Volume(first, data.MomentsRed);
+            var wholeGreen = Volume(first, data.MomentsGreen);
+            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);
+
+            if ((maxAlpha.Value >= maxRed.Value) && (maxAlpha.Value >= maxGreen.Value) && (maxAlpha.Value >= maxBlue.Value))
+            {
+                direction = Alpha;
+                if (maxAlpha.Position == null) return false;
+            }
+            else if ((maxRed.Value >= maxAlpha.Value) && (maxRed.Value >= maxGreen.Value) && (maxRed.Value >= maxBlue.Value))
+                direction = Red;
+            else
+            {
+                if ((maxGreen.Value >= maxAlpha.Value) && (maxGreen.Value >= maxRed.Value) && (maxGreen.Value >= maxBlue.Value))
+                    direction = Green;
+                else
+                    direction = Blue;
+            }
+
+            second.AlphaMaximum = first.AlphaMaximum;
+            second.RedMaximum = first.RedMaximum;
+            second.GreenMaximum = first.GreenMaximum;
+            second.BlueMaximum = first.BlueMaximum;
+
+            switch (direction)
+            {
+                case Alpha:
+                    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.AlphaMinimum = first.AlphaMinimum;
+                    second.GreenMinimum = first.GreenMinimum;
+                    second.BlueMinimum = first.BlueMinimum;
+                    break;
+
+                case Green:
+                    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.AlphaMinimum = first.AlphaMinimum;
+                    second.RedMinimum = first.RedMinimum;
+                    second.GreenMinimum = first.GreenMinimum;
+                    break;
+            }
+
+            first.Size = (first.AlphaMaximum - first.AlphaMinimum) * (first.RedMaximum - first.RedMinimum) * (first.GreenMaximum - first.GreenMinimum) * (first.BlueMaximum - first.BlueMinimum);
+            second.Size = (second.AlphaMaximum - second.AlphaMinimum) * (second.RedMaximum - second.RedMinimum) * (second.GreenMaximum - second.GreenMinimum) * (second.BlueMaximum - second.BlueMinimum);
+
+            return true;
+        }
+
+        private static float CalculateVariance(ColorData data, Box cube)
+        {
+            float volumeAlpha = Volume(cube, data.MomentsAlpha);
+            float volumeRed = Volume(cube, data.MomentsRed);
+            float volumeGreen = Volume(cube, data.MomentsGreen);
+            float volumeBlue = Volume(cube, data.MomentsBlue);
+            float volumeMoment = VolumeFloat(cube, data.Moments);
+            float volumeWeight = Volume(cube, data.Weights);
+
+            float distance = volumeAlpha * volumeAlpha + volumeRed * volumeRed + volumeGreen * volumeGreen + volumeBlue * volumeBlue;
+
+            var result = volumeMoment - distance / volumeWeight;
+            return double.IsNaN(result) ? 0.0f : result;
+        }
+
+        private static long Volume(Box cube, long[,,,] moment)
+        {
+            return (moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMaximum] -
+                    moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMaximum] -
+                    moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMaximum] +
+                    moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMaximum] -
+                    moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMaximum] +
+                    moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMaximum] +
+                    moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMaximum] -
+                    moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMaximum]) -
+
+                   (moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMinimum] -
+                    moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMinimum] -
+                    moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMinimum] +
+                    moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMinimum] -
+                    moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMinimum] +
+                    moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMinimum] +
+                    moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum] -
+                    moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum]);
+        }
+
+        private static float VolumeFloat(Box cube, float[,,,] moment)
+        {
+            return (moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMaximum] -
+                    moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMaximum] -
+                    moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMaximum] +
+                    moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMaximum] -
+                    moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMaximum] +
+                    moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMaximum] +
+                    moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMaximum] -
+                    moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMaximum]) -
+
+                   (moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMinimum] -
+                    moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMinimum] -
+                    moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMinimum] +
+                    moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMinimum] -
+                    moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMinimum] +
+                    moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMinimum] +
+                    moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum] -
+                    moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum]);
+        }
+
+        private IEnumerable<Box> SplitData(ref int colorCount, ColorData data)
+        {
+            --colorCount;
+            var next = 0;
+            var volumeVariance = new float[MaxColor];
+            var cubes = new Box[MaxColor];
+            cubes[0].AlphaMaximum = MaxSideIndex;
+            cubes[0].RedMaximum = MaxSideIndex;
+            cubes[0].GreenMaximum = MaxSideIndex;
+            cubes[0].BlueMaximum = MaxSideIndex;
+            for (var cubeIndex = 1; cubeIndex < colorCount; ++cubeIndex)
+            {
+                if (Cut(data, ref cubes[next], ref cubes[cubeIndex]))
+                {
+                    volumeVariance[next] = cubes[next].Size > 1 ? CalculateVariance(data, cubes[next]) : 0.0f;
+                    volumeVariance[cubeIndex] = cubes[cubeIndex].Size > 1 ? CalculateVariance(data, cubes[cubeIndex]) : 0.0f;
+                }
+                else
+                {
+                    volumeVariance[next] = 0.0f;
+                    cubeIndex--;
+                }
+
+                next = 0;
+                var temp = volumeVariance[0];
+
+                for (var index = 1; index <= cubeIndex; ++index)
+                {
+                    if (volumeVariance[index] <= temp) continue;
+                    temp = volumeVariance[index];
+                    next = index;
+                }
+
+                if (temp > 0.0) continue;
+                colorCount = cubeIndex + 1;
+                break;
+            }
+            return cubes.Take(colorCount).ToList();
+        }
+
+        protected LookupData BuildLookups(IEnumerable<Box> cubes, ColorData data)
+        {
+            LookupData lookups = new LookupData(SideSize);
+            int lookupsCount = lookups.Lookups.Count;
+
+            foreach (var cube in cubes)
+            {
+                for (var alphaIndex = (byte)(cube.AlphaMinimum + 1); alphaIndex <= cube.AlphaMaximum; ++alphaIndex)
+                {
+                    for (var redIndex = (byte)(cube.RedMinimum + 1); redIndex <= cube.RedMaximum; ++redIndex)
+                    {
+                        for (var greenIndex = (byte)(cube.GreenMinimum + 1); greenIndex <= cube.GreenMaximum; ++greenIndex)
+                        {
+                            for (var blueIndex = (byte)(cube.BlueMinimum + 1); blueIndex <= cube.BlueMaximum; ++blueIndex)
+                            {
+                                lookups.Tags[alphaIndex, redIndex, greenIndex, blueIndex] = lookupsCount;
+                            }
+                        }
+                    }
+                }
+
+                var weight = Volume(cube, data.Weights);
+
+                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)
+                    };
+                lookups.Lookups.Add(lookup);
+            }
+            return lookups;
+        }
+
+        protected abstract QuantizedPalette GetQuantizedPalette(int colorCount, ColorData data, IEnumerable<Box> cubes, int alphaThreshold);
+    }
+}

+ 15 - 0
TEAMModelOS.SDK/PngQuant/QuantizationException.cs

@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace TEAMModelOS.SDK.PngQuant
+{
+    public class QuantizationException : ApplicationException
+    {
+        public QuantizationException(string message) : base(message)
+        {
+
+        }
+    }
+}

+ 16 - 0
TEAMModelOS.SDK/PngQuant/QuantizedPalette.cs

@@ -0,0 +1,16 @@
+using System.Collections.Generic;
+using System.Drawing;
+
+namespace TEAMModelOS.SDK.PngQuant
+{
+    public class QuantizedPalette
+    {
+        public QuantizedPalette(int size)
+        {
+            Colors = new List<Color>();
+            PixelIndex = new int[size];
+        }
+        public IList<Color> Colors { get; private set; }
+        public int[] PixelIndex { get; private set; }
+    }
+}

+ 31 - 0
TEAMModelOS/Controllers/Core/CoreController.cs

@@ -0,0 +1,31 @@
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.Json;
+using System.Threading.Tasks;
+using TEAMModelOS.SDK.DI;
+
+namespace TEAMModelOS.Controllers.Core
+{
+    [Route("Core")]
+    [ApiController]
+    public class CoreController : ControllerBase
+    {
+        private readonly AzureStorageFactory _azureStorage;
+
+        public CoreController(AzureStorageFactory azureStorage)
+        {
+            _azureStorage = azureStorage;
+        }
+
+        [HttpPost("image-quant")]
+        public async Task<IActionResult> ImageQuang(JsonElement request)
+        {
+            if (!request.TryGetProperty("id_token", out JsonElement id_token)) return BadRequest();
+
+            return Ok();
+        }   
+    }
+}

+ 1 - 1
TEAMModelOS/Controllers/Teacher/InitController.cs

@@ -139,7 +139,7 @@ namespace TEAMModelOS.Controllers
                 }
 
                 //換取AuthToken,提供給前端
-                var auth_token = JwtAuthExtension.CreateAuthToken(_option.HostName, id, name?.ToString(), picture?.ToString(), _option.JwtSecretKey, roles: new[] { "teacher", "student" });
+                var auth_token = JwtAuthExtension.CreateAuthToken(_option.HostName, id, name?.ToString(), picture?.ToString(), _option.JwtSecretKey, roles: new[] { "teacher"});
 
                 //取得Teacher Blob 容器位置及SAS 
                 var container = _azureStorage.GetBlobContainerClient(id);