Beispiel #1
0
        private Bitmap GenerateBitmap(Bitmap bitmap, IDitherer ditherer)
        {
            Rectangle bounds = Rectangle.FromLTRB(0, 0, bitmap.Width, bitmap.Height);

            // Lock source bits for reading
            BitmapData sourceData = bitmap.LockBits(bounds, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);

            // Lock target bits for writing
            Bitmap     canvas     = new Bitmap(bitmap.Width, bitmap.Height, PixelFormat.Format8bppIndexed);
            BitmapData targetData = canvas.LockBits(bounds, ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);

            // Copy the palette to the target bitmap
            CopyPalette(canvas);

            try
            {
                StartWritingProcess(bitmap.Height, bitmap.Width, sourceData.Scan0.ToInt64(), targetData.Scan0.ToInt64(), sourceData.Stride, targetData.Stride, ditherer);
            }

            finally
            {
                bitmap.UnlockBits(sourceData);
                canvas.UnlockBits(targetData);
            }

            return(canvas);
        }
        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);
        }
Beispiel #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();
                }
            }
        }
Beispiel #4
0
        /// <summary>
        /// Returns an array of bytes to be sent to the /dev/usb/lp0 device. Will rotate and scale the image before dithering.
        /// </summary>
        /// <param name="inputImage"></param>
        /// <param name="ditherer">The ditherer to be used. Use new BurkesDitherer() if unsure.</param>
        /// <param name="rotateForLargerPrint">If true, the image will be rotated 90 degrees if that would increase the printed size.</param>
        /// <returns></returns>
        public byte[] ImageToPrintCommands(Bitmap inputImage, IDitherer ditherer, bool rotateForLargerPrint = true)
        {
            var     resized = ScaleToFitPage(inputImage, rotateForLargerPrint);
            BWImage result  = ditherer.GetBWImage(resized);

            byte[] printCommands = RasterToPrintCommands(result);
            return(printCommands);
        }
