diff --git a/ColorThief/Class/CMap.cs b/ColorThief/Class/CMap.cs index 76c6e33..2e8356e 100644 --- a/ColorThief/Class/CMap.cs +++ b/ColorThief/Class/CMap.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Drawing; namespace ColorThiefDotNet { @@ -26,8 +27,8 @@ public IEnumerable GeneratePalette() { foreach (VBox vbox in vboxes) { - int[] rgb = vbox.Avg(false); - CTColor color = FromRgb(rgb[0], rgb[1], rgb[2]); + int rgb = vbox.Avg(false); + Color color = Color.FromArgb((byte)(rgb >> 16 & 0xFF), (byte)(rgb >> 8 & 0xFF), (byte)(rgb >> 0 & 0xFF)); yield return new QuantizedColor(color, vbox.Count(false)); } } @@ -39,15 +40,13 @@ public List GeneratePaletteList() palette = new List(); foreach (VBox vbox in vboxes) { - int[] rgb = vbox.Avg(false); - CTColor color = FromRgb(rgb[0], rgb[1], rgb[2]); + int rgb = vbox.Avg(false); + Color color = Color.FromArgb((byte)(rgb >> 16 & 0xFF), (byte)(rgb >> 8 & 0xFF), (byte)(rgb >> 0 & 0xFF)); palette.Add(new QuantizedColor(color, vbox.Count(false))); } } return palette; } - - public CTColor FromRgb(int red, int green, int blue) => new CTColor { R = (byte)red, G = (byte)green, B = (byte)blue }; } } \ No newline at end of file diff --git a/ColorThief/Class/Color.cs b/ColorThief/Class/Color.cs deleted file mode 100644 index 318e36f..0000000 --- a/ColorThief/Class/Color.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace ColorThiefDotNet -{ - /// - /// Defines a color in RGB space. - /// - public struct CTColor - { - /// - /// Get or Set the Blue component value for sRGB. - /// - public byte B; - - /// - /// Get or Set the Green component value for sRGB. - /// - public byte G; - - /// - /// Get or Set the Red component value for sRGB. - /// - public byte R; - } -} \ No newline at end of file diff --git a/ColorThief/Class/ColorThief.cs b/ColorThief/Class/ColorThief.cs deleted file mode 100644 index 7dec232..0000000 --- a/ColorThief/Class/ColorThief.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System; -using System.Drawing; - -namespace ColorThiefDotNet -{ - public partial class ColorThief - { - public const int DefaultColorCount = 5; - public const int DefaultQuality = 10; - public const bool DefaultIgnoreWhite = true; - public const int ColorDepth = 3; - - /// - /// Use the median cut algorithm to cluster similar colors. - /// - /// Pixel array. - /// The color count. - /// - private CMap GetColorMap(byte[][] pixelArray, int colorCount) - { - // Send array to quantize function which clusters values using median - // cut algorithm - - if (colorCount > 0) - { - --colorCount; - } - - return Mmcq.Quantize(pixelArray, colorCount); - } - - private byte[][] GetPixels(Bitmap sourceImage, int quality, bool ignoreWhite) - { - int imgWidth = sourceImage.Width; - int imgHeight = sourceImage.Height; - int pixelCount = imgWidth * imgHeight; - - // Store the RGB values in an array format suitable for quantize - // function - - // numRegardedPixels must be rounded up to avoid an - // ArrayIndexOutOfBoundsException if all pixels are good. - int numRegardedPixels = (pixelCount + quality - 1) / quality; - - int numUsedPixels = 0; - byte[][] pixelArray = new byte[numRegardedPixels][]; - - int xstartOffset = 0; - - for (int y = 0; y < imgHeight; y++) - { - for (int x = 0 + (Math.Max(imgWidth, xstartOffset) - imgWidth); x < imgWidth; x += quality, xstartOffset = x) - { - Color pixel = sourceImage.GetPixel(x, y); - byte r = pixel.R; - byte g = pixel.G; - byte b = pixel.B; - - // If pixel is mostly opaque and not white - if (!(ignoreWhite && r > 250 && g > 250 && b > 250)) - { - pixelArray[numUsedPixels] = new[] { r, g, b }; - numUsedPixels++; - } - } - } - - if (ignoreWhite) - { - // Remove unused pixels from the array - byte[][] copy = new byte[numUsedPixels][]; - Array.Copy(pixelArray, copy, numUsedPixels); - return copy; - } - else - { - return pixelArray; - } - } - } -} \ No newline at end of file diff --git a/ColorThief/Class/Mmcq.cs b/ColorThief/Class/Mmcq.cs index 128a9cf..08799e4 100644 --- a/ColorThief/Class/Mmcq.cs +++ b/ColorThief/Class/Mmcq.cs @@ -1,8 +1,21 @@ using System; using System.Collections.Generic; +using System.Linq; namespace ColorThiefDotNet { + internal ref struct VBoxesRef + { + public VBoxesRef(VBox vbox1, VBox vbox2) + { + VBox1 = vbox1; + VBox2 = vbox2; + } + + public readonly VBox VBox1; + public readonly VBox VBox2; + } + internal static class Mmcq { public const int Sigbits = 5; @@ -15,69 +28,29 @@ internal static class Mmcq public const double WeightSaturation = 3d; public const double WeightLuma = 6d; public const double WeightPopulation = 1d; + + private static readonly int[] EmptyVboxLengthArray = Enumerable.Repeat(-1, VboxLength).ToArray(); private static readonly VBoxComparer ComparatorProduct = new VBoxComparer(); private static readonly VBoxCountComparer ComparatorCount = new VBoxCountComparer(); public static int GetColorIndex(int r, int g, int b) => (r << (2 * Sigbits)) + (g << Sigbits) + b; - private static VBox VboxFromPixels(byte[][] pixels, int[] histo) - { - int rmin = 1000000, rmax = 0; - int gmin = 1000000, gmax = 0; - int bmin = 1000000, bmax = 0; - - // find min/max - int numPixels = pixels.Length; - for (int i = 0; i < numPixels; i++) - { - byte[] pixel = pixels[i]; - int rval = pixel[0] >> Rshift; - int gval = pixel[1] >> Rshift; - int bval = pixel[2] >> Rshift; - - if (rval < rmin) - { - rmin = rval; - } - else if (rval > rmax) - { - rmax = rval; - } - - if (gval < gmin) - { - gmin = gval; - } - else if (gval > gmax) - { - gmax = gval; - } - - if (bval < bmin) - { - bmin = bval; - } - else if (bval > bmax) - { - bmax = bval; - } - } - - return new VBox(rmin, rmax, gmin, gmax, bmin, bmax, histo); - } - - private static VBox[] DoCut(char color, VBox vbox, int[] partialsum, int[] lookaheadsum, int total) +#if NETCOREAPP + private static VBoxesRef DoCut(int color, VBox vbox, ReadOnlySpan partialsum, ReadOnlySpan lookaheadsum, int total) +#else + private static VBoxesRef DoCut(int color, VBox vbox, in int[] partialsum, in int[] lookaheadsum, int total) +#endif { int vboxDim1; int vboxDim2; switch (color) { - case 'r': + case 0: vboxDim1 = vbox.R1; vboxDim2 = vbox.R2; break; - case 'g': + case 1: vboxDim1 = vbox.G1; vboxDim2 = vbox.G2; break; @@ -115,11 +88,11 @@ private static VBox[] DoCut(char color, VBox vbox, int[] partialsum, int[] looka // set dimensions switch (color) { - case 'r': + case 0: vbox1.R2 = d2; vbox2.R1 = d2 + 1; break; - case 'g': + case 1: vbox1.G2 = d2; vbox2.G1 = d2 + 1; break; @@ -128,29 +101,22 @@ private static VBox[] DoCut(char color, VBox vbox, int[] partialsum, int[] looka vbox2.B1 = d2 + 1; break; } + vbox1.count = partialsum[d2]; + vbox2.count = lookaheadsum[d2]; - return new[] { vbox1, vbox2 }; + return new VBoxesRef(vbox1, vbox2); } } throw new Exception("VBox can't be cut"); } - private static VBox[] MedianCutApply(int[] histo, VBox vbox) - { - if (vbox.Count(false) == 0) - { - return null; - } - if (vbox.Count(false) == 1) - { -#if NETCOREAPP && NET7_0_OR_GREATER - return new[] { vbox.Clone(), new VBox() }; +#if NETCOREAPP + private static VBoxesRef MedianCutApply(ReadOnlySpan histo, VBox vbox) #else - return new[] { vbox.Clone(), null }; + private static VBoxesRef MedianCutApply(in int[] histo, VBox vbox) #endif - } - + { // only one pixel, no split int rw = vbox.R2 - vbox.R1 + 1; @@ -160,19 +126,25 @@ private static VBox[] MedianCutApply(int[] histo, VBox vbox) // Find the partial sum arrays along the selected axis. int total = 0; - int[] partialsum = new int[VboxLength]; + // -1 = not set / 0 = 0 - for (int l = 0; l < partialsum.Length; l++) - { - partialsum[l] = -1; - } +#if NETCOREAPP + Span partialsum = new int[VboxLength]; + partialsum.Fill(-1); +#else + int[] partialsum = new int[VboxLength]; + Array.Copy(EmptyVboxLengthArray, partialsum, VboxLength); +#endif + // -1 = not set / 0 = 0 +#if NETCOREAPP + Span lookaheadsum = new int[VboxLength]; + lookaheadsum.Fill(-1); +#else int[] lookaheadsum = new int[VboxLength]; - for (int l = 0; l < lookaheadsum.Length; l++) - { - lookaheadsum[l] = -1; - } + Array.Copy(EmptyVboxLengthArray, lookaheadsum, VboxLength); +#endif int i, j, k, sum, index; @@ -237,8 +209,8 @@ private static VBox[] MedianCutApply(int[] histo, VBox vbox) } // determine the cut planes - return maxw == rw ? DoCut('r', vbox, partialsum, lookaheadsum, total) : maxw == gw - ? DoCut('g', vbox, partialsum, lookaheadsum, total) : DoCut('b', vbox, partialsum, lookaheadsum, total); + return maxw == rw ? DoCut(0, vbox, partialsum, lookaheadsum, total) : maxw == gw + ? DoCut(1, vbox, partialsum, lookaheadsum, total) : DoCut(2, vbox, partialsum, lookaheadsum, total); } /// @@ -249,7 +221,11 @@ private static VBox[] MedianCutApply(int[] histo, VBox vbox) /// The target. /// The histo. /// vbox1 not defined; shouldn't happen! +#if NETCOREAPP + private static void Iter(List lh, IComparer comparator, int target, ReadOnlySpan histo) +#else private static void Iter(List lh, IComparer comparator, int target, int[] histo) +#endif { int ncolors = 1; int niters = 0; @@ -257,6 +233,7 @@ private static void Iter(List lh, IComparer comparator, int target, while (niters < MaxIterations) { VBox vbox = lh[lh.Count - 1]; + if (vbox.Count(false) == 0) { lh.Sort(comparator); @@ -267,29 +244,19 @@ private static void Iter(List lh, IComparer comparator, int target, lh.RemoveAt(lh.Count - 1); // do the cut - VBox[] vboxes = MedianCutApply(histo, vbox); - VBox vbox1 = vboxes[0]; - VBox vbox2 = vboxes[1]; + VBoxesRef vboxesRef = MedianCutApply(histo, vbox); -#if NETCOREAPP && NET7_0_OR_GREATER - if (vbox1.isDummy) -#else - if (vbox1 == null) -#endif + if (vboxesRef.VBox1.isDummy) { throw new Exception( "vbox1 not defined; shouldn't happen!"); } - lh.Add(vbox1); + lh.Add(vboxesRef.VBox1); -#if NETCOREAPP && NET7_0_OR_GREATER - if (!vbox2.isDummy) -#else - if (vbox2 != null) -#endif + if (!vboxesRef.VBox2.isDummy) { - lh.Add(vbox2); + lh.Add(vboxesRef.VBox2); ncolors++; } lh.Sort(comparator); @@ -305,22 +272,51 @@ private static void Iter(List lh, IComparer comparator, int target, } } - public static CMap Quantize(byte[][] pixels, int maxcolors) + public static CMap Quantize(IEnumerable pixelEnumerable, int maxcolors, bool ignoreWhite) { int[] histo = new int[Histosize]; + int rmin = 1000000, rmax = 0; + int gmin = 1000000, gmax = 0; + int bmin = 1000000, bmax = 0; - foreach (var pixel in pixels) + int pixelLength = 0; + + foreach (int pixel in pixelEnumerable) { - int rval = pixel[0] >> Rshift; - int gval = pixel[1] >> Rshift; - int bval = pixel[2] >> Rshift; - int index = GetColorIndex(rval, gval, bval); - histo[index]++; + byte r = (byte)pixel; + byte g = (byte)(pixel >> 8); + byte b = (byte)(pixel >> 16); + byte a = (byte)(pixel >> 24); + + if (a < 180) continue; + + if (!(ignoreWhite && r > 230 && g > 230 && b > 230)) + { + int rval = r >> Rshift; + int gval = g >> Rshift; + int bval = b >> Rshift; + + int index = GetColorIndex(rval, gval, bval); + histo[index]++; + + if (rval < rmin) rmin = rval; + if (rval > rmax) rmax = rval; + + if (gval < gmin) gmin = gval; + if (gval > gmax) gmax = gval; + + if (bval < bmin) bmin = bval; + if (bval > bmax) bmax = bval; + + pixelLength++; + } } // get the beginning vbox from the colors - VBox vbox = VboxFromPixels(pixels, histo); - List pq = new List { vbox }; + List pq = new List + { + new VBox(rmin, rmax, gmin, gmax, bmin, bmax, histo) { count = pixelLength } + }; // Round up to have the same behaviour as in JavaScript int target = (int)Math.Ceiling(FractByPopulation * maxcolors); diff --git a/ColorThief/Class/QuantizedColor.cs b/ColorThief/Class/QuantizedColor.cs index e3ebb8a..54810ca 100644 --- a/ColorThief/Class/QuantizedColor.cs +++ b/ColorThief/Class/QuantizedColor.cs @@ -1,4 +1,5 @@ using System; +using System.Drawing; namespace ColorThiefDotNet { @@ -8,17 +9,17 @@ public struct QuantizedColor public class QuantizedColor #endif { - public QuantizedColor(CTColor color, int population) + public QuantizedColor(Color color, int population) { Color = color; Population = population; IsDark = CalculateYiqLuma(color) < 128; } - public CTColor Color { get; private set; } + public Color Color { get; private set; } public int Population { get; private set; } public bool IsDark { get; private set; } - public int CalculateYiqLuma(CTColor color) => (int)Math.Round((299 * color.R + 587 * color.G + 114 * color.B) / 1000f); + public int CalculateYiqLuma(Color color) => (int)Math.Round((299 * color.R + 587 * color.G + 114 * color.B) / 1000f); } } \ No newline at end of file diff --git a/ColorThief/Class/VBox.cs b/ColorThief/Class/VBox.cs index 8792956..e756792 100644 --- a/ColorThief/Class/VBox.cs +++ b/ColorThief/Class/VBox.cs @@ -6,25 +6,19 @@ namespace ColorThiefDotNet /// /// 3D color space box. /// -#if NETCOREAPP && NET7_0_OR_GREATER - internal struct VBox -#else - internal class VBox -#endif + public unsafe struct VBox { - private readonly int[] histo; - private int[] avg; + private int avg; public int B1; public int B2; - private int? count; + public int count; public int G1; public int G2; public int R1; public int R2; - private int? volume; -#if NETCOREAPP && NET7_0_OR_GREATER - public bool isDummy = true; -#endif + private int volume; + public bool isDummy; + public int[] histo; public VBox(int r1, int r2, int g1, int g2, int b1, int b2, int[] histo) { @@ -35,25 +29,25 @@ public VBox(int r1, int r2, int g1, int g2, int b1, int b2, int[] histo) B1 = b1; B2 = b2; - this.histo = histo; -#if NETCOREAPP && NET7_0_OR_GREATER + this.avg = int.MinValue; + this.count = int.MinValue; + this.volume = int.MinValue; this.isDummy = false; -#endif + this.histo = histo; } public int Volume(bool force) { - if (volume == null || force) + if (volume == int.MinValue) { volume = (R2 - R1 + 1) * (G2 - G1 + 1) * (B2 - B1 + 1); } - - return volume.Value; + return volume; } public int Count(bool force) { - if (count == null || force) + if (count == int.MinValue || force) { int npix = 0; int i; @@ -75,14 +69,14 @@ public int Count(bool force) count = npix; } - return count.Value; + return count; } public VBox Clone() => new VBox(R1, R2, G1, G2, B1, B2, histo); - public int[] Avg(bool force) + public int Avg(bool force) { - if (avg == null || force) + if (avg == int.MinValue || force) { int ntot = 0; @@ -112,20 +106,18 @@ public int[] Avg(bool force) if (ntot > 0) { - avg = new[] - { - Math.Abs(rsum / ntot), Math.Abs(gsum / ntot), - Math.Abs(bsum / ntot) - }; + byte r = (byte)Math.Abs(rsum / ntot); + byte g = (byte)Math.Abs(gsum / ntot); + byte b = (byte)Math.Abs(bsum / ntot); + avg = r << 16 | g << 8 | b; + } else { - avg = new[] - { - Math.Abs(Mmcq.Mult * (R1 + R2 + 1) / 2), - Math.Abs(Mmcq.Mult * (G1 + G2 + 1) / 2), - Math.Abs(Mmcq.Mult * (B1 + B2 + 1) / 2) - }; + byte r = (byte)Math.Abs(Mmcq.Mult * (R1 + R2 + 1) / 2); + byte g = (byte)Math.Abs(Mmcq.Mult * (G1 + G2 + 1) / 2); + byte b = (byte)Math.Abs(Mmcq.Mult * (B1 + B2 + 1) / 2); + avg = r << 16 | g << 8 | b; } } diff --git a/ColorThief/ColorThief.cs b/ColorThief/ColorThief.cs index 67e143c..e63bad8 100644 --- a/ColorThief/ColorThief.cs +++ b/ColorThief/ColorThief.cs @@ -1,11 +1,19 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Drawing; +using System.Drawing.Imaging; using System.Linq; +using System.Runtime.Versioning; namespace ColorThiefDotNet { - public partial class ColorThief + public static class ColorThief { + private const int DefaultColorCount = 5; + private const int DefaultQuality = 10; + private const bool DefaultIgnoreWhite = true; + private const int ColorDepth = 3; + /// /// Use the median cut algorithm to cluster similar colors and return the base color from the largest cluster. /// @@ -18,16 +26,16 @@ public partial class ColorThief /// /// if set to true [ignore white]. /// - public QuantizedColor GetColor(Bitmap sourceImage, int quality = DefaultQuality, bool ignoreWhite = DefaultIgnoreWhite) + public static QuantizedColor GetColor(Bitmap sourceImage, int quality = DefaultQuality, bool ignoreWhite = DefaultIgnoreWhite) { IEnumerable palette = GetPaletteEnumeration(sourceImage, 3, quality, ignoreWhite); - - return new QuantizedColor(new CTColor - { - R = (byte)palette.Average(a => a.Color.R), - G = (byte)palette.Average(a => a.Color.G), - B = (byte)palette.Average(a => a.Color.B) - }, (int)palette.Average(a => a.Population)); + return new QuantizedColor( + Color.FromArgb( + 255, + (byte)palette.Average(a => a.Color.R), + (byte)palette.Average(a => a.Color.G), + (byte)palette.Average(a => a.Color.B) + ), (int)palette.Average(a => a.Population)); } /// @@ -44,7 +52,7 @@ public QuantizedColor GetColor(Bitmap sourceImage, int quality = DefaultQuality, /// if set to true [ignore white]. /// /// true - public IEnumerable GetPalette(Bitmap sourceImage, int colorCount = DefaultColorCount, int quality = DefaultQuality, bool ignoreWhite = DefaultIgnoreWhite) => + public static IEnumerable GetPalette(Bitmap sourceImage, int colorCount = DefaultColorCount, int quality = DefaultQuality, bool ignoreWhite = DefaultIgnoreWhite) => GetPaletteEnumeration(sourceImage, colorCount, quality, ignoreWhite); /// @@ -61,10 +69,9 @@ public IEnumerable GetPalette(Bitmap sourceImage, int colorCount /// if set to true [ignore white]. /// /// true - public IEnumerable GetPaletteEnumeration(Bitmap sourceImage, int colorCount = DefaultColorCount, int quality = DefaultQuality, bool ignoreWhite = DefaultIgnoreWhite) + public static IEnumerable GetPaletteEnumeration(Bitmap sourceImage, int colorCount = DefaultColorCount, int quality = DefaultQuality, bool ignoreWhite = DefaultIgnoreWhite) { - byte[][] pixelArray = GetPixels(sourceImage, quality < 1 ? DefaultQuality : quality, ignoreWhite); - CMap cmap = GetColorMap(pixelArray, colorCount); + CMap cmap = GetCMap(sourceImage, colorCount, quality, ignoreWhite); return cmap.GeneratePalette(); } @@ -82,11 +89,88 @@ public IEnumerable GetPaletteEnumeration(Bitmap sourceImage, int /// if set to true [ignore white]. /// /// true - public List GetPaletteList(Bitmap sourceImage, int colorCount = DefaultColorCount, int quality = DefaultQuality, bool ignoreWhite = DefaultIgnoreWhite) + public static List GetPaletteList(Bitmap sourceImage, int colorCount = DefaultColorCount, int quality = DefaultQuality, bool ignoreWhite = DefaultIgnoreWhite) { - byte[][] pixelArray = GetPixels(sourceImage, quality < 1 ? DefaultQuality : quality, ignoreWhite); - CMap cmap = GetColorMap(pixelArray, colorCount); + CMap cmap = GetCMap(sourceImage, colorCount, quality, ignoreWhite); return cmap.GeneratePaletteList(); } + + private static CMap GetCMap(Bitmap sourceImage, int colorCount, int quality, bool ignoreWhite) + { + int _quality = quality < 1 ? DefaultQuality : quality; + int _colorCount = colorCount <= 0 ? 1 : colorCount; + IEnumerable pixelEnumerable = EnumeratePixels(sourceImage, _quality); + return Mmcq.Quantize(pixelEnumerable, _colorCount, ignoreWhite); + } + +#if NETCOREAPP + [SupportedOSPlatform("windows")] +#endif + private static IEnumerable EnumeratePixels(Bitmap sourceImage, int quality) + { + int stepX = 0; + +#if NETCOREAPP + int chanCount = sourceImage.PixelFormat switch + { + PixelFormat.Format32bppRgb => 4, + PixelFormat.Format32bppArgb => 4, + PixelFormat.Format24bppRgb => 3, + _ => throw new NotSupportedException($"Pixel format of the image: {sourceImage.PixelFormat} is unsupported!") + }; +#else + int chanCount; + switch (sourceImage.PixelFormat) + { + case PixelFormat.Format32bppRgb: + case PixelFormat.Format32bppArgb: + chanCount = 4; + break; + case PixelFormat.Format24bppRgb: + chanCount = 3; + break; + default: + throw new NotSupportedException($"Pixel format of the image: {sourceImage.PixelFormat} is unsupported!"); + } +#endif + BitmapData data = sourceImage.LockBits(new Rectangle(new Point(), sourceImage.Size), ImageLockMode.ReadOnly, sourceImage.PixelFormat); + + int pixel; + int offset; + + if (chanCount == 4) + { + for (int y = 0; y < data.Height; y += quality) + { + for (int x = stepX; x < data.Width; x += quality) + { + offset = data.Stride * y + chanCount * x; + pixel = GetUnsafeSinglePixelWithAlpha(data.Scan0, offset); + + if (data.Width - x < quality) stepX = quality - (data.Width - x); + yield return pixel; + } + } + } + else + { + for (int y = 0; y < data.Height; y += quality) + { + for (int x = stepX; x < data.Width; x += quality) + { + offset = data.Stride * y + chanCount * x; + pixel = GetUnsafeSinglePixel(data.Scan0, offset); + + if (data.Width - x < quality) stepX = quality - (data.Width - x); + yield return pixel; + } + } + } + + sourceImage.UnlockBits(data); + } + + private static unsafe int GetUnsafeSinglePixel(IntPtr ptr, int offset) => *(byte*)(ptr + (offset + 2)) | (*(byte*)(ptr + (offset + 1)) << 8) | (*(byte*)(ptr + (offset + 0)) << 16) | (255 << 24); + private static unsafe int GetUnsafeSinglePixelWithAlpha(IntPtr ptr, int offset) => *(byte*)(ptr + (offset + 2)) | (*(byte*)(ptr + (offset + 1)) << 8) | (*(byte*)(ptr + (offset + 0)) << 16) | (*(byte*)(ptr + (offset + 3)) << 24); } } diff --git a/ColorThief/ColorThief.csproj b/ColorThief/ColorThief.csproj index 785073b..dff53a8 100644 --- a/ColorThief/ColorThief.csproj +++ b/ColorThief/ColorThief.csproj @@ -4,6 +4,7 @@ x64 Debug;Release;Publish portable + true