diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 0f812b041..49fc820c5 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -58,7 +58,7 @@ - + all runtime; build; native; contentfiles; analyzers diff --git a/src/Greenshot.Addon.Confluence/Greenshot.Addon.Confluence.csproj b/src/Greenshot.Addon.Confluence/Greenshot.Addon.Confluence.csproj index 03dace116..2ac4fd889 100644 --- a/src/Greenshot.Addon.Confluence/Greenshot.Addon.Confluence.csproj +++ b/src/Greenshot.Addon.Confluence/Greenshot.Addon.Confluence.csproj @@ -24,6 +24,6 @@ - + diff --git a/src/Greenshot.Addon.ExternalCommand/Greenshot.Addon.ExternalCommand.csproj b/src/Greenshot.Addon.ExternalCommand/Greenshot.Addon.ExternalCommand.csproj index 2d3b55970..179ba53b6 100644 --- a/src/Greenshot.Addon.ExternalCommand/Greenshot.Addon.ExternalCommand.csproj +++ b/src/Greenshot.Addon.ExternalCommand/Greenshot.Addon.ExternalCommand.csproj @@ -19,6 +19,6 @@ - + diff --git a/src/Greenshot.Addon.InternetExplorer/Greenshot.Addon.InternetExplorer.csproj b/src/Greenshot.Addon.InternetExplorer/Greenshot.Addon.InternetExplorer.csproj index 62ececceb..ce9c65c91 100644 --- a/src/Greenshot.Addon.InternetExplorer/Greenshot.Addon.InternetExplorer.csproj +++ b/src/Greenshot.Addon.InternetExplorer/Greenshot.Addon.InternetExplorer.csproj @@ -6,7 +6,7 @@ - + diff --git a/src/Greenshot.Addon.Jira/Greenshot.Addon.Jira.csproj b/src/Greenshot.Addon.Jira/Greenshot.Addon.Jira.csproj index c5c01460d..d429e9df2 100644 --- a/src/Greenshot.Addon.Jira/Greenshot.Addon.Jira.csproj +++ b/src/Greenshot.Addon.Jira/Greenshot.Addon.Jira.csproj @@ -20,7 +20,7 @@ - - + + diff --git a/src/Greenshot.Addons/Core/ImageOutput.cs b/src/Greenshot.Addons/Core/ImageOutput.cs index 2fb03ac85..49e3ce60f 100644 --- a/src/Greenshot.Addons/Core/ImageOutput.cs +++ b/src/Greenshot.Addons/Core/ImageOutput.cs @@ -1,19 +1,19 @@ // Greenshot - a free and open source screenshot tool // Copyright (C) 2007-2020 Thomas Braun, Jens Klingen, Robin Krom -// +// // For more information see: http://getgreenshot.org/ // The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot -// +// // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 1 of the License, or // (at your option) any later version. -// +// // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with this program. If not, see . @@ -38,6 +38,7 @@ using Greenshot.Addons.Interfaces.Plugin; using Greenshot.Core.Enums; using Greenshot.Gfx; using Greenshot.Gfx.Quantizer; +using Greenshot.Gfx.Structs; using Encoder = System.Drawing.Imaging.Encoder; namespace Greenshot.Addons.Core @@ -374,7 +375,7 @@ namespace Greenshot.Addons.Core // We create a copy of the bitmap, so everything else can be disposed surfaceFileStream.Position = 0; - var fileImage = BitmapHelper.FromStream(surfaceFileStream, ".greenshot"); + var fileImage = BitmapHelper.FromStream(surfaceFileStream, ".greenshot"); // Start at -14 read "GreenshotXX.YY" (XX=Major, YY=Minor) const int markerSize = 14; @@ -700,7 +701,7 @@ namespace Greenshot.Addons.Core } } - // check for color reduction, forced or automatically, only when the DisableReduceColors is false + // check for color reduction, forced or automatically, only when the DisableReduceColors is false if (outputSettings.DisableReduceColors || !CoreConfiguration.OutputFileAutoReduceColors && !outputSettings.ReduceColors) { return disposeImage; @@ -708,7 +709,7 @@ namespace Greenshot.Addons.Core var isAlpha = Image.IsAlphaPixelFormat(bitmapToSave.PixelFormat); if (outputSettings.ReduceColors || !isAlpha && CoreConfiguration.OutputFileAutoReduceColors) { - using var quantizer = new WuQuantizer(bitmapToSave); + using var quantizer = new WuQuantizer(bitmapToSave); var colorCount = quantizer.GetColorCount(); Log.Info().WriteLine("Image with format {0} has {1} colors", bitmapToSave.PixelFormat, colorCount); if (!outputSettings.ReduceColors && colorCount >= 256) diff --git a/src/Greenshot.Addons/Extensions/ClipboardBitmapExtensions.cs b/src/Greenshot.Addons/Extensions/ClipboardBitmapExtensions.cs index 183e60535..269dff9a4 100644 --- a/src/Greenshot.Addons/Extensions/ClipboardBitmapExtensions.cs +++ b/src/Greenshot.Addons/Extensions/ClipboardBitmapExtensions.cs @@ -1,19 +1,19 @@ // Greenshot - a free and open source screenshot tool // Copyright (C) 2007-2020 Thomas Braun, Jens Klingen, Robin Krom -// +// // For more information see: http://getgreenshot.org/ // The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot -// +// // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 1 of the License, or // (at your option) any later version. -// +// // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with this program. If not, see . @@ -39,7 +39,7 @@ namespace Greenshot.Addons.Extensions public static class ClipboardBitmapExtensions { private static readonly LogSource Log = new LogSource(); - private static readonly string[] SupportedBitmapFormats = + private static readonly string[] SupportedBitmapFormats = { "PNG", "PNG+Office Art", @@ -73,7 +73,7 @@ namespace Greenshot.Addons.Extensions return true; } - return clipboardAccessToken.GetFilenames() + return clipboardAccessToken.GetFileNames() .Select(filename => Path.GetExtension(filename).ToLowerInvariant()) .Intersect(SupportedExtensions) .Any(); @@ -236,7 +236,7 @@ namespace Greenshot.Addons.Extensions /// byte[] private static byte[] BitmapToByteArray(IBitmapWithNativeSupport bitmap) { - // Lock the bitmap's bits. + // Lock the bitmap's bits. var rect = new NativeRect(0, 0, bitmap.Width, bitmap.Height); var bmpData = bitmap.NativeBitmap.LockBits(rect, ImageLockMode.ReadOnly, bitmap.PixelFormat); diff --git a/src/Greenshot.Addons/Greenshot.Addons.csproj b/src/Greenshot.Addons/Greenshot.Addons.csproj index a0e4a67ed..b4d7933c9 100644 --- a/src/Greenshot.Addons/Greenshot.Addons.csproj +++ b/src/Greenshot.Addons/Greenshot.Addons.csproj @@ -20,15 +20,15 @@ - - - - - - + + + + + + - + diff --git a/src/Greenshot.Core/Greenshot.Core.csproj b/src/Greenshot.Core/Greenshot.Core.csproj index 283c2c896..9a50f2d80 100644 --- a/src/Greenshot.Core/Greenshot.Core.csproj +++ b/src/Greenshot.Core/Greenshot.Core.csproj @@ -6,7 +6,7 @@ - + diff --git a/src/Greenshot.Gfx/BitmapHelper.cs b/src/Greenshot.Gfx/BitmapHelper.cs index c712f5dd8..d6dc6d110 100644 --- a/src/Greenshot.Gfx/BitmapHelper.cs +++ b/src/Greenshot.Gfx/BitmapHelper.cs @@ -1,19 +1,19 @@ // Greenshot - a free and open source screenshot tool // Copyright (C) 2007-2020 Thomas Braun, Jens Klingen, Robin Krom -// +// // For more information see: http://getgreenshot.org/ // The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot -// +// // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 1 of the License, or // (at your option) any later version. -// +// // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with this program. If not, see . @@ -46,7 +46,7 @@ namespace Greenshot.Gfx private static readonly LogSource Log = new LogSource(); /// - /// This defines all available bitmap reader functions, registered to an "extension" is called with a stream to a IBitmap. + /// This defines all available bitmap reader functions, registered to an "extension" is called with a stream to a IBitmap. /// public static IDictionary StreamConverters { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase); @@ -444,7 +444,7 @@ namespace Greenshot.Gfx Matrix22 = 0, Matrix33 = darkness }; - + var shadowRectangle = new NativeRect(new NativePoint(shadowSize, shadowSize), sourceBitmap.Size); ApplyColorMatrix(sourceBitmap, NativeRect.Empty, returnImage, shadowRectangle, maskMatrix); @@ -755,7 +755,6 @@ namespace Greenshot.Gfx colors += lineColorCount; } }); - return colors; } @@ -815,7 +814,7 @@ namespace Greenshot.Gfx return original; } - + if (width == original.Width * 2) { return original.Scale2X(); diff --git a/src/Greenshot.Gfx/ChunkyBitmap.cs b/src/Greenshot.Gfx/ChunkyBitmap.cs new file mode 100644 index 000000000..409f01b15 --- /dev/null +++ b/src/Greenshot.Gfx/ChunkyBitmap.cs @@ -0,0 +1,54 @@ +// Greenshot - a free and open source screenshot tool +// Copyright (C) 2007-2020 Thomas Braun, Jens Klingen, Robin Krom +// +// For more information see: http://getgreenshot.org/ +// The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 1 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using System; + +namespace Greenshot.Gfx +{ + /// + /// A bitmap wrapper with memory from Marshal.AllocHGlobal + /// + /// struct for the pixel information + public class ChunkyBitmap : UnmanagedBitmap + { + + /// + /// The constructor for the UnmanagedBitmap + /// + /// int + /// int + /// float + /// float + public ChunkyBitmap(int width, int height, float horizontalPixelsPerInch = 0.96f, float verticalPixelsPerInch = 0.96f) : base(width, height, horizontalPixelsPerInch, verticalPixelsPerInch) + { + } + + /// + /// The constructor for the UnmanagedBitmap with already initialized bits + /// + /// IntPtr to the bits, this will not be freed + /// int + /// int + /// float + /// float + public ChunkyBitmap(IntPtr bits, int width, int height, float horizontalPixelsPerInch = 0.96f, float verticalPixelsPerInch = 0.96f) : base(bits, width, height, horizontalPixelsPerInch, verticalPixelsPerInch) + { + } + } +} diff --git a/src/Greenshot.Gfx/Effects/ReduceColorsEffect.cs b/src/Greenshot.Gfx/Effects/ReduceColorsEffect.cs index aa9092ba4..0f8f99957 100644 --- a/src/Greenshot.Gfx/Effects/ReduceColorsEffect.cs +++ b/src/Greenshot.Gfx/Effects/ReduceColorsEffect.cs @@ -1,19 +1,19 @@ // Greenshot - a free and open source screenshot tool // Copyright (C) 2007-2020 Thomas Braun, Jens Klingen, Robin Krom -// +// // For more information see: http://getgreenshot.org/ // The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot -// +// // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 1 of the License, or // (at your option) any later version. -// +// // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with this program. If not, see . @@ -21,6 +21,7 @@ using System; using System.Drawing.Drawing2D; using Dapplo.Log; using Greenshot.Gfx.Quantizer; +using Greenshot.Gfx.Structs; namespace Greenshot.Gfx.Effects { @@ -39,7 +40,7 @@ namespace Greenshot.Gfx.Effects /// public IBitmapWithNativeSupport Apply(IBitmapWithNativeSupport sourceBitmap, Matrix matrix) { - using (var quantizer = new WuQuantizer(sourceBitmap)) + using (var quantizer = new WuQuantizer(sourceBitmap)) { var colorCount = quantizer.GetColorCount(); if (colorCount <= Colors) diff --git a/src/Greenshot.Gfx/Greenshot.Gfx.csproj b/src/Greenshot.Gfx/Greenshot.Gfx.csproj index d8d407039..d8f96e209 100644 --- a/src/Greenshot.Gfx/Greenshot.Gfx.csproj +++ b/src/Greenshot.Gfx/Greenshot.Gfx.csproj @@ -8,8 +8,8 @@ - - - + + + diff --git a/src/Greenshot.Gfx/Quantizer/WuColorCube.cs b/src/Greenshot.Gfx/Quantizer/WuColorCube.cs index 4c59364e8..076552343 100644 --- a/src/Greenshot.Gfx/Quantizer/WuColorCube.cs +++ b/src/Greenshot.Gfx/Quantizer/WuColorCube.cs @@ -1,25 +1,25 @@ // Greenshot - a free and open source screenshot tool // Copyright (C) 2007-2020 Thomas Braun, Jens Klingen, Robin Krom -// +// // For more information see: http://getgreenshot.org/ // The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot -// +// // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 1 of the License, or // (at your option) any later version. -// +// // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with this program. If not, see . namespace Greenshot.Gfx.Quantizer { - internal class WuColorCube + internal struct WuColorCube { /// /// Gets or sets the red minimum. diff --git a/src/Greenshot.Gfx/Quantizer/WuColorCubeOld.cs b/src/Greenshot.Gfx/Quantizer/WuColorCubeOld.cs new file mode 100644 index 000000000..6e004269f --- /dev/null +++ b/src/Greenshot.Gfx/Quantizer/WuColorCubeOld.cs @@ -0,0 +1,66 @@ +// Greenshot - a free and open source screenshot tool +// Copyright (C) 2007-2020 Thomas Braun, Jens Klingen, Robin Krom +// +// For more information see: http://getgreenshot.org/ +// The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 1 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +namespace Greenshot.Gfx.Quantizer +{ + internal class WuColorCubeOld + { + /// + /// Gets or sets the red minimum. + /// + /// The red minimum. + public int RedMinimum { get; set; } + + /// + /// Gets or sets the red maximum. + /// + /// The red maximum. + public int RedMaximum { get; set; } + + /// + /// Gets or sets the green minimum. + /// + /// The green minimum. + public int GreenMinimum { get; set; } + + /// + /// Gets or sets the green maximum. + /// + /// The green maximum. + public int GreenMaximum { get; set; } + + /// + /// Gets or sets the blue minimum. + /// + /// The blue minimum. + public int BlueMinimum { get; set; } + + /// + /// Gets or sets the blue maximum. + /// + /// The blue maximum. + public int BlueMaximum { get; set; } + + /// + /// Gets or sets the cube volume. + /// + /// The volume. + public int Volume { get; set; } + } +} \ No newline at end of file diff --git a/src/Greenshot.Gfx/Quantizer/WuQuantizer.cs b/src/Greenshot.Gfx/Quantizer/WuQuantizer.cs index 3324dc654..e8188fb48 100644 --- a/src/Greenshot.Gfx/Quantizer/WuQuantizer.cs +++ b/src/Greenshot.Gfx/Quantizer/WuQuantizer.cs @@ -1,19 +1,19 @@ // Greenshot - a free and open source screenshot tool // Copyright (C) 2007-2020 Thomas Braun, Jens Klingen, Robin Krom -// +// // For more information see: http://getgreenshot.org/ // The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot -// +// // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 1 of the License, or // (at your option) any later version. -// +// // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with this program. If not, see . @@ -21,16 +21,16 @@ using System; using System.Collections; using System.Collections.Generic; using System.Drawing; -using System.Drawing.Imaging; -using Dapplo.Log; -using Greenshot.Gfx.FastBitmap; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Greenshot.Gfx.Structs; namespace Greenshot.Gfx.Quantizer { /// /// Implementation of the WuQuantizer algorithm /// - public class WuQuantizer : IDisposable + public class WuQuantizer : IDisposable where TPixel : unmanaged { private const int Maxcolor = 512; private const int Red = 2; @@ -39,7 +39,6 @@ namespace Greenshot.Gfx.Quantizer private const int Sidesize = 33; private const int Maxsideindex = 32; private const int Maxvolume = Sidesize * Sidesize * Sidesize; - private static readonly LogSource Log = new LogSource(); // To count the colors private readonly int _colorCount; @@ -49,23 +48,25 @@ namespace Greenshot.Gfx.Quantizer private readonly long[,,] _momentsBlue; private readonly long[,,] _momentsGreen; private readonly long[,,] _momentsRed; - private readonly IBitmapWithNativeSupport _sourceBitmap; + private readonly UnmanagedBitmap _sourceBitmap; private readonly long[,,] _weights; - private int[] _blues; - private int[] _greens; - - private int[] _reds; - private IBitmapWithNativeSupport _resultBitmap; - private int[] _sums; - - private byte[] _tag; + private ChunkyBitmap _resultBitmap; /// /// The constructor for the WuQauntizer /// - /// - public WuQuantizer(IBitmapWithNativeSupport sourceBitmap) + /// IBitmapWithNativeSupport + public WuQuantizer(IBitmapWithNativeSupport sourceBitmap) : this(sourceBitmap as UnmanagedBitmap) + { + + } + + /// + /// The constructor for the WuQauntizer + /// + /// UnmanagedBitmap of TPixel + public WuQuantizer(UnmanagedBitmap sourceBitmap) { _sourceBitmap = sourceBitmap; // Make sure the color count variables are reset @@ -75,12 +76,6 @@ namespace Greenshot.Gfx.Quantizer // creates all the cubes _cubes = new WuColorCube[Maxcolor]; - // initializes all the cubes - for (var cubeIndex = 0; cubeIndex < Maxcolor; cubeIndex++) - { - _cubes[cubeIndex] = new WuColorCube(); - } - // resets the reference minimums _cubes[0].RedMinimum = 0; _cubes[0].GreenMinimum = 0; @@ -97,32 +92,23 @@ namespace Greenshot.Gfx.Quantizer _momentsBlue = new long[Sidesize, Sidesize, Sidesize]; _moments = new float[Sidesize, Sidesize, Sidesize]; - var table = new int[256]; - - for (var tableIndex = 0; tableIndex < 256; ++tableIndex) + Span table = stackalloc int[256]; + for (var tableIndex = 0; tableIndex < 256; ++tableIndex) { table[tableIndex] = tableIndex * tableIndex; } - // Use a bitmap to store the initial match, which is just as good as an array and saves us 2x the storage - using var sourceFastBitmap = FastBitmapFactory.Create(sourceBitmap); - sourceFastBitmap.Lock(); - using var destinationFastBitmap = FastBitmapFactory.CreateEmpty(sourceBitmap.Size, PixelFormat.Format8bppIndexed, Color.White) as FastChunkyBitmap; - for (var y = 0; y < sourceFastBitmap.Height; y++) + _resultBitmap = new ChunkyBitmap(sourceBitmap.Width, sourceBitmap.Height); + for (var y = 0; y < sourceBitmap.Height; y++) { - for (var x = 0; x < sourceFastBitmap.Width; x++) + var srcPixelSpan = MemoryMarshal.Cast(_sourceBitmap[y]); + var destPixelSpan = _resultBitmap[y]; + for (var x = 0; x < sourceBitmap.Width; x++) { - Color color; - if (!(sourceFastBitmap is IFastBitmapWithBlend sourceFastBitmapWithBlend)) - { - color = sourceFastBitmap.GetColorAt(x, y); - } - else - { - color = sourceFastBitmapWithBlend.GetBlendedColorAt(x, y); - } + var color = srcPixelSpan[x]; + // To count the colors - var index = color.ToArgb() & 0x00ffffff; + var index = (color.B << 16) | (color.G << 8) | color.R; // Check if we already have this color if (!bitArray.Get(index)) { @@ -143,10 +129,9 @@ namespace Greenshot.Gfx.Quantizer // Store the initial "match" var paletteIndex = (indexRed << 10) + (indexRed << 6) + indexRed + (indexGreen << 5) + indexGreen + indexBlue; - destinationFastBitmap.SetColorIndexAt(x, y, (byte) (paletteIndex & 0xff)); + destPixelSpan[x] = (byte)(paletteIndex & 0xff); } } - _resultBitmap = destinationFastBitmap.UnlockAndReturnBitmap(); } /// @@ -155,7 +140,7 @@ namespace Greenshot.Gfx.Quantizer Dispose(true); } - + /// /// Dispose implementation /// @@ -191,39 +176,30 @@ namespace Greenshot.Gfx.Quantizer /// Bitmap public IBitmapWithNativeSupport SimpleReindex() { - var colors = new List(); - var lookup = new Dictionary(); - using (var bbbDest = FastBitmapFactory.Create(_resultBitmap) as FastChunkyBitmap) - { - bbbDest.Lock(); - using var bbbSrc = FastBitmapFactory.Create(_sourceBitmap); - bbbSrc.Lock(); - for (var y = 0; y < bbbSrc.Height; y++) + Span colors = stackalloc uint[256]; + var lookup = new Dictionary(); + + byte colorCount = 0; + for (var y = 0; y < _sourceBitmap.Height; y++) + { + var srcSpan = MemoryMarshal.Cast(_sourceBitmap[y]); + var destSpan = _resultBitmap[y]; + for (var x = 0; x < _sourceBitmap.Width; x++) { - for (var x = 0; x < bbbSrc.Width; x++) + var color = srcSpan[x]; + byte index; + if (lookup.ContainsKey(color)) { - Color color; - if (bbbSrc is IFastBitmapWithBlend bbbSrcBlend) - { - color = bbbSrcBlend.GetBlendedColorAt(x, y); - } - else - { - color = bbbSrc.GetColorAt(x, y); - } - byte index; - if (lookup.ContainsKey(color)) - { - index = lookup[color]; - } - else - { - colors.Add(color); - index = (byte) (colors.Count - 1); - lookup.Add(color, index); - } - bbbDest.SetColorIndexAt(x, y, index); + index = lookup[color]; } + else + { + colors[colorCount] = color; + index = colorCount; + colorCount++; + lookup.Add(color, index); + } + destSpan[x] = index; } } @@ -232,9 +208,9 @@ namespace Greenshot.Gfx.Quantizer var entries = imagePalette.Entries; for (var paletteIndex = 0; paletteIndex < 256; paletteIndex++) { - if (paletteIndex < _colorCount) + if (paletteIndex < colorCount) { - entries[paletteIndex] = colors[paletteIndex]; + entries[paletteIndex] = colors[paletteIndex].ToColor(); } else { @@ -261,12 +237,10 @@ namespace Greenshot.Gfx.Quantizer if (_colorCount < allowedColorCount) { // Simple logic to reduce to 8 bit - Log.Info().WriteLine("Colors in the image are already less as whished for, using simple copy to indexed image, no quantizing needed!"); return SimpleReindex(); } // preprocess the colors CalculateMoments(); - Log.Info().WriteLine("Calculated the moments..."); var next = 0; var volumeVariance = new float[Maxcolor]; @@ -274,7 +248,7 @@ namespace Greenshot.Gfx.Quantizer for (var cubeIndex = 1; cubeIndex < allowedColorCount; ++cubeIndex) { // if cut is possible; make it - if (Cut(_cubes[next], _cubes[cubeIndex])) + if (Cut(ref _cubes[next], ref _cubes[cubeIndex])) { volumeVariance[next] = _cubes[next].Volume > 1 ? CalculateVariance(_cubes[next]) : 0.0f; volumeVariance[cubeIndex] = _cubes[cubeIndex].Volume > 1 ? CalculateVariance(_cubes[cubeIndex]) : 0.0f; @@ -307,16 +281,16 @@ namespace Greenshot.Gfx.Quantizer break; } - var lookupRed = new int[Maxcolor]; - var lookupGreen = new int[Maxcolor]; - var lookupBlue = new int[Maxcolor]; + Span lookupRed = stackalloc int[Maxcolor]; + Span lookupGreen = stackalloc int[Maxcolor]; + Span lookupBlue = stackalloc int[Maxcolor]; - _tag = new byte[Maxvolume]; + Span tag = stackalloc byte[Maxvolume]; // pre-calculates lookup tables for (var k = 0; k < allowedColorCount; ++k) { - Mark(_cubes[k], k, _tag); + Mark(_cubes[k], k, tag); var weight = Volume(_cubes[k], _weights); @@ -334,92 +308,78 @@ namespace Greenshot.Gfx.Quantizer } } - _reds = new int[allowedColorCount + 1]; - _greens = new int[allowedColorCount + 1]; - _blues = new int[allowedColorCount + 1]; - _sums = new int[allowedColorCount + 1]; + Span reds = stackalloc int[allowedColorCount + 1]; + Span greens = stackalloc int[allowedColorCount + 1]; + Span blues = stackalloc int[allowedColorCount + 1]; + Span sums = stackalloc int[allowedColorCount + 1]; - Log.Info().WriteLine("Starting bitmap reconstruction..."); - using (var dest = FastBitmapFactory.Create(_resultBitmap) as FastChunkyBitmap) + var lookup = new Dictionary(); + for (var y = 0; y < _sourceBitmap.Height; y++) { - using var src = FastBitmapFactory.Create(_sourceBitmap); - var lookup = new Dictionary(); - for (var y = 0; y < src.Height; y++) + var destSpan = _resultBitmap[y]; + var srcSpan = MemoryMarshal.Cast(_sourceBitmap[y]); + for (var x = 0; x < _sourceBitmap.Width; x++) { - for (var x = 0; x < src.Width; x++) + var color = srcSpan[x]; + // Check if we already matched the color + byte bestMatch; + if (!lookup.ContainsKey(color)) { - Color color; - if (src is IFastBitmapWithBlend srcBlend) - { - // WithoutAlpha, this makes it possible to ignore the alpha - color = srcBlend.GetBlendedColorAt(x, y); - } - else - { - color = src.GetColorAt(x, y); - } + // If not we need to find the best match - // Check if we already matched the color - byte bestMatch; - if (!lookup.ContainsKey(color)) + // First get initial match + bestMatch = destSpan[x]; + bestMatch = tag[bestMatch]; + + var bestDistance = 100000000; + for (var lookupIndex = 0; lookupIndex < allowedColorCount; lookupIndex++) { - // If not we need to find the best match + var foundRed = lookupRed[lookupIndex]; + var foundGreen = lookupGreen[lookupIndex]; + var foundBlue = lookupBlue[lookupIndex]; + var deltaRed = color.R - foundRed; + var deltaGreen = color.G - foundGreen; + var deltaBlue = color.B - foundBlue; - // First get initial match - bestMatch = dest.GetColorIndexAt(x, y); - bestMatch = _tag[bestMatch]; + var distance = deltaRed * deltaRed + deltaGreen * deltaGreen + deltaBlue * deltaBlue; - var bestDistance = 100000000; - for (var lookupIndex = 0; lookupIndex < allowedColorCount; lookupIndex++) + if (distance < bestDistance) { - var foundRed = lookupRed[lookupIndex]; - var foundGreen = lookupGreen[lookupIndex]; - var foundBlue = lookupBlue[lookupIndex]; - var deltaRed = color.R - foundRed; - var deltaGreen = color.G - foundGreen; - var deltaBlue = color.B - foundBlue; - - var distance = deltaRed * deltaRed + deltaGreen * deltaGreen + deltaBlue * deltaBlue; - - if (distance < bestDistance) - { - bestDistance = distance; - bestMatch = (byte) lookupIndex; - } + bestDistance = distance; + bestMatch = (byte) lookupIndex; } - lookup.Add(color, bestMatch); } - else - { - // Already matched, so we just use the lookup - bestMatch = lookup[color]; - } - - _reds[bestMatch] += color.R; - _greens[bestMatch] += color.G; - _blues[bestMatch] += color.B; - _sums[bestMatch]++; - - dest.SetColorIndexAt(x, y, bestMatch); + lookup.Add(color, bestMatch); } + else + { + // Already matched, so we just use the lookup + bestMatch = lookup[color]; + } + + reds[bestMatch] += color.R; + greens[bestMatch] += color.G; + blues[bestMatch] += color.B; + sums[bestMatch]++; + + destSpan[x] = bestMatch; } } - // generates palette var imagePalette = _resultBitmap.NativeBitmap.Palette; var entries = imagePalette.Entries; for (var paletteIndex = 0; paletteIndex < allowedColorCount; paletteIndex++) { - if (_sums[paletteIndex] > 0) + if (sums[paletteIndex] > 0) { - _reds[paletteIndex] /= _sums[paletteIndex]; - _greens[paletteIndex] /= _sums[paletteIndex]; - _blues[paletteIndex] /= _sums[paletteIndex]; + reds[paletteIndex] /= sums[paletteIndex]; + greens[paletteIndex] /= sums[paletteIndex]; + blues[paletteIndex] /= sums[paletteIndex]; } - entries[paletteIndex] = Color.FromArgb(255, _reds[paletteIndex], _greens[paletteIndex], _blues[paletteIndex]); + entries[paletteIndex] = Color.FromArgb(255, reds[paletteIndex], greens[paletteIndex], blues[paletteIndex]); } _resultBitmap.NativeBitmap.Palette = imagePalette; @@ -434,11 +394,11 @@ namespace Greenshot.Gfx.Quantizer /// private void CalculateMoments() { - var area = new long[Sidesize]; - var areaRed = new long[Sidesize]; - var areaGreen = new long[Sidesize]; - var areaBlue = new long[Sidesize]; - var area2 = new float[Sidesize]; + Span area = stackalloc long[Sidesize]; + Span areaRed = stackalloc long[Sidesize]; + Span areaGreen = stackalloc long[Sidesize]; + Span areaBlue = stackalloc long[Sidesize]; + var area2 = new float[Sidesize]; for (var redIndex = 1; redIndex <= Maxsideindex; ++redIndex) { @@ -483,10 +443,11 @@ namespace Greenshot.Gfx.Quantizer } } - /// - /// Computes the volume of the cube in a specific moment. - /// - private static long Volume(WuColorCube cube, long[,,] moment) + /// + /// Computes the volume of the cube in a specific moment. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static long Volume(in WuColorCube cube, long[,,] moment) { return moment[cube.RedMaximum, cube.GreenMaximum, cube.BlueMaximum] - moment[cube.RedMaximum, cube.GreenMaximum, cube.BlueMinimum] - @@ -498,10 +459,11 @@ namespace Greenshot.Gfx.Quantizer moment[cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum]; } - /// - /// Computes the volume of the cube in a specific moment. For the floating-point values. - /// - private static float VolumeFloat(WuColorCube cube, float[,,] moment) + /// + /// Computes the volume of the cube in a specific moment. For the floating-point values. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float VolumeFloat(in WuColorCube cube, float[,,] moment) { return moment[cube.RedMaximum, cube.GreenMaximum, cube.BlueMaximum] - moment[cube.RedMaximum, cube.GreenMaximum, cube.BlueMinimum] - @@ -513,10 +475,11 @@ namespace Greenshot.Gfx.Quantizer moment[cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum]; } - /// - /// Splits the cube in given position, and color direction. - /// - private static long Top(WuColorCube cube, int direction, int position, long[,,] moment) => + /// + /// Splits the cube in given position, and color direction. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static long Top(in WuColorCube cube, int direction, int position, long[,,] moment) => direction switch { Red => (moment[position, cube.GreenMaximum, cube.BlueMaximum] - @@ -537,7 +500,8 @@ namespace Greenshot.Gfx.Quantizer /// /// Splits the cube in a given color direction at its minimum. /// - private static long Bottom(WuColorCube cube, int direction, long[,,] moment) => + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static long Bottom(in WuColorCube cube, int direction, long[,,] moment) => direction switch { Red => (-moment[cube.RedMinimum, cube.GreenMaximum, cube.BlueMaximum] + @@ -558,7 +522,7 @@ namespace Greenshot.Gfx.Quantizer /// /// Calculates statistical variance for a given cube. /// - private float CalculateVariance(WuColorCube cube) + private float CalculateVariance(in WuColorCube cube) { float volumeRed = Volume(cube, _momentsRed); float volumeGreen = Volume(cube, _momentsGreen); @@ -574,7 +538,7 @@ namespace Greenshot.Gfx.Quantizer /// /// Finds the optimal (maximal) position for the cut. /// - private float Maximize(WuColorCube cube, int direction, int first, int last, int[] cut, long wholeRed, long wholeGreen, long wholeBlue, long wholeWeight) + private float Maximize(in WuColorCube cube, int direction, int first, int last, int[] cut, long wholeRed, long wholeGreen, long wholeBlue, long wholeWeight) { var bottomRed = Bottom(cube, direction, _momentsRed); var bottomGreen = Bottom(cube, direction, _momentsGreen); @@ -623,7 +587,7 @@ namespace Greenshot.Gfx.Quantizer /// /// Cuts a cube with another one. /// - private bool Cut(WuColorCube first, WuColorCube second) + private bool Cut(ref WuColorCube first, ref WuColorCube second) { int direction; @@ -699,7 +663,7 @@ namespace Greenshot.Gfx.Quantizer /// /// Marks all the tags with a given label. /// - private static void Mark(WuColorCube cube, int label, byte[] tag) + private static void Mark(in WuColorCube cube, int label, Span tag) { for (var redIndex = cube.RedMinimum + 1; redIndex <= cube.RedMaximum; ++redIndex) { diff --git a/src/Greenshot.Gfx/Quantizer/WuQuantizerOld.cs b/src/Greenshot.Gfx/Quantizer/WuQuantizerOld.cs new file mode 100644 index 000000000..472d3a1c0 --- /dev/null +++ b/src/Greenshot.Gfx/Quantizer/WuQuantizerOld.cs @@ -0,0 +1,716 @@ +// Greenshot - a free and open source screenshot tool +// Copyright (C) 2007-2020 Thomas Braun, Jens Klingen, Robin Krom +// +// For more information see: http://getgreenshot.org/ +// The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 1 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using Dapplo.Log; +using Greenshot.Gfx.FastBitmap; + +namespace Greenshot.Gfx.Quantizer +{ + /// + /// Implementation of the WuQuantizer algorithm + /// + public class WuQuantizerOld : IDisposable + { + private const int Maxcolor = 512; + private const int Red = 2; + private const int Green = 1; + private const int Blue = 0; + private const int Sidesize = 33; + private const int Maxsideindex = 32; + private const int Maxvolume = Sidesize * Sidesize * Sidesize; + private static readonly LogSource Log = new LogSource(); + + // To count the colors + private readonly int _colorCount; + + private readonly WuColorCubeOld[] _cubes; + private readonly float[,,] _moments; + private readonly long[,,] _momentsBlue; + private readonly long[,,] _momentsGreen; + private readonly long[,,] _momentsRed; + private readonly IBitmapWithNativeSupport _sourceBitmap; + + private readonly long[,,] _weights; + private int[] _blues; + private int[] _greens; + + private int[] _reds; + private IBitmapWithNativeSupport _resultBitmap; + private int[] _sums; + + private byte[] _tag; + + /// + /// The constructor for the WuQauntizer + /// + /// + public WuQuantizerOld(IBitmapWithNativeSupport sourceBitmap) + { + _sourceBitmap = sourceBitmap; + // Make sure the color count variables are reset + var bitArray = new BitArray((int) Math.Pow(2, 24)); + _colorCount = 0; + + // creates all the cubes + _cubes = new WuColorCubeOld[Maxcolor]; + + // initializes all the cubes + for (var cubeIndex = 0; cubeIndex < Maxcolor; cubeIndex++) + { + _cubes[cubeIndex] = new WuColorCubeOld(); + } + + // resets the reference minimums + _cubes[0].RedMinimum = 0; + _cubes[0].GreenMinimum = 0; + _cubes[0].BlueMinimum = 0; + + // resets the reference maximums + _cubes[0].RedMaximum = Maxsideindex; + _cubes[0].GreenMaximum = Maxsideindex; + _cubes[0].BlueMaximum = Maxsideindex; + + _weights = new long[Sidesize, Sidesize, Sidesize]; + _momentsRed = new long[Sidesize, Sidesize, Sidesize]; + _momentsGreen = new long[Sidesize, Sidesize, Sidesize]; + _momentsBlue = new long[Sidesize, Sidesize, Sidesize]; + _moments = new float[Sidesize, Sidesize, Sidesize]; + + var table = new int[256]; + + for (var tableIndex = 0; tableIndex < 256; ++tableIndex) + { + table[tableIndex] = tableIndex * tableIndex; + } + + // Use a bitmap to store the initial match, which is just as good as an array and saves us 2x the storage + using var sourceFastBitmap = FastBitmapFactory.Create(sourceBitmap); + sourceFastBitmap.Lock(); + using var destinationFastBitmap = FastBitmapFactory.CreateEmpty(sourceBitmap.Size, PixelFormat.Format8bppIndexed, Color.White) as FastChunkyBitmap; + for (var y = 0; y < sourceFastBitmap.Height; y++) + { + for (var x = 0; x < sourceFastBitmap.Width; x++) + { + Color color; + if (!(sourceFastBitmap is IFastBitmapWithBlend sourceFastBitmapWithBlend)) + { + color = sourceFastBitmap.GetColorAt(x, y); + } + else + { + color = sourceFastBitmapWithBlend.GetBlendedColorAt(x, y); + } + // To count the colors + var index = color.ToArgb() & 0x00ffffff; + // Check if we already have this color + if (!bitArray.Get(index)) + { + // If not, add 1 to the single colors + _colorCount++; + bitArray.Set(index, true); + } + + var indexRed = (color.R >> 3) + 1; + var indexGreen = (color.G >> 3) + 1; + var indexBlue = (color.B >> 3) + 1; + + _weights[indexRed, indexGreen, indexBlue]++; + _momentsRed[indexRed, indexGreen, indexBlue] += color.R; + _momentsGreen[indexRed, indexGreen, indexBlue] += color.G; + _momentsBlue[indexRed, indexGreen, indexBlue] += color.B; + _moments[indexRed, indexGreen, indexBlue] += table[color.R] + table[color.G] + table[color.B]; + + // Store the initial "match" + var paletteIndex = (indexRed << 10) + (indexRed << 6) + indexRed + (indexGreen << 5) + indexGreen + indexBlue; + destinationFastBitmap.SetColorIndexAt(x, y, (byte) (paletteIndex & 0xff)); + } + } + _resultBitmap = destinationFastBitmap.UnlockAndReturnBitmap(); + } + + /// + public void Dispose() + { + Dispose(true); + } + + + /// + /// Dispose implementation + /// + /// bool + protected virtual void Dispose(bool disposing) + { + if (!disposing) + { + return; + } + + if (_resultBitmap == null) + { + return; + } + + _resultBitmap.Dispose(); + _resultBitmap = null; + } + + /// + /// Returns the number of colors + /// + /// int + public int GetColorCount() + { + return _colorCount; + } + + /// + /// Reindex the 24/32 BPP (A)RGB image to a 8BPP + /// + /// Bitmap + public IBitmapWithNativeSupport SimpleReindex() + { + var colors = new List(); + var lookup = new Dictionary(); + using (var bbbDest = FastBitmapFactory.Create(_resultBitmap) as FastChunkyBitmap) + { + bbbDest.Lock(); + using var bbbSrc = FastBitmapFactory.Create(_sourceBitmap); + bbbSrc.Lock(); + for (var y = 0; y < bbbSrc.Height; y++) + { + for (var x = 0; x < bbbSrc.Width; x++) + { + Color color; + if (bbbSrc is IFastBitmapWithBlend bbbSrcBlend) + { + color = bbbSrcBlend.GetBlendedColorAt(x, y); + } + else + { + color = bbbSrc.GetColorAt(x, y); + } + byte index; + if (lookup.ContainsKey(color)) + { + index = lookup[color]; + } + else + { + colors.Add(color); + index = (byte) (colors.Count - 1); + lookup.Add(color, index); + } + bbbDest.SetColorIndexAt(x, y, index); + } + } + } + + // generates palette + var imagePalette = _resultBitmap.NativeBitmap.Palette; + var entries = imagePalette.Entries; + for (var paletteIndex = 0; paletteIndex < 256; paletteIndex++) + { + if (paletteIndex < _colorCount) + { + entries[paletteIndex] = colors[paletteIndex]; + } + else + { + entries[paletteIndex] = Color.Black; + } + } + _resultBitmap.NativeBitmap.Palette = imagePalette; + + // Make sure the bitmap is not disposed, as we return it. + var tmpBitmap = _resultBitmap; + _resultBitmap = null; + return tmpBitmap; + } + + /// + /// Get the image + /// + public IBitmapWithNativeSupport GetQuantizedImage(int allowedColorCount = 256) + { + if (allowedColorCount > 256) + { + throw new ArgumentOutOfRangeException(nameof(allowedColorCount), "Quantizing muss be done to get less than 256 colors"); + } + if (_colorCount < allowedColorCount) + { + // Simple logic to reduce to 8 bit + Log.Info().WriteLine("Colors in the image are already less as whished for, using simple copy to indexed image, no quantizing needed!"); + return SimpleReindex(); + } + // preprocess the colors + CalculateMoments(); + Log.Info().WriteLine("Calculated the moments..."); + var next = 0; + var volumeVariance = new float[Maxcolor]; + + // processes the cubes + for (var cubeIndex = 1; cubeIndex < allowedColorCount; ++cubeIndex) + { + // if cut is possible; make it + if (Cut(_cubes[next], _cubes[cubeIndex])) + { + volumeVariance[next] = _cubes[next].Volume > 1 ? CalculateVariance(_cubes[next]) : 0.0f; + volumeVariance[cubeIndex] = _cubes[cubeIndex].Volume > 1 ? CalculateVariance(_cubes[cubeIndex]) : 0.0f; + } + else + { + // the cut was not possible, revert the index + volumeVariance[next] = 0.0f; + cubeIndex--; + } + + next = 0; + var temp = volumeVariance[0]; + + for (var index = 1; index <= cubeIndex; ++index) + { + if (volumeVariance[index] <= temp) + { + continue; + } + temp = volumeVariance[index]; + next = index; + } + + if (temp > 0.0) + { + continue; + } + allowedColorCount = cubeIndex + 1; + break; + } + + var lookupRed = new int[Maxcolor]; + var lookupGreen = new int[Maxcolor]; + var lookupBlue = new int[Maxcolor]; + + _tag = new byte[Maxvolume]; + + // pre-calculates lookup tables + for (var k = 0; k < allowedColorCount; ++k) + { + Mark(_cubes[k], k, _tag); + + var weight = Volume(_cubes[k], _weights); + + if (weight > 0) + { + lookupRed[k] = (int) (Volume(_cubes[k], _momentsRed) / weight); + lookupGreen[k] = (int) (Volume(_cubes[k], _momentsGreen) / weight); + lookupBlue[k] = (int) (Volume(_cubes[k], _momentsBlue) / weight); + } + else + { + lookupRed[k] = 0; + lookupGreen[k] = 0; + lookupBlue[k] = 0; + } + } + + _reds = new int[allowedColorCount + 1]; + _greens = new int[allowedColorCount + 1]; + _blues = new int[allowedColorCount + 1]; + _sums = new int[allowedColorCount + 1]; + + Log.Info().WriteLine("Starting bitmap reconstruction..."); + + using (var dest = FastBitmapFactory.Create(_resultBitmap) as FastChunkyBitmap) + { + using var src = FastBitmapFactory.Create(_sourceBitmap); + var lookup = new Dictionary(); + for (var y = 0; y < src.Height; y++) + { + for (var x = 0; x < src.Width; x++) + { + Color color; + if (src is IFastBitmapWithBlend srcBlend) + { + // WithoutAlpha, this makes it possible to ignore the alpha + color = srcBlend.GetBlendedColorAt(x, y); + } + else + { + color = src.GetColorAt(x, y); + } + + // Check if we already matched the color + byte bestMatch; + if (!lookup.ContainsKey(color)) + { + // If not we need to find the best match + + // First get initial match + bestMatch = dest.GetColorIndexAt(x, y); + bestMatch = _tag[bestMatch]; + + var bestDistance = 100000000; + for (var lookupIndex = 0; lookupIndex < allowedColorCount; lookupIndex++) + { + var foundRed = lookupRed[lookupIndex]; + var foundGreen = lookupGreen[lookupIndex]; + var foundBlue = lookupBlue[lookupIndex]; + var deltaRed = color.R - foundRed; + var deltaGreen = color.G - foundGreen; + var deltaBlue = color.B - foundBlue; + + var distance = deltaRed * deltaRed + deltaGreen * deltaGreen + deltaBlue * deltaBlue; + + if (distance < bestDistance) + { + bestDistance = distance; + bestMatch = (byte) lookupIndex; + } + } + lookup.Add(color, bestMatch); + } + else + { + // Already matched, so we just use the lookup + bestMatch = lookup[color]; + } + + _reds[bestMatch] += color.R; + _greens[bestMatch] += color.G; + _blues[bestMatch] += color.B; + _sums[bestMatch]++; + + dest.SetColorIndexAt(x, y, bestMatch); + } + } + } + + + // generates palette + var imagePalette = _resultBitmap.NativeBitmap.Palette; + var entries = imagePalette.Entries; + for (var paletteIndex = 0; paletteIndex < allowedColorCount; paletteIndex++) + { + if (_sums[paletteIndex] > 0) + { + _reds[paletteIndex] /= _sums[paletteIndex]; + _greens[paletteIndex] /= _sums[paletteIndex]; + _blues[paletteIndex] /= _sums[paletteIndex]; + } + + entries[paletteIndex] = Color.FromArgb(255, _reds[paletteIndex], _greens[paletteIndex], _blues[paletteIndex]); + } + _resultBitmap.NativeBitmap.Palette = imagePalette; + + // Make sure the bitmap is not disposed, as we return it. + var tmpBitmap = _resultBitmap; + _resultBitmap = null; + return tmpBitmap; + } + + /// + /// Converts the histogram to a series of moments. + /// + private void CalculateMoments() + { + var area = new long[Sidesize]; + var areaRed = new long[Sidesize]; + var areaGreen = new long[Sidesize]; + var areaBlue = new long[Sidesize]; + var area2 = new float[Sidesize]; + + for (var redIndex = 1; redIndex <= Maxsideindex; ++redIndex) + { + for (var index = 0; index <= Maxsideindex; ++index) + { + area[index] = 0; + areaRed[index] = 0; + areaGreen[index] = 0; + areaBlue[index] = 0; + area2[index] = 0; + } + + for (var greenIndex = 1; greenIndex <= Maxsideindex; ++greenIndex) + { + long line = 0; + long lineRed = 0; + long lineGreen = 0; + long lineBlue = 0; + var line2 = 0.0f; + + for (var blueIndex = 1; blueIndex <= Maxsideindex; ++blueIndex) + { + line += _weights[redIndex, greenIndex, blueIndex]; + lineRed += _momentsRed[redIndex, greenIndex, blueIndex]; + lineGreen += _momentsGreen[redIndex, greenIndex, blueIndex]; + lineBlue += _momentsBlue[redIndex, greenIndex, blueIndex]; + line2 += _moments[redIndex, greenIndex, blueIndex]; + + area[blueIndex] += line; + areaRed[blueIndex] += lineRed; + areaGreen[blueIndex] += lineGreen; + areaBlue[blueIndex] += lineBlue; + area2[blueIndex] += line2; + + _weights[redIndex, greenIndex, blueIndex] = _weights[redIndex - 1, greenIndex, blueIndex] + area[blueIndex]; + _momentsRed[redIndex, greenIndex, blueIndex] = _momentsRed[redIndex - 1, greenIndex, blueIndex] + areaRed[blueIndex]; + _momentsGreen[redIndex, greenIndex, blueIndex] = _momentsGreen[redIndex - 1, greenIndex, blueIndex] + areaGreen[blueIndex]; + _momentsBlue[redIndex, greenIndex, blueIndex] = _momentsBlue[redIndex - 1, greenIndex, blueIndex] + areaBlue[blueIndex]; + _moments[redIndex, greenIndex, blueIndex] = _moments[redIndex - 1, greenIndex, blueIndex] + area2[blueIndex]; + } + } + } + } + + /// + /// Computes the volume of the cube in a specific moment. + /// + private static long Volume(WuColorCubeOld cube, long[,,] moment) + { + return moment[cube.RedMaximum, cube.GreenMaximum, cube.BlueMaximum] - + moment[cube.RedMaximum, cube.GreenMaximum, cube.BlueMinimum] - + moment[cube.RedMaximum, cube.GreenMinimum, cube.BlueMaximum] + + moment[cube.RedMaximum, cube.GreenMinimum, cube.BlueMinimum] - + moment[cube.RedMinimum, cube.GreenMaximum, cube.BlueMaximum] + + moment[cube.RedMinimum, cube.GreenMaximum, cube.BlueMinimum] + + moment[cube.RedMinimum, cube.GreenMinimum, cube.BlueMaximum] - + moment[cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum]; + } + + /// + /// Computes the volume of the cube in a specific moment. For the floating-point values. + /// + private static float VolumeFloat(WuColorCubeOld cube, float[,,] moment) + { + return moment[cube.RedMaximum, cube.GreenMaximum, cube.BlueMaximum] - + moment[cube.RedMaximum, cube.GreenMaximum, cube.BlueMinimum] - + moment[cube.RedMaximum, cube.GreenMinimum, cube.BlueMaximum] + + moment[cube.RedMaximum, cube.GreenMinimum, cube.BlueMinimum] - + moment[cube.RedMinimum, cube.GreenMaximum, cube.BlueMaximum] + + moment[cube.RedMinimum, cube.GreenMaximum, cube.BlueMinimum] + + moment[cube.RedMinimum, cube.GreenMinimum, cube.BlueMaximum] - + moment[cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum]; + } + + /// + /// Splits the cube in given position, and color direction. + /// + private static long Top(WuColorCubeOld cube, int direction, int position, long[,,] moment) => + direction switch + { + Red => (moment[position, cube.GreenMaximum, cube.BlueMaximum] - + moment[position, cube.GreenMaximum, cube.BlueMinimum] - + moment[position, cube.GreenMinimum, cube.BlueMaximum] + + moment[position, cube.GreenMinimum, cube.BlueMinimum]), + Green => (moment[cube.RedMaximum, position, cube.BlueMaximum] - + moment[cube.RedMaximum, position, cube.BlueMinimum] - + moment[cube.RedMinimum, position, cube.BlueMaximum] + + moment[cube.RedMinimum, position, cube.BlueMinimum]), + Blue => (moment[cube.RedMaximum, cube.GreenMaximum, position] - + moment[cube.RedMaximum, cube.GreenMinimum, position] - + moment[cube.RedMinimum, cube.GreenMaximum, position] + + moment[cube.RedMinimum, cube.GreenMinimum, position]), + _ => 0 + }; + + /// + /// Splits the cube in a given color direction at its minimum. + /// + private static long Bottom(WuColorCubeOld cube, int direction, long[,,] moment) => + direction switch + { + Red => (-moment[cube.RedMinimum, cube.GreenMaximum, cube.BlueMaximum] + + moment[cube.RedMinimum, cube.GreenMaximum, cube.BlueMinimum] + + moment[cube.RedMinimum, cube.GreenMinimum, cube.BlueMaximum] - + moment[cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum]), + Green => (-moment[cube.RedMaximum, cube.GreenMinimum, cube.BlueMaximum] + + moment[cube.RedMaximum, cube.GreenMinimum, cube.BlueMinimum] + + moment[cube.RedMinimum, cube.GreenMinimum, cube.BlueMaximum] - + moment[cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum]), + Blue => (-moment[cube.RedMaximum, cube.GreenMaximum, cube.BlueMinimum] + + moment[cube.RedMaximum, cube.GreenMinimum, cube.BlueMinimum] + + moment[cube.RedMinimum, cube.GreenMaximum, cube.BlueMinimum] - + moment[cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum]), + _ => 0 + }; + + /// + /// Calculates statistical variance for a given cube. + /// + private float CalculateVariance(WuColorCubeOld cube) + { + float volumeRed = Volume(cube, _momentsRed); + float volumeGreen = Volume(cube, _momentsGreen); + float volumeBlue = Volume(cube, _momentsBlue); + var volumeMoment = VolumeFloat(cube, _moments); + float volumeWeight = Volume(cube, _weights); + + var distance = volumeRed * volumeRed + volumeGreen * volumeGreen + volumeBlue * volumeBlue; + + return volumeMoment - distance / volumeWeight; + } + + /// + /// Finds the optimal (maximal) position for the cut. + /// + private float Maximize(WuColorCubeOld cube, int direction, int first, int last, int[] cut, long wholeRed, long wholeGreen, long wholeBlue, long wholeWeight) + { + var bottomRed = Bottom(cube, direction, _momentsRed); + var bottomGreen = Bottom(cube, direction, _momentsGreen); + var bottomBlue = Bottom(cube, direction, _momentsBlue); + var bottomWeight = Bottom(cube, direction, _weights); + + var result = 0.0f; + cut[0] = -1; + + for (var position = first; position < last; ++position) + { + // determines the cube cut at a certain position + var halfRed = bottomRed + Top(cube, direction, position, _momentsRed); + var halfGreen = bottomGreen + Top(cube, direction, position, _momentsGreen); + var halfBlue = bottomBlue + Top(cube, direction, position, _momentsBlue); + var halfWeight = bottomWeight + Top(cube, direction, position, _weights); + + // the cube cannot be cut at bottom (this would lead to empty cube) + if (halfWeight != 0) + { + var halfDistance = (float) halfRed * halfRed + (float) halfGreen * halfGreen + (float) halfBlue * halfBlue; + var temp = halfDistance / halfWeight; + + halfRed = wholeRed - halfRed; + halfGreen = wholeGreen - halfGreen; + halfBlue = wholeBlue - halfBlue; + halfWeight = wholeWeight - halfWeight; + + if (halfWeight != 0) + { + halfDistance = (float) halfRed * halfRed + (float) halfGreen * halfGreen + (float) halfBlue * halfBlue; + temp += halfDistance / halfWeight; + + if (temp > result) + { + result = temp; + cut[0] = position; + } + } + } + } + + return result; + } + + /// + /// Cuts a cube with another one. + /// + private bool Cut(WuColorCubeOld first, WuColorCubeOld second) + { + int direction; + + int[] cutRed = {0}; + int[] cutGreen = {0}; + int[] cutBlue = {0}; + + var wholeRed = Volume(first, _momentsRed); + var wholeGreen = Volume(first, _momentsGreen); + var wholeBlue = Volume(first, _momentsBlue); + var wholeWeight = Volume(first, _weights); + + var maxRed = Maximize(first, Red, first.RedMinimum + 1, first.RedMaximum, cutRed, wholeRed, wholeGreen, wholeBlue, wholeWeight); + var maxGreen = Maximize(first, Green, first.GreenMinimum + 1, first.GreenMaximum, cutGreen, wholeRed, wholeGreen, wholeBlue, wholeWeight); + var maxBlue = Maximize(first, Blue, first.BlueMinimum + 1, first.BlueMaximum, cutBlue, wholeRed, wholeGreen, wholeBlue, wholeWeight); + + if (maxRed >= maxGreen && maxRed >= maxBlue) + { + direction = Red; + + // cannot split empty cube + if (cutRed[0] < 0) + { + return false; + } + } + else + { + if (maxGreen >= maxRed && maxGreen >= maxBlue) + { + direction = Green; + } + else + { + direction = Blue; + } + } + + second.RedMaximum = first.RedMaximum; + second.GreenMaximum = first.GreenMaximum; + second.BlueMaximum = first.BlueMaximum; + + // cuts in a certain direction + switch (direction) + { + case Red: + second.RedMinimum = first.RedMaximum = cutRed[0]; + second.GreenMinimum = first.GreenMinimum; + second.BlueMinimum = first.BlueMinimum; + break; + + case Green: + second.GreenMinimum = first.GreenMaximum = cutGreen[0]; + second.RedMinimum = first.RedMinimum; + second.BlueMinimum = first.BlueMinimum; + break; + + case Blue: + second.BlueMinimum = first.BlueMaximum = cutBlue[0]; + second.RedMinimum = first.RedMinimum; + second.GreenMinimum = first.GreenMinimum; + break; + } + + // determines the volumes after cut + first.Volume = (first.RedMaximum - first.RedMinimum) * (first.GreenMaximum - first.GreenMinimum) * (first.BlueMaximum - first.BlueMinimum); + second.Volume = (second.RedMaximum - second.RedMinimum) * (second.GreenMaximum - second.GreenMinimum) * (second.BlueMaximum - second.BlueMinimum); + + // the cut was successfull + return true; + } + + /// + /// Marks all the tags with a given label. + /// + private static void Mark(WuColorCubeOld cube, int label, byte[] tag) + { + for (var redIndex = cube.RedMinimum + 1; redIndex <= cube.RedMaximum; ++redIndex) + { + for (var greenIndex = cube.GreenMinimum + 1; greenIndex <= cube.GreenMaximum; ++greenIndex) + { + for (var blueIndex = cube.BlueMinimum + 1; blueIndex <= cube.BlueMaximum; ++blueIndex) + { + tag[(redIndex << 10) + (redIndex << 6) + redIndex + (greenIndex << 5) + greenIndex + blueIndex] = (byte) label; + } + } + } + } + } +} \ No newline at end of file diff --git a/src/Greenshot.Gfx/Structs/Bgr32.cs b/src/Greenshot.Gfx/Structs/Bgr32.cs index 3e4514f5b..6d3603b2f 100644 --- a/src/Greenshot.Gfx/Structs/Bgr32.cs +++ b/src/Greenshot.Gfx/Structs/Bgr32.cs @@ -1,19 +1,19 @@ // Greenshot - a free and open source screenshot tool // Copyright (C) 2007-2020 Thomas Braun, Jens Klingen, Robin Krom -// +// // For more information see: http://getgreenshot.org/ // The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot -// +// // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 1 of the License, or // (at your option) any later version. -// +// // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with this program. If not, see . @@ -49,7 +49,7 @@ namespace Greenshot.Gfx.Structs /// Equal /// public static bool operator ==(Bgr32 left, Bgr32 right) => Equals(left, right); - + /// /// Not Equal /// diff --git a/src/Greenshot.Gfx/Structs/PixelExtensions.cs b/src/Greenshot.Gfx/Structs/PixelExtensions.cs index f11a4c584..61393e1d9 100644 --- a/src/Greenshot.Gfx/Structs/PixelExtensions.cs +++ b/src/Greenshot.Gfx/Structs/PixelExtensions.cs @@ -1,19 +1,19 @@ // Greenshot - a free and open source screenshot tool // Copyright (C) 2007-2020 Thomas Braun, Jens Klingen, Robin Krom -// +// // For more information see: http://getgreenshot.org/ // The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot -// +// // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 1 of the License, or // (at your option) any later version. -// +// // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with this program. If not, see . @@ -26,6 +26,34 @@ namespace Greenshot.Gfx.Structs /// public static class PixelExtensions { + /// + public static Bgr24 BackgroundBlendColor { get; set; } + + /// + /// Make a Brga32 from the specified color + /// + /// Color + /// Bgr32 + public static Bgra32 BlendedColor(this Bgra32 color) + { + if (color.A == 255) + { + return color; + } + + // As the request is to get without alpha, we blend. + var rem = 255 - color.A; + var red = (byte)((color.R * color.A + BackgroundBlendColor.R * rem) / 255); + var green = (byte)((color.G * color.A + BackgroundBlendColor.G * rem) / 255); + var blue = (byte)((color.B * color.A + BackgroundBlendColor.B * rem) / 255); + return new Bgra32 + { + A = 255, + R = red, + G = green, + B = blue, + }; + } /// /// Make a Brga32 from the specified color /// @@ -55,6 +83,27 @@ namespace Greenshot.Gfx.Structs G = color.G, B = color.B, }; - } + } + + /// + /// Make a color from the specified Brg32 + /// + /// Bgr32 + /// Color + public static Color ToColor(this Bgr32 color) + { + return Color.FromArgb(color.R, color.G, color.B); + } + + + /// + /// Make a color from the specified uint + /// + /// uint + /// Color + public static Color ToColor(this uint color) + { + return Color.FromArgb((int)color); + } } } \ No newline at end of file diff --git a/src/Greenshot.Gfx/UnmanagedBitmap.cs b/src/Greenshot.Gfx/UnmanagedBitmap.cs index 8dc20f136..fd0178f36 100644 --- a/src/Greenshot.Gfx/UnmanagedBitmap.cs +++ b/src/Greenshot.Gfx/UnmanagedBitmap.cs @@ -1,19 +1,19 @@ // Greenshot - a free and open source screenshot tool // Copyright (C) 2007-2020 Thomas Braun, Jens Klingen, Robin Krom -// +// // For more information see: http://getgreenshot.org/ // The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot -// +// // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 1 of the License, or // (at your option) any later version. -// +// // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with this program. If not, see . @@ -138,11 +138,12 @@ namespace Greenshot.Gfx Bgr24 _ => PixelFormat.Format24bppRgb, Bgra32 _ => PixelFormat.Format32bppArgb, Bgr32 _ => PixelFormat.Format32bppRgb, + byte _ => PixelFormat.Format8bppIndexed, _ => throw new NotSupportedException("Unknown pixel format") }; } } - + /// /// Returns the pixel format for this Unmanaged bitmap /// @@ -156,6 +157,7 @@ namespace Greenshot.Gfx Bgr24 _ => PixelFormats.Bgr24, Bgra32 _ => PixelFormats.Bgra32, Bgr32 _ => PixelFormats.Bgr32, + byte _ => PixelFormats.Indexed8, _ => throw new NotSupportedException("Unknown pixel format") }; } @@ -171,7 +173,7 @@ namespace Greenshot.Gfx /// Convert this to a real bitmap /// /// Bitmap - public Bitmap NativeBitmap + public virtual Bitmap NativeBitmap { get { @@ -182,7 +184,7 @@ namespace Greenshot.Gfx return _nativeBitmap; } } - + /// /// Convert this to a real bitmap /// diff --git a/src/Greenshot.PerformanceTests/GfxPerformance.cs b/src/Greenshot.PerformanceTests/GfxPerformance.cs index 253fc05f7..290e2c9e7 100644 --- a/src/Greenshot.PerformanceTests/GfxPerformance.cs +++ b/src/Greenshot.PerformanceTests/GfxPerformance.cs @@ -1,19 +1,19 @@ // Greenshot - a free and open source screenshot tool // Copyright (C) 2007-2020 Thomas Braun, Jens Klingen, Robin Krom -// +// // For more information see: http://getgreenshot.org/ // The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot -// +// // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 1 of the License, or // (at your option) any later version. -// +// // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with this program. If not, see . @@ -53,10 +53,31 @@ namespace Greenshot.PerformanceTests [Benchmark] - [Arguments(PixelFormat.Format24bppRgb)] - [Arguments(PixelFormat.Format32bppRgb)] - [Arguments(PixelFormat.Format32bppArgb)] - public void WuQuantizer(PixelFormat pixelFormat) + //[Arguments(PixelFormat.Format24bppRgb)] + [Arguments(PixelFormat.Format32bppRgb, 256)] + [Arguments(PixelFormat.Format32bppRgb, 128)] + //[Arguments(PixelFormat.Format32bppArgb)] + public void WuQuantizer(PixelFormat pixelFormat, int maxColors) + { + var bitmap = new UnmanagedBitmap(400, 400); + bitmap.Span.Fill(Color.White.FromColor()); + using (var graphics = Graphics.FromImage(bitmap.NativeBitmap)) + { + using var pen = new SolidBrush(Color.Blue); + graphics.FillRectangle(pen, new Rectangle(30, 30, 340, 340)); + } + + var quantizer = new WuQuantizer(bitmap); + using var quantizedImage = quantizer.GetQuantizedImage(maxColors); + quantizedImage.NativeBitmap.Save(@"quantized.png", ImageFormat.Png); + } + + [Benchmark] + //[Arguments(PixelFormat.Format24bppRgb)] + [Arguments(PixelFormat.Format32bppRgb, 256)] + [Arguments(PixelFormat.Format32bppRgb, 128)] + //[Arguments(PixelFormat.Format32bppArgb)] + public void WuQuantizerOld(PixelFormat pixelFormat, int maxColors) { using var bitmap = BitmapFactory.CreateEmpty(400, 400, pixelFormat, Color.White); using (var graphics = Graphics.FromImage(bitmap.NativeBitmap)) @@ -65,12 +86,12 @@ namespace Greenshot.PerformanceTests graphics.FillRectangle(pen, new Rectangle(30, 30, 340, 340)); } - var quantizer = new WuQuantizer(bitmap); - using var quantizedImage = quantizer.GetQuantizedImage(); + var quantizer = new WuQuantizerOld(bitmap); + using var quantizedImage = quantizer.GetQuantizedImage(maxColors); quantizedImage.NativeBitmap.Save(@"quantized.png", ImageFormat.Png); } - [Benchmark] + //[Benchmark] [Arguments(PixelFormat.Format24bppRgb)] [Arguments(PixelFormat.Format32bppRgb)] [Arguments(PixelFormat.Format32bppArgb)] @@ -86,7 +107,7 @@ namespace Greenshot.PerformanceTests bitmap.ApplyBoxBlur(10); } - [Benchmark] + //[Benchmark] public void Blur_UnmanagedBitmap() { using var unmanagedBitmap = new UnmanagedBitmap(400, 400); @@ -100,7 +121,7 @@ namespace Greenshot.PerformanceTests unmanagedBitmap.ApplyBoxBlur(10); } - [Benchmark] + //[Benchmark] public void Blur_Old() { using var bitmap = BitmapFactory.CreateEmpty(400, 400, PixelFormat.Format32bppRgb, Color.White); @@ -113,37 +134,37 @@ namespace Greenshot.PerformanceTests BoxBlurOld.ApplyOldBoxBlur(bitmap, 10); } - [Benchmark] + //[Benchmark] public void Scale2x_FastBitmap() { ScaleX.Scale2X(_unmanagedTestBitmap).Dispose(); } - [Benchmark] + //[Benchmark] public void Scale2x_Unmanaged() { _unmanagedTestBitmap.Scale2X().Dispose(); } - [Benchmark] + //[Benchmark] public void Scale2x_Unmanaged_Reference() { _unmanagedTestBitmap.Scale2XReference().Dispose(); } - [Benchmark] + //[Benchmark] public void Scale3x_FastBitmap() { ScaleX.Scale3X(_unmanagedTestBitmap).Dispose(); } - [Benchmark] + //[Benchmark] public void Scale3x_Unmanaged() { _unmanagedTestBitmap.Scale3X().Dispose(); } - [Benchmark] + //[Benchmark] public void Scale3x_Unmanaged_Reference() { _unmanagedTestBitmap.Scale3XReference().Dispose(); diff --git a/src/Greenshot.PerformanceTests/Greenshot.PerformanceTests.csproj b/src/Greenshot.PerformanceTests/Greenshot.PerformanceTests.csproj index a3d107fd6..91240097d 100644 --- a/src/Greenshot.PerformanceTests/Greenshot.PerformanceTests.csproj +++ b/src/Greenshot.PerformanceTests/Greenshot.PerformanceTests.csproj @@ -16,10 +16,10 @@ - - - + + + - + \ No newline at end of file diff --git a/src/Greenshot.Tests/Greenshot.Tests.csproj b/src/Greenshot.Tests/Greenshot.Tests.csproj index 1610a9611..98e055cee 100644 --- a/src/Greenshot.Tests/Greenshot.Tests.csproj +++ b/src/Greenshot.Tests/Greenshot.Tests.csproj @@ -28,15 +28,15 @@ - - + + - + - + runtime; build; native; contentfiles; analyzers all @@ -51,6 +51,9 @@ + + PreserveNewest + PreserveNewest diff --git a/src/Greenshot.Tests/QuantizeTests.cs b/src/Greenshot.Tests/QuantizeTests.cs index deed44eae..1aa283879 100644 --- a/src/Greenshot.Tests/QuantizeTests.cs +++ b/src/Greenshot.Tests/QuantizeTests.cs @@ -1,26 +1,29 @@ // Greenshot - a free and open source screenshot tool // Copyright (C) 2007-2020 Thomas Braun, Jens Klingen, Robin Krom -// +// // For more information see: http://getgreenshot.org/ // The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot -// +// // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 1 of the License, or // (at your option) any later version. -// +// // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with this program. If not, see . using System.Drawing; using System.Drawing.Imaging; +using System.IO; using Greenshot.Gfx; +using Greenshot.Gfx.Formats; using Greenshot.Gfx.Quantizer; +using Greenshot.Gfx.Structs; using Xunit; namespace Greenshot.Tests @@ -30,19 +33,58 @@ namespace Greenshot.Tests /// public class QuantizeTests { + public QuantizeTests() + { + BitmapHelper.RegisterFormatReader(); + } + + [Fact] + public void Test_WuQuantizer_128() + { + var bitmap = BitmapHelper.LoadBitmap(@"TestFiles\paiS7wOGKN0XA1wIaq8qHNoWQqq64wnFu3svA9Ux.jpeg"); + + var quantizerOld = new WuQuantizerOld(bitmap); + using var quantizedImageOld = quantizerOld.GetQuantizedImage(128); + quantizedImageOld.NativeBitmap.Save(@"quantized1.png", ImageFormat.Png); + + var quantizer = new WuQuantizer(bitmap); + using var quantizedImage = quantizer.GetQuantizedImage(128); + quantizedImage.NativeBitmap.Save(@"quantized2.png", ImageFormat.Png); + + Assert.Equal(quantizerOld.GetColorCount(), quantizer.GetColorCount()); + FileAssert.AreEqual("quantized1.png", "quantized2.png"); + File.Delete("quantized1.png"); + File.Delete("quantized2.png"); + } + [Fact] public void Test_WuQuantizer() { - using var bitmap = BitmapFactory.CreateEmpty(400, 400, PixelFormat.Format24bppRgb, Color.White); + using var bitmap = BitmapFactory.CreateEmpty(400, 400, PixelFormat.Format32bppArgb, Color.White); using (var graphics = Graphics.FromImage(bitmap.NativeBitmap)) { using var pen = new SolidBrush(Color.Blue); graphics.FillRectangle(pen, new Rectangle(30, 30, 340, 340)); } - var quantizer = new WuQuantizer(bitmap); + var quantizerOld = new WuQuantizerOld(bitmap); + using var quantizedImageOld = quantizerOld.GetQuantizedImage(); + quantizedImageOld.NativeBitmap.Save(@"quantized1.png", ImageFormat.Png); + + var bitmap2 = new UnmanagedBitmap(400, 400); + bitmap2.Span.Fill(Color.White.FromColor()); + using (var graphics = Graphics.FromImage(bitmap2.NativeBitmap)) + { + using var pen = new SolidBrush(Color.Blue); + graphics.FillRectangle(pen, new Rectangle(30, 30, 340, 340)); + } + + var quantizer = new WuQuantizer(bitmap2); using var quantizedImage = quantizer.GetQuantizedImage(); - quantizedImage.NativeBitmap.Save(@"quantized.png", ImageFormat.Png); + quantizedImage.NativeBitmap.Save(@"quantized2.png", ImageFormat.Png); + + FileAssert.AreEqual("quantized1.png", "quantized2.png"); } + } } diff --git a/src/Greenshot.Tests/ScaleXTests.cs b/src/Greenshot.Tests/ScaleXTests.cs index 9a5b05f81..82c3eb466 100644 --- a/src/Greenshot.Tests/ScaleXTests.cs +++ b/src/Greenshot.Tests/ScaleXTests.cs @@ -1,19 +1,19 @@ // Greenshot - a free and open source screenshot tool // Copyright (C) 2007-2020 Thomas Braun, Jens Klingen, Robin Krom -// +// // For more information see: http://getgreenshot.org/ // The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot -// +// // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 1 of the License, or // (at your option) any later version. -// +// // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with this program. If not, see . @@ -37,7 +37,7 @@ namespace Greenshot.Tests { LogSettings.RegisterDefaultLogger(LogLevels.Verbose, testOutputHelper); } - + [Fact] public void Test_Scale2X_UnmanagedBitmap() { diff --git a/src/Greenshot.Tests/TestFiles/paiS7wOGKN0XA1wIaq8qHNoWQqq64wnFu3svA9Ux.jpeg b/src/Greenshot.Tests/TestFiles/paiS7wOGKN0XA1wIaq8qHNoWQqq64wnFu3svA9Ux.jpeg new file mode 100644 index 000000000..198910cf9 Binary files /dev/null and b/src/Greenshot.Tests/TestFiles/paiS7wOGKN0XA1wIaq8qHNoWQqq64wnFu3svA9Ux.jpeg differ diff --git a/src/Greenshot/Greenshot.csproj b/src/Greenshot/Greenshot.csproj index b1558bdbb..024cae4c8 100644 --- a/src/Greenshot/Greenshot.csproj +++ b/src/Greenshot/Greenshot.csproj @@ -34,16 +34,16 @@ - + - + - - - - + + + + diff --git a/src/global.json b/src/global.json index c3d298e9f..e6509422e 100644 --- a/src/global.json +++ b/src/global.json @@ -1,6 +1,7 @@ { "sdk": { "version": "3.1.100", - "rollForward": "latestPatch" + "rollForward": "latestMajor", + "allowPrerelease": true } } \ No newline at end of file