public void DitherPerformanceTest(bool errorDiffusion)
        {
            using var bmpRef = Icons.Information.ExtractBitmap(new Size(256, 256));
            IQuantizer quantizer = PredefinedColorsQuantizer.SystemDefault8BppPalette();
            IDitherer  ditherer  = errorDiffusion ? (IDitherer)ErrorDiffusionDitherer.FloydSteinberg : OrderedDitherer.Bayer8x8;

            new PerformanceTest {
                TestName = $"{bmpRef.Width}x{bmpRef.Height}@{bmpRef.GetColorCount()} {(errorDiffusion ? "Error Diffusion" : "Ordered")}", Iterations = 100, CpuAffinity = null
            }
            .AddCase(() =>
            {
                using var result = bmpRef.CloneBitmap();
                result.Dither(quantizer, ditherer);
            }, "BitmapExtensions.Dither")
            .AddCase(() =>
            {
                using var result = bmpRef.CloneBitmap();
                using (IBitmapDataInternal bitmapData = BitmapDataFactory.CreateBitmapData(result, ImageLockMode.ReadWrite))
                    using (IQuantizingSession quantizingSession = quantizer.Initialize(bitmapData))
                        using (IDitheringSession ditheringSession = ditherer.Initialize(bitmapData, quantizingSession))
                        {
                            var row   = bitmapData.DoGetRow(0);
                            int width = bitmapData.Width;
                            do
                            {
                                for (int x = 0; x < width; x++)
                                {
                                    row.DoSetColor32(x, ditheringSession.GetDitheredColor(row.DoGetColor32(x), x, row.Index));
                                }
                            } while (row.MoveNextRow());
                        }
            }, "Sequential dithering")
            .DoTest()
            .DumpResults(Console.Out);
        }
        public void QuantizePerformanceTest()
        {
            //using var bmpRef = new Bitmap(@"D:\Letolt\MYSTY8RQER62.jpg");
            using var bmpRef = Icons.Information.ExtractBitmap(new Size(256, 256));
            IQuantizer quantizer = PredefinedColorsQuantizer.SystemDefault8BppPalette();

            new PerformanceTest {
                TestName = $"{bmpRef.Width}x{bmpRef.Height}@{bmpRef.GetColorCount()}", Iterations = 100, CpuAffinity = null
            }
            .AddCase(() =>
            {
                using var result = bmpRef.CloneBitmap();
                result.Quantize(quantizer);
            }, "BitmapExtensions.Quantize")
            .AddCase(() =>
            {
                using var result = bmpRef.CloneBitmap();
                using (IBitmapDataInternal bitmapData = BitmapDataFactory.CreateBitmapData(result, ImageLockMode.ReadWrite))
                    using (IQuantizingSession session = quantizer.Initialize(bitmapData))
                    {
                        var row   = bitmapData.DoGetRow(0);
                        int width = bitmapData.Width;
                        do
                        {
                            for (int x = 0; x < width; x++)
                            {
                                row.DoSetColor32(x, session.GetQuantizedColor(row.DoGetColor32(x)));
                            }
                        } while (row.MoveNextRow());
                    }
            }, "Sequential quantization")
            .DoTest()
            .DumpResults(Console.Out);
        }
