/// <summary> /// Creates a managed <see cref="IBitmapDataInternal"/> with the specified <paramref name="size"/> and <paramref name="pixelFormat"/>. /// </summary> internal static IBitmapDataInternal CreateManagedBitmapData(Size size, PixelFormat pixelFormat = PixelFormat.Format32bppArgb, Color32 backColor = default, byte alphaThreshold = 128, Palette?palette = null) { if (pixelFormat.IsIndexed() && palette != null) { int maxColors = 1 << pixelFormat.ToBitsPerPixel(); if (palette.Count > maxColors) { throw new ArgumentException(Res.ImagingPaletteTooLarge(maxColors, pixelFormat), nameof(palette)); } } Debug.Assert(palette == null || backColor.ToOpaque() == palette.BackColor && alphaThreshold == palette.AlphaThreshold); switch (pixelFormat) { case PixelFormat.Format32bppArgb: return(new ManagedBitmapData <Color32, ManagedBitmapDataRow32Argb>(size, pixelFormat)); case PixelFormat.Format32bppPArgb: return(new ManagedBitmapData <Color32, ManagedBitmapDataRow32PArgb>(size, pixelFormat)); case PixelFormat.Format32bppRgb: return(new ManagedBitmapData <Color32, ManagedBitmapDataRow32Rgb>(size, pixelFormat, backColor, alphaThreshold)); case PixelFormat.Format24bppRgb: return(new ManagedBitmapData <Color24, ManagedBitmapDataRow24Rgb>(size, pixelFormat, backColor, alphaThreshold)); case PixelFormat.Format8bppIndexed: return(new ManagedBitmapData <byte, ManagedBitmapDataRow8I>(size, pixelFormat, backColor, alphaThreshold, palette)); case PixelFormat.Format4bppIndexed: return(new ManagedBitmapData <byte, ManagedBitmapDataRow4I>(size, pixelFormat, backColor, alphaThreshold, palette)); case PixelFormat.Format1bppIndexed: return(new ManagedBitmapData <byte, ManagedBitmapDataRow1I>(size, pixelFormat, backColor, alphaThreshold, palette)); case PixelFormat.Format64bppArgb: return(new ManagedBitmapData <Color64, ManagedBitmapDataRow64Argb>(size, pixelFormat)); case PixelFormat.Format64bppPArgb: return(new ManagedBitmapData <Color64, ManagedBitmapDataRow64PArgb>(size, pixelFormat)); case PixelFormat.Format48bppRgb: return(new ManagedBitmapData <Color48, ManagedBitmapDataRow48Rgb>(size, pixelFormat, backColor, alphaThreshold)); case PixelFormat.Format16bppRgb565: return(new ManagedBitmapData <Color16Rgb565, ManagedBitmapDataRow16Rgb565>(size, pixelFormat, backColor, alphaThreshold)); case PixelFormat.Format16bppRgb555: return(new ManagedBitmapData <Color16Rgb555, ManagedBitmapDataRow16Rgb555>(size, pixelFormat, backColor, alphaThreshold)); case PixelFormat.Format16bppArgb1555: return(new ManagedBitmapData <Color16Argb1555, ManagedBitmapDataRow16Argb1555>(size, pixelFormat, backColor, alphaThreshold)); case PixelFormat.Format16bppGrayScale: return(new ManagedBitmapData <Color16Gray, ManagedBitmapDataRow16Gray>(size, pixelFormat, backColor, alphaThreshold)); default: throw new ArgumentOutOfRangeException(nameof(pixelFormat), Res.PixelFormatInvalid(pixelFormat)); } }
protected NativeBitmapDataBase(Bitmap bitmap, PixelFormat pixelFormat, ImageLockMode lockMode, Color32 backColor, byte alphaThreshold, Palette?palette) { // It must be the same as bitmap format except if LockBits is not supported with the original pixel format (occurs on Linux). Debug.Assert(bitmap.PixelFormat == pixelFormat || !OSUtils.IsWindows, "Unmatching pixel format"); // If palette is passed it must match with actual palette Debug.Assert(palette == null || palette.Equals(bitmap.Palette.Entries), "Unmatching palette"); this.bitmap = bitmap; BackColor = pixelFormat.HasMultiLevelAlpha() ? default : backColor.ToOpaque(); AlphaThreshold = alphaThreshold; bitmapData = bitmap.LockBits(new Rectangle(Point.Empty, bitmap.Size), lockMode, pixelFormat); RowSize = Math.Abs(bitmapData.Stride); if (pixelFormat.IsIndexed()) { Palette = palette ?? new Palette(bitmap.Palette.Entries, backColor.ToColor(), alphaThreshold); } }
internal ManagedBitmapData(Size size, PixelFormat pixelFormat, Color32 backColor = default, byte alphaThreshold = 0, Palette?palette = null) { Debug.Assert(size.Width > 0 && size.Height > 0, "Non-empty size expected"); Debug.Assert(pixelFormat.IsValidFormat(), "Valid format expected"); Debug.Assert(!pixelFormat.IsIndexed() || typeof(TColor) == typeof(byte), "For indexed pixel formats byte elements are expected"); BackColor = pixelFormat.HasMultiLevelAlpha() ? default : backColor.ToOpaque(); AlphaThreshold = alphaThreshold; PixelFormat = pixelFormat; Width = size.Width; // Unlike native bitmaps our stride have 1 byte alignment so Stride = (Width * bpp + 7) / 8) int bpp = pixelFormat.ToBitsPerPixel(); int byteWidth = pixelFormat.GetByteWidth(size.Width); RowSize = byteWidth; Buffer = new Array2D <TColor>(size.Height, bpp <= 8 ? byteWidth : size.Width); if (!pixelFormat.IsIndexed()) { return; } if (palette != null) { Debug.Assert(palette.Entries.Length <= (1 << bpp), "Too many colors"); Palette = palette; return; } // if there was no palette specified we use a default one Palette = pixelFormat switch { PixelFormat.Format8bppIndexed => Palette.SystemDefault8BppPalette(backColor, alphaThreshold), PixelFormat.Format4bppIndexed => Palette.SystemDefault4BppPalette(backColor), _ => Palette.SystemDefault1BppPalette(backColor) }; }
/// <summary> /// Creates a native <see cref="IBitmapDataInternal"/> from a <see cref="Bitmap"/>. /// </summary> internal static IBitmapDataInternal CreateBitmapData(Bitmap bitmap, ImageLockMode lockMode, Color32 backColor = default, byte alphaThreshold = 128, Palette?palette = null) { if (bitmap == null) { throw new ArgumentNullException(nameof(bitmap), PublicResources.ArgumentNull); } if (!lockMode.IsDefined()) { throw new ArgumentOutOfRangeException(nameof(lockMode), PublicResources.EnumOutOfRange(lockMode)); } Debug.Assert(palette == null || backColor.ToOpaque() == palette.BackColor && alphaThreshold == palette.AlphaThreshold); var pixelFormat = bitmap.PixelFormat; switch (pixelFormat) { case PixelFormat.Format32bppArgb: return(new NativeBitmapData <NativeBitmapDataRow32Argb>(bitmap, pixelFormat, lockMode)); case PixelFormat.Format32bppPArgb: return(new NativeBitmapData <NativeBitmapDataRow32PArgb>(bitmap, pixelFormat, lockMode)); case PixelFormat.Format32bppRgb: return(new NativeBitmapData <NativeBitmapDataRow32Rgb>(bitmap, pixelFormat, lockMode, backColor, alphaThreshold)); case PixelFormat.Format24bppRgb: return(new NativeBitmapData <NativeBitmapDataRow24Rgb>(bitmap, pixelFormat, lockMode, backColor, alphaThreshold)); case PixelFormat.Format8bppIndexed: return(new NativeBitmapData <NativeBitmapDataRow8I>(bitmap, pixelFormat, lockMode, backColor, alphaThreshold, palette)); case PixelFormat.Format4bppIndexed: return(new NativeBitmapData <NativeBitmapDataRow4I>(bitmap, pixelFormat, lockMode, backColor, alphaThreshold, palette)); case PixelFormat.Format1bppIndexed: return(new NativeBitmapData <NativeBitmapDataRow1I>(bitmap, pixelFormat, lockMode, backColor, alphaThreshold, palette)); case PixelFormat.Format64bppArgb: return(new NativeBitmapData <NativeBitmapDataRow64Argb>(bitmap, pixelFormat, lockMode)); case PixelFormat.Format64bppPArgb: return(new NativeBitmapData <NativeBitmapDataRow64PArgb>(bitmap, pixelFormat, lockMode)); case PixelFormat.Format48bppRgb: return(new NativeBitmapData <NativeBitmapDataRow48Rgb>(bitmap, pixelFormat, lockMode, backColor, alphaThreshold)); case PixelFormat.Format16bppRgb565: return(OSUtils.IsWindows ? new NativeBitmapData <NativeBitmapDataRow16Rgb565>(bitmap, pixelFormat, lockMode, backColor, alphaThreshold) : new NativeBitmapData <NativeBitmapDataRow16Rgb565Via24Bpp>(bitmap, PixelFormat.Format24bppRgb, lockMode, backColor, alphaThreshold)); case PixelFormat.Format16bppRgb555: return(OSUtils.IsWindows ? new NativeBitmapData <NativeBitmapDataRow16Rgb555>(bitmap, pixelFormat, lockMode, backColor, alphaThreshold) : new NativeBitmapData <NativeBitmapDataRow16Rgb555Via24Bpp>(bitmap, PixelFormat.Format24bppRgb, lockMode, backColor, alphaThreshold)); case PixelFormat.Format16bppArgb1555: return(new NativeBitmapData <NativeBitmapDataRow16Argb1555>(bitmap, pixelFormat, lockMode, backColor, alphaThreshold)); case PixelFormat.Format16bppGrayScale: return(new NativeBitmapData <NativeBitmapDataRow16Gray>(bitmap, pixelFormat, lockMode, backColor, alphaThreshold)); default: throw new InvalidOperationException(Res.InternalError($"Unexpected pixel format {pixelFormat}")); } }
/// <summary> /// Initializes a new instance of the <see cref="Palette"/> class. /// <br/>See the <strong>Remarks</strong> section of the <see cref="Palette"/> class for details. /// </summary> /// <param name="entries">The color entries to be stored by this <see cref="Palette"/> instance.</param> /// <param name="backColor">Specifies the background color for lookup operations (<see cref="GetNearestColor">GetNearestColor</see>, <see cref="GetNearestColorIndex">GetNearestColorIndex</see>). /// When a lookup is performed with a color, whose <see cref="Color32.A">Color32.A</see> field is equal to or greater than <paramref name="alphaThreshold"/>, and there is no exact match among the <paramref name="entries"/>, /// then the color to be found will be blended with this color before performing the lookup. The <see cref="Color32.A">Color32.A</see> field of the background color is ignored. This parameter is optional. /// <br/>Default value: The default value of the <see cref="Color32"/> type, which has the same RGB values as <see cref="Color.Black"/>.</param> /// <param name="alphaThreshold">If there is at least one completely transparent color among <paramref name="entries"/>, /// then specifies a threshold value for the <see cref="Color32.A">Color32.A</see> field, under which lookup operations will return the first transparent color (<see cref="GetNearestColor">GetNearestColor</see>) /// or the index of the first transparent color (<see cref="GetNearestColorIndex">GetNearestColorIndex</see>). This parameter is optional. /// <br/>Default value: <c>128</c>.</param> /// <param name="customGetNearestColorIndex">A delegate specifying an optional custom lookup logic to obtain an index from <paramref name="entries"/> by a <see cref="Color32"/> instance. /// If specified, it must be thread-safe and it is expected to be fast. The results returned by the specified delegate are not cached. If <see langword="null"/>, /// then <see cref="GetNearestColor">GetNearestColor</see>, <see cref="GetNearestColorIndex">GetNearestColorIndex</see> will perform a sequential lookup by using a default logic and results will be cached. This parameter is optional. /// <br/>Default value: <see langword="null"/>.</param> /// <exception cref="ArgumentNullException"><paramref name="entries"/> must not be <see langword="null"/>.</exception> /// <exception cref="ArgumentException"><paramref name="entries"/> must not be empty.</exception> public Palette(Color32[] entries, Color32 backColor = default, byte alphaThreshold = 128, Func <Color32, int> customGetNearestColorIndex = null) { Entries = entries ?? throw new ArgumentNullException(nameof(entries), PublicResources.ArgumentNull); if (entries.Length == 0) { throw new ArgumentException(PublicResources.ArgumentEmpty, nameof(entries)); } BackColor = backColor.ToOpaque(); AlphaThreshold = alphaThreshold; // initializing color32ToIndex, which is the 1st level of caching color32ToIndex = new Dictionary <Color32, int>(entries.Length); IsGrayscale = true; for (int i = 0; i < entries.Length; i++) { Color32 c = entries[i]; if (!color32ToIndex.ContainsKey(c) && !(AlphaThreshold == 0 && c.A == 0)) { color32ToIndex[c] = i; } if (c.A != Byte.MaxValue) { HasAlpha = true; HasMultiLevelAlpha = c.A > 0; } if (c.A == 0) { if (transparentIndex < 0) { transparentIndex = i; } continue; } if (IsGrayscale) { IsGrayscale = c.R == c.G && c.R == c.B; } } this.customGetNearestColorIndex = customGetNearestColorIndex; if (customGetNearestColorIndex != null) { return; } // Caching results is a problem because a true color image can have millions of colors. // In order not to run out of memory we need to limit the cache size but that is another problem because: // - ConcurrentDictionary is actually quite slow in itself and its Count is ridiculously expensive, too. // Using it without a size limit (so we don't need to read Count) ends up consuming way too much memory. // - Cache.GetThreadSafeAccessor uses lock and if colors are never the same caching is nothing but an additional cost. // Conclusion: We use two levels of caching (actually 3 with color32ToIndex) where there is a lock-free first level // cache and a second level used with locking. Only the locking cache is expanded continuously, which is regularly // copied to the lock-free cache if elements count reaches a limit. This makes lookups significantly faster even // with single-core processing. We use simple Dictionary instances. Not even a Cache because we handle both capacity and // expansion explicitly. Even the most commonly used elements are irrelevant because we copy the cache before clearing it. syncRoot = new object(); }