Beispiel #5
0
        /// <summary>
        /// Draws a bitmap while applying dithering and/or quantization
        /// </summary>
        /// <returns>the altered bitmap</returns>
        public override Bitmap Draw()
        {
            if (!imageStore.QuantizerReady)
            {
                throw new Exception("The Quantizer was not ready yet.");
            }
            TotalError = 0;
            IDitherer  ditherer = imageStore.Ditherer;
            Bitmap     canvas;
            BitmapData sourceData;
            BitmapData targetData;
            int        width;
            int        height;

            lock (imageStore.Image)
            {
                width  = imageStore.Image.Width;
                height = imageStore.Image.Height;

                Rectangle bounds = Rectangle.FromLTRB(0, 0, imageStore.Image.Width, imageStore.Image.Height);

                // Lock source bits for reading
                sourceData = imageStore.Image.LockBits(bounds, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);

                // Lock target bits for writing
                canvas     = new Bitmap(imageStore.Image.Width, imageStore.Image.Height, PixelFormat.Format8bppIndexed);
                targetData = canvas.LockBits(bounds, ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
            }

            // Copy the palette to the target bitmap
            CopyPalette(canvas);

            try
            {
                long           total            = width * height;
                Models.Color[] ditherDistortion = new Models.Color[total];

                LoopRows(width, height, sourceData, targetData, ditherDistortion, ditherer);

                AverageError = TotalError / total;
            }

            finally
            {
                lock (imageStore.Image)
                {
                    imageStore.Image.UnlockBits(sourceData);
                }
                canvas.UnlockBits(targetData);
            }

            return(canvas);
        }
Beispiel #6
0
        /// <summary>
        /// Draws a bitmap while applying dithering and/or quantization
        /// </summary>
        /// <returns>the altered bitmap</returns>
        public override Bitmap Draw()
        {
            if (!imageStore.QuantizerReady)
            {
                throw new Exception("The Quantizer was not ready yet.");
            }

            TotalError = 0;
            IDitherer ditherer = imageStore.Ditherer;

            // Lock the image for thread-safeness
            lock (imageStore.Image)
            {
                return(GenerateBitmap(imageStore.Image, ditherer));
            }
        }
        private void ButtonLoadImage_Click(object sender, EventArgs e)
        {
            if (OpenFileDialogImageLoader.ShowDialog() == DialogResult.OK)
            {
                ButtonSaveImage.Enabled = false;
                Quantizer quantizer = GetQuantizer();
                IDitherer ditherer  = GetDitherer();

                ProgressBarQuantization.Value = 0;
                ImagePath      = OpenFileDialogImageLoader.FileName;
                LabelPath.Text = ImagePath;
                var image = new Bitmap(ImagePath);
                PictureBoxLoadedImage.Image = image;

                imageStore = new ImageStore(image, quantizer, ditherer);
                drawer     = GetDrawer(imageStore);
                imageStore.InitFinished += AfterInit;
                drawer.ProgressUpdate   += ProgressUpdate;
            }
        }
Beispiel #8
0
        /// <summary>
        /// Generate a task that writes to the target bitmap
        /// </summary>
        /// <param name="sourceOffset">the offset to the source bitmap in memory</param>
        /// <param name="targetOffset">the offset to the target bitmap in memory</param>
        /// <param name="width">the width of the image</param>
        /// <param name="height">the height of the image</param>
        /// <param name="row">the row this task has to process</param>
        /// <param name="behind">the behind constant of the ditherer</param>
        /// <param name="progress">the progress array, this will get updated as the task progresses, and is used for checking if this task can continue</param>
        /// <param name="ditherDistortion">the dither distortion overlay, this will be edited by this task</param>
        /// <param name="ditherer">the ditherer to be used</param>
        /// <returns>the task</returns>
        private Task GenerateTask(long sourceOffset, long targetOffset, int width, int height, int row, int behind, int[] progress, Models.Color[] ditherDistortion, IDitherer ditherer)
        {
            // Re initialize the source and target rows
            byte[] targetLine = new byte[width];
            int[]  sourceLine = new int[width];
            var    id         = row;


            Task t = new Task(() =>
            {
                Marshal.Copy(new IntPtr(sourceOffset), sourceLine, 0, width);

                for (int index = 0; index < width; index++)
                {
                    if (id != 0)
                    {
                        // Check if the task processing the row above ours is done dithering the pixel this task is processing
                        while (index > (progress[id - 1] - behind))
                        {
                            System.Threading.Thread.Sleep(1);
                        }
                    }
                    // Read the color from the source image
                    Color color = Color.FromArgb(sourceLine[index]);
                    // Add the dithering to the pixel
                    var colorAsColor = new Models.Color(color) + ditherDistortion[index + id * width];
                    // Get the index of the color closest to the dithered pixel
                    targetLine[index] = (byte)imageStore.Quantizer.GetPaletteIndex(colorAsColor);
                    // Get the distance to dither the other pixels!
                    var distance = System.Math.Sqrt(Util.Math.Distance(new Models.Color(color), imageStore.Quantizer.GetColorByIndex(targetLine[index])));
                    ditherer.Dither(colorAsColor, imageStore.Quantizer.GetColorByIndex(targetLine[index]), ditherDistortion, index, id, width, height);
                    TotalError += distance;
                    // Free up memory by deleting this entry in the dither distortion matrix
                    ditherDistortion[index + id * width] = null;
                    progress[id]++;
                }
                // Increase this significantly so task below this can get to the end
                progress[id] += width;

                Marshal.Copy(targetLine, 0, new IntPtr(targetOffset), width);
                // Update progress
                completed++;

                lock (ProgressUpdate)
                {
                    ProgressUpdate?.Invoke(this, new ProgressEventArgs((int)(completed / (float)height * 100)));
                }
            });

            return(t);
        }
Beispiel #9
0
        /// <summary>
        /// Spawns tasks that will each write a line to the target bitmap
        /// </summary>
        /// <param name="height">the height of the bitmap</param>
        /// <param name="width">the width of the bitmap</param>
        /// <param name="sourceOffset">the offset of the source bitmap in memory</param>
        /// <param name="targetOffset">the offset of the target bitmap in memory</param>
        /// <param name="sourceStride">the stride of the source bitmap in memory</param>
        /// <param name="targetStride">the stride of the trarget bitmap in memory</param>
        /// <param name="ditherer">the ditherer to be used</param>
        /// <param name="behind">how many pixels to the left of the currently processed pixel the ditherer writes distortion to</param>
        /// <returns>A list of writing tasks</returns>
        private Task[] GetTasks(int height, int width, long sourceOffset, long targetOffset, long sourceStride, int targetStride, IDitherer ditherer, int behind)
        {
            Models.Color[] ditherDistortion = new Models.Color[width * height];

            // Keeps track of the progress of every task, so they can know when to start
            // since they have to leave enough time so they won't quantize before dithering is finished
            // this is what behind is used for
            int[]  progress = new int[height];
            Task[] tasks    = new Task[height];
            completed = 0;
            for (int row = 0; row < height; row++)
            {
                Task t = GenerateTask(sourceOffset, targetOffset, width, height, row, behind, progress, ditherDistortion, ditherer);

                tasks[row] = t;
                // Set ConfigureAwait to false, to avoid deadlocking
                t.ConfigureAwait(false);
                t.Start();
                sourceOffset += sourceStride;
                targetOffset += targetStride;
            }
            return(tasks);
        }
Beispiel #10
0
        public void ConvertPixelFormatCustomTest(string testName, PixelFormat pixelFormat, IQuantizer quantizer, IDitherer ditherer)
        {
            if (!pixelFormat.IsSupportedNatively())
            {
                Assert.Inconclusive($"Pixel format is not supported: {pixelFormat}");
            }

            using var source    = Icons.Information.ExtractBitmap(new Size(256, 256));
            using var converted = source.ConvertPixelFormat(pixelFormat, quantizer, ditherer);
            Assert.AreEqual(pixelFormat, converted.PixelFormat);
            SaveImage(testName, converted);
        }
Beispiel #11
0
 public Quantizer(Bitmap image, IDitherer ditherer)
 {
     this.image    = image;
     this.ditherer = ditherer;
 }
Beispiel #12
0
 /// <summary>
 /// Clears the content of the specified <paramref name="bitmapData"/> and fills it with the specified <paramref name="color"/>.
 /// <br/>This method is similar to <see cref="Graphics.Clear">Graphics.Clear</see> except that this one supports any <see cref="PixelFormat"/> and also dithering.
 /// </summary>
 /// <param name="bitmapData">The <see cref="IWritableBitmapData"/> to be cleared.</param>
 /// <param name="color">A <see cref="Color32"/> that represents the desired result color of the <paramref name="bitmapData"/>.
 /// If it has transparency, which is not supported by <see cref="IBitmapData.PixelFormat"/> of <paramref name="bitmapData"/>, then the result might be either
 /// completely transparent (depends also on <see cref="IBitmapData.AlphaThreshold"/>), or the color will be blended with <see cref="IBitmapData.BackColor"/>.
 /// </param>
 /// <param name="ditherer">The ditherer to be used for the clearing. Has no effect if <see cref="IBitmapData.PixelFormat"/> of <paramref name="bitmapData"/> has at least 24 bits-per-pixel size. This parameter is optional.
 /// <br/>Default value: <see langword="null"/>.</param>
 /// <remarks>
 /// <note>This method adjusts the degree of parallelization automatically, blocks the caller, and does not support cancellation or reporting progress. Use the <see cref="BeginClear">BeginClear</see>
 /// or <see cref="ClearAsync">ClearAsync</see> (in .NET 4.0 and above) methods for asynchronous call and to adjust parallelization, set up cancellation and for reporting progress.</note>
 /// </remarks>
 /// <seealso cref="BitmapExtensions.Clear(Bitmap, Color, IDitherer, Color, byte)"/>
 public static void Clear(this IWritableBitmapData bitmapData, Color32 color, IDitherer ditherer = null)
 {
     if (bitmapData == null)
         throw new ArgumentNullException(nameof(bitmapData), PublicResources.ArgumentNull);
     DoClear(AsyncContext.Null, bitmapData, color, ditherer);
 }
Beispiel #13
0
 /// <summary>
 /// Process a line from the bitmap image
 /// </summary>
 /// <param name="width">the width of the image</param>
 /// <param name="height">the height of the image</param>
 /// <param name="row">the current row being processed</param>
 /// <param name="ditherDistortion">the dither distortion overlay</param>
 /// <param name="sourceLine">the source row of pixels</param>
 /// <param name="targetLine">the target row of pixels</param>
 /// <param name="ditherer">the ditherer to use</param>
 private void GenerateLine(int width, int height, int row, Models.Color[] ditherDistortion, int[] sourceLine, byte[] targetLine, IDitherer ditherer)
 {
     for (int index = 0; index < width; index++)
     {
         // Read the color from the source image
         Color color = Color.FromArgb(sourceLine[index]);
         // Add the dithering to the pixel
         var colorAsColor = new Models.Color(color) + ditherDistortion[index + row * width];
         // Get the index of the color closest to the dithered pixel
         targetLine[index] = (byte)imageStore.Quantizer.GetPaletteIndex(colorAsColor);
         // Get the distance to dither the other pixels!
         var distance = System.Math.Sqrt(Util.Math.Distance(new Models.Color(color), imageStore.Quantizer.GetColorByIndex(targetLine[index])));
         ditherer.Dither(colorAsColor, imageStore.Quantizer.GetColorByIndex(targetLine[index]), ditherDistortion, index, row, width, height);
         TotalError += distance;
     }
 }
Beispiel #14
0
        /// <summary>
        /// Creates tasks for writing to pixels and calculates the average error
        /// </summary>
        /// <param name="height">the height of the bitmap</param>
        /// <param name="width">the width of the bitmap</param>
        /// <param name="sourceOffset">the offset of the source bitmap in memory</param>
        /// <param name="targetOffset">the offset of the target bitmap in memory</param>
        /// <param name="sourceStride">the stride of the source bitmap in memory</param>
        /// <param name="targetStride">the stride of the trarget bitmap in memory</param>
        /// <param name="ditherer">the ditherer to be used</param>
        private void StartWritingProcess(int height, int width, long sourceOffset, long targetOffset, int sourceStride, int targetStride, IDitherer ditherer)
        {
            int behind = imageStore.Ditherer.GetBehind() + 1;

            var tasks = GetTasks(height, width, sourceOffset, targetOffset, sourceStride, targetStride, ditherer, behind);

            Task.WaitAll(tasks);
            lock (ProgressUpdate)
            {
                ProgressUpdate?.Invoke(this, new ProgressEventArgs(100));
            }
            AverageError = TotalError / (width * height);
        }
 /// <summary>
 /// Constructor
 /// </summary>
 /// <param name="image">the image this class is storing</param>
 /// <param name="quantizer">the quantizer that will be used</param>
 /// <param name="ditherer">the ditherer that will be used</param>
 public ImageStore(Bitmap image, IQuantizer quantizer, IDitherer ditherer)
 {
     Image     = Cloner.DeepClone(image);
     Quantizer = quantizer;
     Ditherer  = ditherer;
 }
Beispiel #16
0
 internal GenerateTask(PixelFormat pixelFormat, IQuantizer quantizer, IDitherer ditherer)
 {
     PixelFormat = pixelFormat;
     Quantizer   = quantizer;
     Ditherer    = ditherer;
 }
Beispiel #17
0
 /// <summary>
 /// Begins to clear the content of the specified <paramref name="bitmapData"/> and fills it with the specified <paramref name="color"/> asynchronously.
 /// <br/>This method is similar to <see cref="Graphics.Clear">Graphics.Clear</see> except that this one supports any <see cref="PixelFormat"/> and also dithering.
 /// </summary>
 /// <param name="bitmapData">The <see cref="IWritableBitmapData"/> to be cleared.</param>
 /// <param name="color">A <see cref="Color32"/> that represents the desired result color of the <paramref name="bitmapData"/>.
 /// If it has transparency, which is not supported by <see cref="IBitmapData.PixelFormat"/> of <paramref name="bitmapData"/>, then the result might be either
 /// completely transparent (depends also on <see cref="IBitmapData.AlphaThreshold"/>), or the color will be blended with <see cref="IBitmapData.BackColor"/>.
 /// </param>
 /// <param name="ditherer">The ditherer to be used for the clearing. Has no effect if <see cref="IBitmapData.PixelFormat"/> of <paramref name="bitmapData"/> has at least 24 bits-per-pixel size. This parameter is optional.
 /// <br/>Default value: <see langword="null"/>.</param>
 /// <param name="asyncConfig">The configuration of the asynchronous operation such as parallelization, cancellation, reporting progress, etc. This parameter is optional.
 /// <br/>Default value: <see langword="null"/>.</param>
 /// <returns>A <see cref="Task"/> that represents the asynchronous operation, which could still be pending.</returns>
 /// <remarks>
 /// <para>This method is not a blocking call even if the <see cref="AsyncConfigBase.MaxDegreeOfParallelism"/> property of the <paramref name="asyncConfig"/> parameter is 1.</para>
 /// </remarks>
 public static Task ClearAsync(this IWritableBitmapData bitmapData, Color32 color, IDitherer ditherer = null, TaskConfig asyncConfig = null)
 {
     if (bitmapData == null)
         throw new ArgumentNullException(nameof(bitmapData), PublicResources.ArgumentNull);
     return AsyncContext.DoOperationAsync(ctx => DoClear(ctx, bitmapData, color, ditherer), asyncConfig);
 }
Beispiel #18
0
 private static void DoClear(IAsyncContext context, IWritableBitmapData bitmapData, Color32 color, IDitherer ditherer)
 {
     IBitmapDataInternal accessor = bitmapData as IBitmapDataInternal ?? new BitmapDataWrapper(bitmapData, false, true);
     try
     {
         if (ditherer == null || !accessor.PixelFormat.CanBeDithered())
             ClearDirect(context, accessor, color);
         else
             ClearWithDithering(context, accessor, color, ditherer);
     }
     finally
     {
         if (!ReferenceEquals(accessor, bitmapData))
             accessor.Dispose();
     }
 }
Beispiel #19
0
        private static void AdjustQuantizerAndDitherer(IBitmapData target, ref IQuantizer quantizer, ref IDitherer ditherer)
        {
            if (quantizer != null || ditherer == null)
            {
                return;
            }

            if (target.PixelFormat.CanBeDithered())
            {
                quantizer = PredefinedColorsQuantizer.FromBitmapData(target);
            }
            else
            {
                ditherer = null;
            }
        }
Beispiel #20
0
        /// <summary>
        /// Loop over all bitmap rows and replace them with a palette color
        /// </summary>
        /// <param name="width">the width of the bitmap</param>
        /// <param name="height">the height of the bitmap</param>
        /// <param name="sourceData">the source bitmap data</param>
        /// <param name="targetData">the target bitmap data</param>
        /// <param name="ditherDistortion">the dither distortion overlay</param>
        /// <param name="ditherer">the ditherer to use</param>
        private void LoopRows(int width, int height, BitmapData sourceData, BitmapData targetData, Models.Color[] ditherDistortion, IDitherer ditherer)
        {
            int  completed    = 0;
            long sourceOffset = sourceData.Scan0.ToInt64();
            long targetOffset = targetData.Scan0.ToInt64();

            for (int row = 0; row < height; row++)
            {
                // Re initialize the source and target rows
                byte[] targetLine = new byte[width];
                int[]  sourceLine = new int[width];

                Marshal.Copy(new IntPtr(sourceOffset), sourceLine, 0, width);

                GenerateLine(width, height, row, ditherDistortion, sourceLine, targetLine, ditherer);

                Marshal.Copy(targetLine, 0, new IntPtr(targetOffset), width);

                sourceOffset += sourceData.Stride;
                targetOffset += targetData.Stride;

                // Update progress
                completed++;
                ProgressUpdate?.Invoke(this, new ProgressEventArgs((int)(completed / (float)height * 100)));
            }
        }