Example #3
0
        internal void PerformDraw(IQuantizer?quantizer, IDitherer?ditherer)
        {
            if (quantizer == null)
            {
                PerformDrawDirect();
                return;
            }

            IReadableBitmapData initSource = SourceRectangle.Size == Source.GetSize()
                ? Source
                : Source.Clip(SourceRectangle);

            try
            {
                Debug.Assert(!quantizer.InitializeReliesOnContent || !Source.HasMultiLevelAlpha(), "This draw performs blending on-the-fly but the used quantizer would require two-pass processing");
                context.Progress?.New(DrawingOperation.InitializingQuantizer);
                using (IQuantizingSession quantizingSession = quantizer.Initialize(initSource, context))
                {
                    if (context.IsCancellationRequested)
                    {
                        return;
                    }
                    if (quantizingSession == null)
                    {
                        throw new InvalidOperationException(Res.ImagingQuantizerInitializeNull);
                    }

                    // quantizing without dithering
                    if (ditherer == null)
                    {
                        PerformDrawWithQuantizer(quantizingSession);
                        return;
                    }

                    // quantizing with dithering
                    Debug.Assert(!ditherer.InitializeReliesOnContent || !Source.HasMultiLevelAlpha(), "This draw performs blending on-the-fly but the used ditherer would require two-pass processing");

                    context.Progress?.New(DrawingOperation.InitializingDitherer);
                    using IDitheringSession ditheringSession = ditherer.Initialize(initSource, quantizingSession, context);
                    if (context.IsCancellationRequested)
                    {
                        return;
                    }
                    if (ditheringSession == null)
                    {
                        throw new InvalidOperationException(Res.ImagingDithererInitializeNull);
                    }

                    PerformDrawWithDithering(quantizingSession, ditheringSession);
                }
            }
            finally
            {
                if (!ReferenceEquals(initSource, Source))
                {
                    initSource.Dispose();
                }
            }
        }
Example #4
0
            internal InterleavedGradientNoiseDitheringSession(IQuantizingSession quantizingSession, InterleavedGradientNoiseDitherer ditherer)
                : base(quantizingSession)
            {
                if (ditherer.strength > 0f)
                {
                    Strength = ditherer.strength;
                    return;
                }

                CalibrateStrength(-127, 127);
            }
