/// <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);
                        }
        }
Example #3
0
        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}"));
            }
        }
Example #5
0
        /// <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();
        }