diff --git a/KGySoft.Drawing/Drawing/Imaging/_BitmapData/_Unmanaged/UnmanagedCustomBitmapDataIndexed.cs b/KGySoft.Drawing/Drawing/Imaging/_BitmapData/_Unmanaged/UnmanagedCustomBitmapDataIndexed.cs index f8726802..de9c84b9 100644 --- a/KGySoft.Drawing/Drawing/Imaging/_BitmapData/_Unmanaged/UnmanagedCustomBitmapDataIndexed.cs +++ b/KGySoft.Drawing/Drawing/Imaging/_BitmapData/_Unmanaged/UnmanagedCustomBitmapDataIndexed.cs @@ -84,6 +84,7 @@ public unsafe ref T GetRefAs(int x) where T : unmanaged #region Constructors + [SecurityCritical] public UnmanagedCustomBitmapDataIndexed(IntPtr buffer, Size size, int stride, PixelFormat pixelFormat, Func rowGetColorIndex, Action rowSetColorIndex, Palette? palette, Func? trySetPaletteCallback, Action? disposeCallback) diff --git a/KGySoft.Drawing/Drawing/Imaging/_Quantizers/OptimizedPaletteQuantizer.MedianCut.cs b/KGySoft.Drawing/Drawing/Imaging/_Quantizers/OptimizedPaletteQuantizer.MedianCut.cs index 2d4f6842..934b7148 100644 --- a/KGySoft.Drawing/Drawing/Imaging/_Quantizers/OptimizedPaletteQuantizer.MedianCut.cs +++ b/KGySoft.Drawing/Drawing/Imaging/_Quantizers/OptimizedPaletteQuantizer.MedianCut.cs @@ -3,7 +3,7 @@ /////////////////////////////////////////////////////////////////////////////// // File: OptimizedPaletteQuantizer.MedianCut.cs /////////////////////////////////////////////////////////////////////////////// -// Copyright (C) KGy SOFT, 2005-2021 - All Rights Reserved +// Copyright (C) KGy SOFT, 2005-2022 - All Rights Reserved // // You should have received a copy of the LICENSE file at the top-level // directory of this distribution. @@ -41,28 +41,62 @@ private enum ColorComponent { R, G, B } private sealed class ColorBucket { - private static readonly IComparer redSorter = new RedComparer(); - private static readonly IComparer greenSorter = new GreenComparer(); - private static readonly IComparer blueSorter = new BlueComparer(); + + #region Nested Classes + + #region RedComparer class private sealed class RedComparer : IComparer { - public int Compare(Color32 a, Color32 b) => ((a.R << 16) | (a.G << 8) | a.B) - ((b.R << 16) | (b.G << 8) | b.B); + #region Methods + + public int Compare(Color32 a, Color32 b) => a.R - b.R; + + #endregion } + #endregion + + #region GreenComparer class + private sealed class GreenComparer : IComparer { - public int Compare(Color32 a, Color32 b) => ((a.G << 16) | (a.R << 8) | a.B) - ((b.G << 16) | (b.R << 8) | b.B); + #region Methods + + public int Compare(Color32 a, Color32 b) => a.G - b.G; + + #endregion } + #endregion + + #region BlueComparer class + private sealed class BlueComparer : IComparer { - public int Compare(Color32 a, Color32 b) => ((a.B << 16) | (a.G << 8) | a.R) - ((b.B << 16) | (b.G << 8) | b.R); + #region Methods + + public int Compare(Color32 a, Color32 b) => a.B - b.B; + + #endregion } + #endregion + + #endregion #region Fields + #region Static Fields + + private static readonly IComparer redSorter = new RedComparer(); + private static readonly IComparer greenSorter = new GreenComparer(); + private static readonly IComparer blueSorter = new BlueComparer(); + + #endregion + + #region Instance Fields + private readonly List colors; private int rMin; @@ -74,6 +108,8 @@ private sealed class BlueComparer : IComparer #endregion + #endregion + #region Properties internal int Count => colors.Count; @@ -159,7 +195,7 @@ internal void Split(ColorComponent component, ColorBucketCollection buckets, ref int medianIndex = colors.Count >> 1; // single color check is correct because we sorted by all of the components - bool isLeftSingleColor = colors[0] == colors[medianIndex - 1]; + bool isLeftSingleColor = colors[0] == colors[medianIndex - 1]; bool isRightSingleColor = colors[medianIndex] == colors[colors.Count - 1]; ColorBucket? left = isLeftSingleColor ? null : new ColorBucket(medianIndex); ColorBucket? right = isRightSingleColor ? null : new ColorBucket(colors.Count - medianIndex); @@ -219,7 +255,7 @@ internal void Split(ColorComponent component, ColorBucketCollection buckets, ref private sealed class ColorBucketCollection { #region Fields - + private readonly int maxColors; private readonly CircularList buckets; private readonly HashSet finalColors = new HashSet(); @@ -330,12 +366,12 @@ internal bool SplitBuckets(IAsyncContext context, ref int index) #endregion - #region Methods - public void Initialize(int requestedColors, IBitmapData source) + public void Initialize(int requestedColors, byte? bitLevel, IBitmapData source) { - maxColors = requestedColors; + int maxLevels = 1 << (bitLevel ?? 8); + maxColors = Math.Min(requestedColors, maxLevels * maxLevels * maxLevels); root = new ColorBucket(source.Width * source.Height); } diff --git a/KGySoft.Drawing/Drawing/Imaging/_Quantizers/OptimizedPaletteQuantizer.Octree.cs b/KGySoft.Drawing/Drawing/Imaging/_Quantizers/OptimizedPaletteQuantizer.Octree.cs index 1ed2d6ed..8fb210c7 100644 --- a/KGySoft.Drawing/Drawing/Imaging/_Quantizers/OptimizedPaletteQuantizer.Octree.cs +++ b/KGySoft.Drawing/Drawing/Imaging/_Quantizers/OptimizedPaletteQuantizer.Octree.cs @@ -3,7 +3,7 @@ /////////////////////////////////////////////////////////////////////////////// // File: OptimizedPaletteQuantizer.Octree.cs /////////////////////////////////////////////////////////////////////////////// -// Copyright (C) KGy SOFT, 2005-2021 - All Rights Reserved +// Copyright (C) KGy SOFT, 2005-2022 - All Rights Reserved // // You should have received a copy of the LICENSE file at the top-level // directory of this distribution. @@ -55,7 +55,7 @@ internal int DeepPixelCount if (children == null) return result; - // Adding also the direct children because reducing the tree starts at level BPP - 2. + // Adding also the direct children because reducing the tree starts at level levelCount - 2. // And due to reducing no more than two levels can have non-empty nodes. for (int index = 0; index < 8; index++) { @@ -83,7 +83,7 @@ internal int DeepPixelCount internal OctreeNode(int level, OctreeQuantizer parent) { this.parent = parent; - Debug.Assert(level < parent.bpp); + Debug.Assert(level < parent.levelCount); if (level >= 0) parent.levels[level].Add(this); @@ -98,7 +98,7 @@ internal OctreeNode(int level, OctreeQuantizer parent) internal bool AddColor(Color32 color, int level) { // In the populating phase all colors are summed up in leaves at deepest level. - if (level == parent.bpp) + if (level == parent.levelCount) { sumRed += color.R; sumGreen += color.G; @@ -109,7 +109,7 @@ internal bool AddColor(Color32 color, int level) return pixelCount == 1; } - Debug.Assert(level < parent.bpp); + Debug.Assert(level < parent.levelCount); children ??= new OctreeNode[8]; // Generating a 0..7 index based on the color components and adding new branches on demand. @@ -238,7 +238,7 @@ private Color32 ToColor() private int maxColors; private int size; - private int bpp; + private int levelCount; private bool hasTransparency; [AllowNull]private List[] levels; @@ -257,14 +257,14 @@ private Color32 ToColor() #region Public Methods - public void Initialize(int requestedColors, IBitmapData source) + public void Initialize(int requestedColors, byte? bitLevel, IBitmapData source) { maxColors = requestedColors; size = source.Width * source.Height; - bpp = requestedColors.ToBitsPerPixel(); - levels = new List[bpp]; - for (int level = 0; level < bpp; level++) + levelCount = bitLevel ?? Math.Min(8, requestedColors.ToBitsPerPixel()); + levels = new List[levelCount]; + for (int level = 0; level < levelCount; level++) levels[level] = new List(); root = new OctreeNode(-1, this); @@ -314,7 +314,7 @@ public void Dispose() private void ReduceTree(IAsyncContext context) { // Scanning all levels towards root. Leaves are skipped (hence -2) because they are not reducible. - for (int level = bpp - 2; level >= 0; level--) + for (int level = levelCount - 2; level >= 0; level--) { if (levels[level].Count == 0) continue; diff --git a/KGySoft.Drawing/Drawing/Imaging/_Quantizers/OptimizedPaletteQuantizer.Wu.cs b/KGySoft.Drawing/Drawing/Imaging/_Quantizers/OptimizedPaletteQuantizer.Wu.cs index cde5711d..f2c9e89b 100644 --- a/KGySoft.Drawing/Drawing/Imaging/_Quantizers/OptimizedPaletteQuantizer.Wu.cs +++ b/KGySoft.Drawing/Drawing/Imaging/_Quantizers/OptimizedPaletteQuantizer.Wu.cs @@ -3,7 +3,7 @@ /////////////////////////////////////////////////////////////////////////////// // File: OptimizedPaletteQuantizer.Wu.cs /////////////////////////////////////////////////////////////////////////////// -// Copyright (C) KGy SOFT, 2005-2021 - All Rights Reserved +// Copyright (C) KGy SOFT, 2005-2022 - All Rights Reserved // // You should have received a copy of the LICENSE file at the top-level // directory of this distribution. @@ -17,6 +17,7 @@ using System; using System.Collections.Generic; +using System.Security; using KGySoft.Collections; @@ -27,7 +28,7 @@ namespace KGySoft.Drawing.Imaging public sealed partial class OptimizedPaletteQuantizer { /// - /// Credits to Xiaolin Wu's Color Quantizer published at https://www.ece.mcmaster.ca/~xwu/cq.c + /// Credit to Xiaolin Wu's Color Quantizer published at https://www.ece.mcmaster.ca/~xwu/cq.c /// This quantizer is mainly based on his code. /// private sealed class WuQuantizer : IOptimizedPaletteQuantizer @@ -158,13 +159,6 @@ internal long Top(Direction dir, int pos, ref Array3D mmt) #endregion - #region Constants - - private const int histCount = 33; - private const int histSize = 32; - - #endregion - #region Fields #region Static Fields @@ -179,6 +173,7 @@ internal long Top(Direction dir, int pos, ref Array3D mmt) #region Instance Fields private int maxColors; + private int histBitSize; /// /// The squared moment values of color RGB values. @@ -187,39 +182,39 @@ internal long Top(Direction dir, int pos, ref Array3D mmt) /// and after it contains cumulative moments. /// The strictly taken Bernoulli probability is actually multiplied by image size. /// but it does not matter here. - /// Effective histogram elements are in 1.. along each axis, + /// Effective histogram elements are in 1.. along each axis, /// element 0 is just for base or marginal value. /// Values are floats just because of the possible big ranges due to squared values. /// - private Array3D m2 = new Array3D(histCount, histCount, histCount); + private Array3D m2; /// /// The counts of voxels of the 3D color cubes in each position. /// The same applies as for except that after values are interpreted as /// wt[r, g, b] = sum over voxel of P(c) /// - private Array3D wt = new Array3D(histCount, histCount, histCount); + private Array3D wt; /// /// The moment values of red color components. /// The same applies as for except that after values are interpreted as /// wt[r, g, b] = sum over voxel of r*P(c) /// - private Array3D mr = new Array3D(histCount, histCount, histCount); + private Array3D mr; /// /// The moment values of green color components. /// The same applies as for except that after values are interpreted as /// wt[r, g, b] = sum over voxel of g*P(c) /// - private Array3D mg = new Array3D(histCount, histCount, histCount); + private Array3D mg; /// /// The moment values of green color components. /// The same applies as for except that after values are interpreted as /// wt[r, g, b] = sum over voxel of b*P(c) /// - private Array3D mb = new Array3D(histCount, histCount, histCount); + private Array3D mb; private bool hasTransparency; @@ -227,6 +222,13 @@ internal long Top(Direction dir, int pos, ref Array3D mmt) #endregion + #region Properties + + private int HistSize => 1 << histBitSize; + private int HistCount => HistSize + 1; + + #endregion + #region Methods #region Static Methods @@ -245,7 +247,18 @@ private static int[] InitSqrTable() #region Public Methods - public void Initialize(int requestedColors, IBitmapData source) => maxColors = requestedColors; + public void Initialize(int requestedColors, byte? bitLevel, IBitmapData source) + { + maxColors = requestedColors; + + // unless 8 bit is explicitly specified, not using more than 7 bit levels due to huge memory requirement + histBitSize = bitLevel ?? Math.Min(7, requestedColors.ToBitsPerPixel()); + m2 = new Array3D(HistCount, HistCount, HistCount); + wt = new Array3D(HistCount, HistCount, HistCount); + mr = new Array3D(HistCount, HistCount, HistCount); + mg = new Array3D(HistCount, HistCount, HistCount); + mb = new Array3D(HistCount, HistCount, HistCount); + } public void AddColor(Color32 c) { @@ -266,13 +279,12 @@ public void AddColor(Color32 c) // Actually each of these should be divided by 'size' to give the usual // interpretation of P() as ranging from 0 to 1, but we needn't do that here. - // We pre-quantize the color components to 5 bit to reduce the size of the 3D histogram. - int indR = (c.R >> 3) + 1; - int indG = (c.G >> 3) + 1; - int indB = (c.B >> 3) + 1; + // We pre-quantize the color components to histBitDepth bits (unless it is 8 bits) to reduce the size of the 3D histogram. + // Using a 1D index instead of [r + 1, g + 1, b + 1], which would use multiplication inside + int ind = GetFlattenIndex((c.R >> (8 - histBitSize)) + 1, + (c.G >> (8 - histBitSize)) + 1, + (c.B >> (8 - histBitSize)) + 1); - // instead of [indR, indG, indB], which would use multiplication inside - int ind = (indR << 10) + (indR << 6) + indR + (indG << 5) + indG + indB; wt.Buffer.GetElementReference(ind) += 1; mr.Buffer.GetElementReference(ind) += c.R; mg.Buffer.GetElementReference(ind) += c.G; @@ -280,6 +292,7 @@ public void AddColor(Color32 c) m2.Buffer.GetElementReference(ind) += sqrTable[c.R] + sqrTable[c.G] + sqrTable[c.B]; } + [SecuritySafeCritical] public Color32[]? GeneratePalette(IAsyncContext context) { // Original comment from Xiaolin Wu: @@ -325,31 +338,34 @@ public void Dispose() #region Private Methods + private int GetFlattenIndex(int indR, int indG, int indB) + => (indR << (histBitSize << 1)) + (indR << (histBitSize + 1)) + indR + (indG << histBitSize) + indG + indB; + /// /// Computing cumulative moments from the histogram. /// - private void HistogramToMoments() + [SecurityCritical] + private unsafe void HistogramToMoments() { - // Not using ArraySection for these because they are too small for pooling. - // We could use stackalloc but it's too negligible advantage for a fairly large stack allocation - long[] area = new long[histCount]; - long[] areaR = new long[histCount]; - long[] areaG = new long[histCount]; - long[] areaB = new long[histCount]; - float[] area2 = new float[histCount]; + long* area = stackalloc long[HistCount]; + long* areaR = stackalloc long[HistCount]; + long* areaG = stackalloc long[HistCount]; + long* areaB = stackalloc long[HistCount]; + float* area2 = stackalloc float[HistCount]; + // Getting the 1D view of the Array3D fields because we will repeatedly re-use the same indices ArraySection wtBuf = wt.Buffer; ArraySection mrBuf = mr.Buffer; ArraySection mgBuf = mg.Buffer; ArraySection mbBuf = mb.Buffer; ArraySection m2Buf = m2.Buffer; - for (int r = 1; r <= histSize; r++) + for (int r = 1; r <= HistSize; r++) { - for (int i = 0; i <= histSize; i++) + for (int i = 0; i <= HistSize; i++) area2[i] = area[i] = areaR[i] = areaG[i] = areaB[i] = 0; - for (int g = 1; g <= histSize; g++) + for (int g = 1; g <= HistSize; g++) { float line2 = 0f; long line = 0; @@ -357,10 +373,10 @@ private void HistogramToMoments() long lineG = 0; long lineB = 0; - for (int b = 1; b <= histSize; b++) + for (int b = 1; b <= HistSize; b++) { // instead of [r, g, b] - int ind1 = (r << 10) + (r << 6) + r + (g << 5) + g + b; + int ind1 = GetFlattenIndex(r, g, b); line += wtBuf[ind1]; lineR += mrBuf[ind1]; lineG += mgBuf[ind1]; @@ -373,8 +389,7 @@ private void HistogramToMoments() areaB[b] += lineB; area2[b] += line2; - // instead of [r-1, g, b] - int ind2 = ind1 - 1089; + int ind2 = GetFlattenIndex(r - 1, g, b); wtBuf[ind1] = wtBuf[ind2] + area[b]; mrBuf[ind1] = mrBuf[ind2] + areaR[b]; mgBuf[ind1] = mgBuf[ind2] + areaG[b]; @@ -392,7 +407,7 @@ private void HistogramToMoments() // Adding an initial item with largest possible size. We split it until we // have the needed colors or we cannot split further any of the boxes. - cubes.Add(new Box { RMax = histSize, GMax = histSize, BMax = histSize }); + cubes.Add(new Box { RMax = HistSize, GMax = HistSize, BMax = HistSize }); context.Progress?.New(DrawingOperation.GeneratingPalette, colorCount, 1); float[] vv = new float[colorCount]; @@ -522,11 +537,11 @@ private bool TryCut(Box set1, Box set2) long wholeW = set1.Volume(ref wt); float maxR = Maximize(set1, Direction.Red, set1.RMin + 1, set1.RMax, - out int cutR, wholeR, wholeG, wholeB, wholeW); + out int cutR, wholeR, wholeG, wholeB, wholeW); float maxG = Maximize(set1, Direction.Green, set1.GMin + 1, set1.GMax, - out int cutG, wholeR, wholeG, wholeB, wholeW); + out int cutG, wholeR, wholeG, wholeB, wholeW); float maxB = Maximize(set1, Direction.Blue, set1.BMin + 1, set1.BMax, - out int cutB, wholeR, wholeG, wholeB, wholeW); + out int cutB, wholeR, wholeG, wholeB, wholeW); Direction dir; if (maxR >= maxG && maxR >= maxB) diff --git a/KGySoft.Drawing/Drawing/Imaging/_Quantizers/OptimizedPaletteQuantizer.cs b/KGySoft.Drawing/Drawing/Imaging/_Quantizers/OptimizedPaletteQuantizer.cs index f7bfba3e..60127cc0 100644 --- a/KGySoft.Drawing/Drawing/Imaging/_Quantizers/OptimizedPaletteQuantizer.cs +++ b/KGySoft.Drawing/Drawing/Imaging/_Quantizers/OptimizedPaletteQuantizer.cs @@ -3,7 +3,7 @@ /////////////////////////////////////////////////////////////////////////////// // File: OptimizedPaletteQuantizer.cs /////////////////////////////////////////////////////////////////////////////// -// Copyright (C) KGy SOFT, 2005-2021 - All Rights Reserved +// Copyright (C) KGy SOFT, 2005-2022 - All Rights Reserved // // You should have received a copy of the LICENSE file at the top-level // directory of this distribution. @@ -151,7 +151,7 @@ private interface IOptimizedPaletteQuantizer : IDisposable { #region Methods - void Initialize(int requestedColors, IBitmapData source); + void Initialize(int requestedColors, byte? bitLevel, IBitmapData source); void AddColor(Color32 c); @@ -211,7 +211,7 @@ public Color32 GetQuantizedColor(Color32 origColor) private Palette? InitializePalette(IReadableBitmapData source, IAsyncContext context) { using var alg = new TAlg(); - alg.Initialize(quantizer.maxColors, source); + alg.Initialize(quantizer.maxColors, quantizer.bitLevel, source); int width = source.Width; IReadableBitmapDataRow row = source.FirstRow; context.Progress?.New(DrawingOperation.InitializingQuantizer, source.Height); @@ -220,7 +220,6 @@ public Color32 GetQuantizedColor(Color32 origColor) if (context.IsCancellationRequested) return null; - // TODO: parallel if possible for (int x = 0; x < width; x++) { Color32 c = row[x]; @@ -252,20 +251,24 @@ public Color32 GetQuantizedColor(Color32 origColor) private readonly Color32 backColor; private readonly byte alphaThreshold; private readonly Algorithm algorithm; + private readonly byte? bitLevel; #endregion #region Properties #region Public Properties - + /// - /// Gets a representing the lowest bits-per-pixel value format, which is compatible with this instance. + /// Gets a , which is compatible with this instance. /// - public PixelFormat PixelFormatHint - => maxColors > 16 ? PixelFormat.Format8bppIndexed - : maxColors > 2 ? PixelFormat.Format4bppIndexed - : PixelFormat.Format1bppIndexed; + public PixelFormat PixelFormatHint => maxColors switch + { + > 256 => alphaThreshold == 0 ? PixelFormat.Format24bppRgb : PixelFormat.Format32bppArgb, + > 16 => PixelFormat.Format8bppIndexed, + > 2 => PixelFormat.Format4bppIndexed, + _ => PixelFormat.Format1bppIndexed + }; #endregion @@ -281,14 +284,21 @@ public PixelFormat PixelFormatHint private OptimizedPaletteQuantizer(Algorithm algorithm, int maxColors, Color backColor, byte alphaThreshold) { - if (maxColors < 2 || maxColors > 256) - throw new ArgumentOutOfRangeException(nameof(maxColors), PublicResources.ArgumentMustBeBetween(2, 256)); + const int max = 1 << 16; + if (maxColors is < 2 or > max) + throw new ArgumentOutOfRangeException(nameof(maxColors), PublicResources.ArgumentMustBeBetween(2, max)); this.algorithm = algorithm; this.maxColors = maxColors; this.backColor = new Color32(backColor).ToOpaque(); this.alphaThreshold = alphaThreshold; } + private OptimizedPaletteQuantizer(OptimizedPaletteQuantizer original, byte? bitLevel) + : this(original.algorithm, original.maxColors, original.backColor.ToColor(), original.alphaThreshold) + { + this.bitLevel = bitLevel; + } + #endregion #region Methods @@ -410,24 +420,48 @@ public static OptimizedPaletteQuantizer Wu(int maxColors = 256, Color backColor #region Instance Methods + #region Public Methods + + /// + /// Configures the bit level per color channel to be used while optimizing the palette. + /// If the input image is a monochromatic one, then determines the bit depth of the result. + /// Affects the quality, speed and memory usage. + ///
See the Remarks section for details. + ///
+ /// Specifies the desired bit level. If , then the value is automatically set by the chosen algorithm. + /// must be either , or between 1 and 8. + /// + /// TODO: memory usage, default values, effect for each quantizers + /// + public OptimizedPaletteQuantizer ConfigureBitLevel(int? bitLevel) + { + if (this.bitLevel == bitLevel) + return this; + if (bitLevel is < 1 or > 8) + throw new ArgumentOutOfRangeException(nameof(bitLevel), PublicResources.ArgumentMustBeBetween(1, 8)); + return new OptimizedPaletteQuantizer(this, (byte?)bitLevel); + } + + #endregion + + #region Explicitly Implemented Interface Methods + IQuantizingSession IQuantizer.Initialize(IReadableBitmapData source, IAsyncContext? context) { context ??= AsyncContext.Null; - switch (algorithm) + return algorithm switch { - case Algorithm.Octree: - return new OptimizedPaletteQuantizerSession(this, source, context); - case Algorithm.MedianCut: - return new OptimizedPaletteQuantizerSession(this, source, context); - case Algorithm.Wu: - return new OptimizedPaletteQuantizerSession(this, source, context); - default: - throw new InvalidOperationException(Res.InternalError($"Unexpected algorithm: {algorithm}")); - } + Algorithm.Octree => new OptimizedPaletteQuantizerSession(this, source, context), + Algorithm.MedianCut => new OptimizedPaletteQuantizerSession(this, source, context), + Algorithm.Wu => new OptimizedPaletteQuantizerSession(this, source, context), + _ => throw new InvalidOperationException(Res.InternalError($"Unexpected algorithm: {algorithm}")) + }; } #endregion #endregion + + #endregion } } diff --git a/KGySoft.Drawing/Drawing/_Extensions/ImageExtensions.cs b/KGySoft.Drawing/Drawing/_Extensions/ImageExtensions.cs index 877abba2..21e841cc 100644 --- a/KGySoft.Drawing/Drawing/_Extensions/ImageExtensions.cs +++ b/KGySoft.Drawing/Drawing/_Extensions/ImageExtensions.cs @@ -2351,7 +2351,7 @@ private static void InitPalette(PixelFormat newPixelFormat, Bitmap source, Bitma // there is a desired palette to apply int maxColors = 1 << bpp; if (palette.Length > maxColors) - throw new ArgumentException(Res.ImagingPaletteTooLarge(maxColors, newPixelFormat), nameof(palette)); + throw new ArgumentException(Res.ImagingPaletteTooLarge(maxColors, bpp), nameof(palette)); target.TrySetPalette(palette); } diff --git a/KGySoft.Drawing/changelog.txt b/KGySoft.Drawing/changelog.txt index 7acc27fa..96c344e1 100644 --- a/KGySoft.Drawing/changelog.txt +++ b/KGySoft.Drawing/changelog.txt @@ -11,15 +11,21 @@ * KGySoft.Drawing.Imaging namespace =================================== -+ BitmapDataExtensions class: +* BitmapDataExtensions class: + New Clone/BeginClone/CloneAsync overloads to be able to clone a specific region without specifying pixel format. + * Improving Clear performance in some cases. + BitmapDataFactory class: + New CreateBitmapData overloads to create bitmap data for preallocated memory. Supporting 1D/2D arrays of any unmanaged element type (structs without references) as well as unmanaged memory and custom pixel formats. + New PixelFormatInfo struct to support custom pixel formats. + New ICustomBitmapDataRow/ICustomBitmapDataRow interfaces to support accessing rows with custom pixel formats. + PredefinedColorsQuantizer: - + FromBitmapData method: From now on it supports also custom pixel formats + + FromBitmapData method: From now on it supports also custom pixel formats. +* OptimizedPaletteQuantizer: + + Maximum colors is no longer limited to 256 colors. New limit is now 65536 colors. + + New ConfigureBitLevel method to adjust possible quality and memory usage. + * Improving MedianCut quantizer performance + * Improving default quality of Wu quantizer (7 bits per channel instead of 5, but adjustable by ConfigureBitLevel) * GifEncoder class: * The performance and memory usage of the LZW compression has been improved. - EncodeAnimation method: If quantizer is not set but the input image is already indexed, then not using the