Example #5
0
            internal DitheringSessionRaster(IQuantizingSession quantizer, ErrorDiffusionDitherer ditherer, IBitmapData source)
            {
                this.quantizer = quantizer;
                this.ditherer  = ditherer;
                ImageWidth     = source.Width;
                imageHeight    = source.Height;
                byBrightness   = ditherer.byBrightness ?? quantizer.Palette?.IsGrayscale ?? false;

                // Initializing a circular buffer for the diffused errors.
                // This helps to minimize used memory because it needs only a few lines to be stored.
                // Another solution could be to store resulting colors instead of just the errors but then the color
                // entries would be clipped not just in the end but in every iteration, and small errors would be lost
                // that could stack up otherwise.
                // See also the ErrorDiffusionDitherer constructor for more comments on why using floats.
                errorsBuffer = new CircularList <(float, float, float)[]>(ditherer.matrixHeight);
Example #6
0
        internal static Color32 BlendOrMakeTransparent(this IQuantizingSession session, Color32 origColor)
        {
            // the color can be considered fully transparent
            if (origColor.A < session.AlphaThreshold)
            {
                // and even the quantizer returns a transparent color
                var c = session.GetQuantizedColor(origColor);
                if (c.A == 0)
                {
                    return(c);
                }
            }

            // the color will not be transparent in the end: blending
            return(origColor.BlendWithBackground(session.BackColor));
        }
            internal RandomNoiseDitheringSession(IQuantizingSession quantizingSession, RandomNoiseDitherer ditherer)
                : base(quantizingSession)
            {
                this.ditherer = ditherer;

                // If we have don't have a seed, we must use a thread safe random generator because pixels can be queried in any order
                random = ditherer.seed == null ? ThreadSafeRandom.Instance : new FastRandom(ditherer.seed.Value);

                if (ditherer.strength > 0f)
                {
                    Strength = ditherer.strength;
                    return;
                }

                CalibrateStrength(-127, 127);
            }
        public void ClearWithDitheringTest(PixelFormat pixelFormat, uint argb, bool errorDiffusion)
        {
            const int size     = 512;
            Color     color    = Color.FromArgb((int)argb);
            var       ditherer = errorDiffusion ? (IDitherer)ErrorDiffusionDitherer.FloydSteinberg : OrderedDitherer.Bayer8x8;

            new PerformanceTest {
                TestName = $"{pixelFormat} {size}x{size} {(errorDiffusion ? "Error Diffusion" : "Ordered Dithering")}", Iterations = 10, CpuAffinity = null
            }
            .AddCase(() =>
            {
                using var bmp = new Bitmap(size, size, pixelFormat);
                using IBitmapDataInternal acc = BitmapDataFactory.CreateBitmapData(bmp, ImageLockMode.ReadWrite);
                IQuantizer quantizer          = PredefinedColorsQuantizer.FromBitmapData(acc);
                var c = new Color32(color);
                using (IQuantizingSession quantizingSession = quantizer.Initialize(acc))
                    using (IDitheringSession ditheringSession = ditherer.Initialize(acc, quantizingSession))
                    {
                        IReadWriteBitmapDataRow row = acc.DoGetRow(0);
                        do
                        {
                            for (int x = 0; x < acc.Width; x++)
                            {
                                row[x] = ditheringSession.GetDitheredColor(c, x, row.Index);
                            }
                        } while (row.MoveNextRow());
                    }
            }, "Sequential clear")
            .AddCase(() =>
            {
                using var bmp = new Bitmap(size, size, pixelFormat);
                bmp.Clear(color, ditherer);
            }, "BitmapDataAccessor.Clear")
            .DoTest()
            .DumpResults(Console.Out);
        }
 internal NativeBitmapData(Bitmap bitmap, PixelFormat pixelFormat, ImageLockMode lockMode, IQuantizingSession quantizingSession)
     : base(bitmap, pixelFormat, lockMode, quantizingSession.BackColor, quantizingSession.AlphaThreshold, quantizingSession.Palette)
 {
 }
Example #10
0
 internal DitheringSessionSerpentine(IQuantizingSession quantizer, ErrorDiffusionDitherer ditherer, IReadableBitmapData source)
     : base(quantizer, ditherer, source)
 {
     this.source         = source;
     preprocessedResults = new Color32[ImageWidth];
 }
 protected VariableStrengthDitheringSessionBase(IQuantizingSession quantizingSession)
 {
     QuantizingSession = quantizingSession ?? throw new ArgumentNullException(nameof(quantizingSession), PublicResources.ArgumentNull);
 }
Example #12
0
        internal void PerformCopyWithQuantizer(IQuantizingSession quantizingSession, bool skipTransparent)
        {
            // Sequential processing
            if (SourceRectangle.Width < parallelThreshold >> quantizingScale)
            {
                context.Progress?.New(DrawingOperation.ProcessingPixels, SourceRectangle.Height);
                IBitmapDataRowInternal rowSrc = Source.DoGetRow(SourceRectangle.Y);
                IBitmapDataRowInternal rowDst = Target.DoGetRow(TargetRectangle.Y);
                byte alphaThreshold           = quantizingSession.AlphaThreshold;
                for (int y = 0; y < SourceRectangle.Height; y++)
                {
                    if (context.IsCancellationRequested)
                    {
                        return;
                    }
                    for (int x = 0; x < SourceRectangle.Width; x++)
                    {
                        Color32 colorSrc = rowSrc.DoGetColor32(x + SourceRectangle.X);
                        if (skipTransparent && colorSrc.A < alphaThreshold)
                        {
                            continue;
                        }

                        rowDst.DoSetColor32(x + TargetRectangle.X, quantizingSession.GetQuantizedColor(colorSrc));
                    }

                    rowSrc.MoveNextRow();
                    rowDst.MoveNextRow();
                    context.Progress?.Increment();
                }

                return;
            }

            IBitmapDataInternal source = Source;
            IBitmapDataInternal target = Target;
            Point sourceLocation       = SourceRectangle.Location;
            Point targetLocation       = TargetRectangle.Location;
            int   sourceWidth          = SourceRectangle.Width;

            ParallelHelper.For(context, DrawingOperation.ProcessingPixels, 0, SourceRectangle.Height, y =>
            {
                IQuantizingSession session    = quantizingSession;
                IBitmapDataRowInternal rowSrc = source.DoGetRow(sourceLocation.Y + y);
                IBitmapDataRowInternal rowDst = target.DoGetRow(targetLocation.Y + y);
                int offsetSrc       = sourceLocation.X;
                int offsetDst       = targetLocation.X;
                int width           = sourceWidth;
                byte alphaThreshold = session.AlphaThreshold;
                bool skip           = skipTransparent;
                for (int x = 0; x < width; x++)
                {
                    Color32 colorSrc = rowSrc.DoGetColor32(x + offsetSrc);
                    if (skip && colorSrc.A < alphaThreshold)
                    {
                        continue;
                    }

                    rowDst.DoSetColor32(x + offsetDst, session.GetQuantizedColor(colorSrc));
                }
            });
        }
Example #13
0
        private void PerformDrawWithDithering(IQuantizingSession quantizingSession, IDitheringSession ditheringSession)
        {
            IBitmapDataInternal source = Source;
            IBitmapDataInternal target = Target;
            Point sourceLocation       = SourceRectangle.Location;
            Point targetLocation       = TargetRectangle.Location;
            int   sourceWidth          = SourceRectangle.Width;

            // Sequential processing
            if (ditheringSession.IsSequential || SourceRectangle.Width < parallelThreshold >> ditheringScale)
            {
                context.Progress?.New(DrawingOperation.ProcessingPixels, SourceRectangle.Height);
                for (int y = 0; y < SourceRectangle.Height; y++)
                {
                    if (context.IsCancellationRequested)
                    {
                        return;
                    }
                    ProcessRow(y);
                    context.Progress?.Increment();
                }

                return;
            }

            // Parallel processing
            ParallelHelper.For(context, DrawingOperation.ProcessingPixels, 0, SourceRectangle.Height, ProcessRow);

            #region Local Methods

            void ProcessRow(int y)
            {
                IDitheringSession      session = ditheringSession;
                IBitmapDataRowInternal rowSrc  = source.DoGetRow(sourceLocation.Y + y);
                IBitmapDataRowInternal rowDst  = target.DoGetRow(targetLocation.Y + y);
                int  offsetSrc      = sourceLocation.X;
                int  offsetDst      = targetLocation.X;
                byte alphaThreshold = quantizingSession.AlphaThreshold;
                int  width          = sourceWidth;

                for (int x = 0; x < width; x++)
                {
                    Color32 colorSrc = rowSrc.DoGetColor32(x + offsetSrc);

                    // fully solid source: overwrite
                    if (colorSrc.A == Byte.MaxValue)
                    {
                        rowDst.DoSetColor32(x + offsetDst, session.GetDitheredColor(colorSrc, x, y));
                        continue;
                    }

                    // fully transparent source: skip
                    if (colorSrc.A == 0)
                    {
                        continue;
                    }

                    // source here has a partial transparency: we need to read the target color
                    int     pos      = x + offsetDst;
                    Color32 colorDst = rowDst.DoGetColor32(pos);

                    // non-transparent target: blending
                    if (colorDst.A != 0)
                    {
                        colorSrc = colorDst.A == Byte.MaxValue
                                   // target pixel is fully solid: simple blending
                            ? colorSrc.BlendWithBackground(colorDst)
                                   // both source and target pixels are partially transparent: complex blending
                            : colorSrc.BlendWith(colorDst);
                    }

                    // overwriting target color only if blended color has high enough alpha
                    if (colorSrc.A < alphaThreshold)
                    {
                        continue;
                    }

                    rowDst.DoSetColor32(pos, session.GetDitheredColor(colorSrc, x, y));
                }
            }

            #endregion
        }
Example #14
0
        internal void PerformCopyWithDithering(IQuantizingSession quantizingSession, IDitheringSession ditheringSession, bool skipTransparent)
        {
            // Sequential processing
            if (ditheringSession.IsSequential || SourceRectangle.Width < parallelThreshold >> ditheringScale)
            {
                context.Progress?.New(DrawingOperation.ProcessingPixels, SourceRectangle.Height);
                IBitmapDataRowInternal rowSrc = Source.DoGetRow(SourceRectangle.Y);
                IBitmapDataRowInternal rowDst = Target.DoGetRow(TargetRectangle.Y);
                byte alphaThreshold           = quantizingSession.AlphaThreshold;
                for (int y = 0; y < SourceRectangle.Height; y++)
                {
                    if (context.IsCancellationRequested)
                    {
                        return;
                    }

                    // we can pass x, y to the dithering session because if there is an offset it was initialized by a properly clipped rectangle
                    for (int x = 0; x < SourceRectangle.Width; x++)
                    {
                        Color32 colorSrc = rowSrc.DoGetColor32(x + SourceRectangle.X);
                        if (skipTransparent && colorSrc.A < alphaThreshold)
                        {
                            continue;
                        }

                        rowDst.DoSetColor32(x + TargetRectangle.X, ditheringSession.GetDitheredColor(colorSrc, x, y));
                    }

                    rowSrc.MoveNextRow();
                    rowDst.MoveNextRow();
                    context.Progress?.Increment();
                }

                return;
            }

            // Parallel processing
            IBitmapDataInternal source = Source;
            IBitmapDataInternal target = Target;
            Point sourceLocation       = SourceRectangle.Location;
            Point targetLocation       = TargetRectangle.Location;
            int   sourceWidth          = SourceRectangle.Width;

            ParallelHelper.For(context, DrawingOperation.ProcessingPixels, 0, SourceRectangle.Height, y =>
            {
                IDitheringSession session     = ditheringSession;
                IBitmapDataRowInternal rowSrc = source.DoGetRow(sourceLocation.Y + y);
                IBitmapDataRowInternal rowDst = target.DoGetRow(targetLocation.Y + y);
                int offsetSrc       = sourceLocation.X;
                int offsetDst       = targetLocation.X;
                int width           = sourceWidth;
                byte alphaThreshold = quantizingSession.AlphaThreshold;
                bool skip           = skipTransparent;

                // we can pass x, y to the dithering session because if there is an offset it was initialized by a properly clipped rectangle
                for (int x = 0; x < width; x++)
                {
                    Color32 colorSrc = rowSrc.DoGetColor32(x + offsetSrc);
                    if (skip && colorSrc.A < alphaThreshold)
                    {
                        continue;
                    }

                    rowDst.DoSetColor32(x + offsetDst, session.GetDitheredColor(colorSrc, x, y));
                }
            });
        }
 IDitheringSession IDitherer.Initialize(IReadableBitmapData source, IQuantizingSession quantizer, IAsyncContext?context)
 => new RandomNoiseDitheringSession(quantizer, this);
        /// <summary>
        /// Creates a native <see cref="IBitmapDataInternal"/> by a quantizer session re-using its palette if possible.
        /// </summary>
        internal static IBitmapDataInternal CreateBitmapData(Bitmap bitmap, ImageLockMode lockMode, IQuantizingSession quantizingSession)
        {
            if (bitmap == null)
            {
                throw new ArgumentNullException(nameof(bitmap), PublicResources.ArgumentNull);
            }

            var pixelFormat = bitmap.PixelFormat;

            if (!pixelFormat.IsIndexed() || quantizingSession.Palette == null)
            {
                return(CreateBitmapData(bitmap, lockMode, quantizingSession.BackColor, quantizingSession.AlphaThreshold));
            }

            // checking if bitmap and quantizer palette has the same entries
            if (!quantizingSession.Palette.Equals(bitmap.Palette.Entries))
            {
                return(CreateBitmapData(bitmap, lockMode, quantizingSession.BackColor, quantizingSession.AlphaThreshold));
            }

            if (!lockMode.IsDefined())
            {
                throw new ArgumentOutOfRangeException(nameof(lockMode), PublicResources.EnumOutOfRange(lockMode));
            }

            // here the quantizer and the target bitmap uses the same palette
            switch (pixelFormat)
            {
            case PixelFormat.Format8bppIndexed:
                return(new NativeBitmapData <NativeBitmapDataRow8I>(bitmap, pixelFormat, lockMode, quantizingSession));

            case PixelFormat.Format4bppIndexed:
                return(new NativeBitmapData <NativeBitmapDataRow4I>(bitmap, pixelFormat, lockMode, quantizingSession));

            case PixelFormat.Format1bppIndexed:
                return(new NativeBitmapData <NativeBitmapDataRow1I>(bitmap, pixelFormat, lockMode, quantizingSession));

            default:
                throw new InvalidOperationException(Res.InternalError($"Unexpected indexed format: {pixelFormat}"));
            }
        }