Working state before .NET 5 upgrade

This commit is contained in:
Robin Krom 2020-07-20 11:49:19 +02:00
commit 56ccfd4d2a
28 changed files with 1203 additions and 284 deletions

View file

@ -58,7 +58,7 @@
</PropertyGroup>
<ItemGroup Condition="!$(MSBuildProjectName.Contains('Tests')) And $(MSBuildProjectName.StartsWith('Greenshot'))">
<PackageReference Include="Nerdbank.GitVersioning" Version="3.0.50">
<PackageReference Include="Nerdbank.GitVersioning" Version="3.1.91">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>

View file

@ -24,6 +24,6 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Dapplo.Confluence" Version="0.9.7" />
<PackageReference Include="Dapplo.Confluence" Version="1.0.16" />
</ItemGroup>
</Project>

View file

@ -19,6 +19,6 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="CliWrap" Version="3.0.0" />
<PackageReference Include="CliWrap" Version="3.1.0" />
</ItemGroup>
</Project>

View file

@ -6,7 +6,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Dapplo.Windows.Com" Version="0.11.15" />
<PackageReference Include="Dapplo.Windows.Com" Version="0.11.17" />
<PackageReference Include="Unofficial.Microsoft.mshtml" Version="7.0.3300" />
</ItemGroup>

View file

@ -20,7 +20,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Dapplo.Jira" Version="0.9.19" />
<PackageReference Include="DynamicData" Version="6.14.8" />
<PackageReference Include="Dapplo.Jira" Version="0.9.28" />
<PackageReference Include="DynamicData" Version="6.16.1" />
</ItemGroup>
</Project>

View file

@ -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 <http://www.gnu.org/licenses/>.
@ -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<Bgra32>(bitmapToSave);
var colorCount = quantizer.GetColorCount();
Log.Info().WriteLine("Image with format {0} has {1} colors", bitmapToSave.PixelFormat, colorCount);
if (!outputSettings.ReduceColors && colorCount >= 256)

View file

@ -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 <http://www.gnu.org/licenses/>.
@ -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
/// <returns>byte[]</returns>
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);

View file

@ -20,15 +20,15 @@
<PackageReference Include="Dapplo.CaliburnMicro.Metro" Version="2.1.10" />
<PackageReference Include="Dapplo.CaliburnMicro.Toasts" Version="2.1.10" />
<PackageReference Include="Dapplo.CaliburnMicro.Translations" Version="2.1.10" />
<PackageReference Include="Dapplo.HttpExtensions" Version="0.10.9" />
<PackageReference Include="Dapplo.HttpExtensions.JsonNet" Version="0.10.9" />
<PackageReference Include="Dapplo.HttpExtensions.OAuth" Version="0.10.9" />
<PackageReference Include="Dapplo.Windows.Clipboard" Version="0.11.15" />
<PackageReference Include="Dapplo.Windows.Dpi" Version="0.11.15" />
<PackageReference Include="Dapplo.Windows.Icons" Version="0.11.15" />
<PackageReference Include="Dapplo.HttpExtensions" Version="1.0.3" />
<PackageReference Include="Dapplo.HttpExtensions.JsonNet" Version="1.0.3" />
<PackageReference Include="Dapplo.HttpExtensions.OAuth" Version="1.0.3" />
<PackageReference Include="Dapplo.Windows.Clipboard" Version="0.11.17" />
<PackageReference Include="Dapplo.Windows.Dpi" Version="0.11.17" />
<PackageReference Include="Dapplo.Windows.Icons" Version="0.11.17" />
<PackageReference Include="gong-wpf-dragdrop" Version="2.2.0" />
<PackageReference Include="MahApps.Metro.IconPacks" Version="3.3.0" />
<PackageReference Include="Svg" Version="3.0.102" />
<PackageReference Include="Svg" Version="3.1.1" />
</ItemGroup>
<ItemGroup>

View file

@ -6,7 +6,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Dapplo.Windows.Icons" Version="0.11.15" />
<PackageReference Include="Dapplo.Windows.Icons" Version="0.11.17" />
</ItemGroup>
<ItemGroup>

View file

@ -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 <http://www.gnu.org/licenses/>.
@ -46,7 +46,7 @@ namespace Greenshot.Gfx
private static readonly LogSource Log = new LogSource();
/// <summary>
/// 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.
/// </summary>
public static IDictionary<string, IImageFormatReader> StreamConverters { get; } = new Dictionary<string, IImageFormatReader>(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();

View file

@ -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 <http://www.gnu.org/licenses/>.
using System;
namespace Greenshot.Gfx
{
/// <summary>
/// A bitmap wrapper with memory from Marshal.AllocHGlobal
/// </summary>
/// <typeparam name="TPixelLayout">struct for the pixel information</typeparam>
public class ChunkyBitmap : UnmanagedBitmap<byte>
{
/// <summary>
/// The constructor for the UnmanagedBitmap
/// </summary>
/// <param name="width">int</param>
/// <param name="height">int</param>
/// <param name="horizontalPixelsPerInch">float</param>
/// <param name="verticalPixelsPerInch">float</param>
public ChunkyBitmap(int width, int height, float horizontalPixelsPerInch = 0.96f, float verticalPixelsPerInch = 0.96f) : base(width, height, horizontalPixelsPerInch, verticalPixelsPerInch)
{
}
/// <summary>
/// The constructor for the UnmanagedBitmap with already initialized bits
/// </summary>
/// <param name="bits">IntPtr to the bits, this will not be freed</param>
/// <param name="width">int</param>
/// <param name="height">int</param>
/// <param name="horizontalPixelsPerInch">float</param>
/// <param name="verticalPixelsPerInch">float</param>
public ChunkyBitmap(IntPtr bits, int width, int height, float horizontalPixelsPerInch = 0.96f, float verticalPixelsPerInch = 0.96f) : base(bits, width, height, horizontalPixelsPerInch, verticalPixelsPerInch)
{
}
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
@ -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
/// <inheritdoc />
public IBitmapWithNativeSupport Apply(IBitmapWithNativeSupport sourceBitmap, Matrix matrix)
{
using (var quantizer = new WuQuantizer(sourceBitmap))
using (var quantizer = new WuQuantizer<Bgra32>(sourceBitmap))
{
var colorCount = quantizer.GetColorCount();
if (colorCount <= Colors)

View file

@ -8,8 +8,8 @@
<ItemGroup>
<PackageReference Include="Dapplo.Addons" Version="1.3.3" />
<PackageReference Include="Dapplo.Log" Version="1.3.26" />
<PackageReference Include="Dapplo.Windows.Dpi" Version="0.11.15" />
<PackageReference Include="Svg" Version="3.0.102" />
<PackageReference Include="System.Memory" Version="4.5.3" />
<PackageReference Include="Dapplo.Windows.Dpi" Version="0.11.17" />
<PackageReference Include="Svg" Version="3.1.1" />
<PackageReference Include="System.Memory" Version="4.5.4" />
</ItemGroup>
</Project>

View file

@ -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 <http://www.gnu.org/licenses/>.
namespace Greenshot.Gfx.Quantizer
{
internal class WuColorCube
internal struct WuColorCube
{
/// <summary>
/// Gets or sets the red minimum.

View file

@ -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 <http://www.gnu.org/licenses/>.
namespace Greenshot.Gfx.Quantizer
{
internal class WuColorCubeOld
{
/// <summary>
/// Gets or sets the red minimum.
/// </summary>
/// <value>The red minimum.</value>
public int RedMinimum { get; set; }
/// <summary>
/// Gets or sets the red maximum.
/// </summary>
/// <value>The red maximum.</value>
public int RedMaximum { get; set; }
/// <summary>
/// Gets or sets the green minimum.
/// </summary>
/// <value>The green minimum.</value>
public int GreenMinimum { get; set; }
/// <summary>
/// Gets or sets the green maximum.
/// </summary>
/// <value>The green maximum.</value>
public int GreenMaximum { get; set; }
/// <summary>
/// Gets or sets the blue minimum.
/// </summary>
/// <value>The blue minimum.</value>
public int BlueMinimum { get; set; }
/// <summary>
/// Gets or sets the blue maximum.
/// </summary>
/// <value>The blue maximum.</value>
public int BlueMaximum { get; set; }
/// <summary>
/// Gets or sets the cube volume.
/// </summary>
/// <value>The volume.</value>
public int Volume { get; set; }
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
@ -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
{
/// <summary>
/// Implementation of the WuQuantizer algorithm
/// </summary>
public class WuQuantizer : IDisposable
public class WuQuantizer<TPixel> : 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<TPixel> _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;
/// <summary>
/// The constructor for the WuQauntizer
/// </summary>
/// <param name="sourceBitmap"></param>
public WuQuantizer(IBitmapWithNativeSupport sourceBitmap)
/// <param name="sourceBitmap">IBitmapWithNativeSupport</param>
public WuQuantizer(IBitmapWithNativeSupport sourceBitmap) : this(sourceBitmap as UnmanagedBitmap<TPixel>)
{
}
/// <summary>
/// The constructor for the WuQauntizer
/// </summary>
/// <param name="sourceBitmap">UnmanagedBitmap of TPixel</param>
public WuQuantizer(UnmanagedBitmap<TPixel> 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<int> 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<TPixel, Bgra32>(_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();
}
/// <inheritdoc/>
@ -155,7 +140,7 @@ namespace Greenshot.Gfx.Quantizer
Dispose(true);
}
/// <summary>
/// Dispose implementation
/// </summary>
@ -191,39 +176,30 @@ namespace Greenshot.Gfx.Quantizer
/// <returns>Bitmap</returns>
public IBitmapWithNativeSupport SimpleReindex()
{
var colors = new List<Color>();
var lookup = new Dictionary<Color, byte>();
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<uint> colors = stackalloc uint[256];
var lookup = new Dictionary<uint, byte>();
byte colorCount = 0;
for (var y = 0; y < _sourceBitmap.Height; y++)
{
var srcSpan = MemoryMarshal.Cast<TPixel, uint>(_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<int> lookupRed = stackalloc int[Maxcolor];
Span<int> lookupGreen = stackalloc int[Maxcolor];
Span<int> lookupBlue = stackalloc int[Maxcolor];
_tag = new byte[Maxvolume];
Span<byte> 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<int> reds = stackalloc int[allowedColorCount + 1];
Span<int> greens = stackalloc int[allowedColorCount + 1];
Span<int> blues = stackalloc int[allowedColorCount + 1];
Span<int> sums = stackalloc int[allowedColorCount + 1];
Log.Info().WriteLine("Starting bitmap reconstruction...");
using (var dest = FastBitmapFactory.Create(_resultBitmap) as FastChunkyBitmap)
var lookup = new Dictionary<Bgra32, byte>();
for (var y = 0; y < _sourceBitmap.Height; y++)
{
using var src = FastBitmapFactory.Create(_sourceBitmap);
var lookup = new Dictionary<Color, byte>();
for (var y = 0; y < src.Height; y++)
var destSpan = _resultBitmap[y];
var srcSpan = MemoryMarshal.Cast<TPixel, Bgra32>(_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
/// </summary>
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<long> area = stackalloc long[Sidesize];
Span<long> areaRed = stackalloc long[Sidesize];
Span<long> areaGreen = stackalloc long[Sidesize];
Span<long> areaBlue = stackalloc long[Sidesize];
var area2 = new float[Sidesize];
for (var redIndex = 1; redIndex <= Maxsideindex; ++redIndex)
{
@ -483,10 +443,11 @@ namespace Greenshot.Gfx.Quantizer
}
}
/// <summary>
/// Computes the volume of the cube in a specific moment.
/// </summary>
private static long Volume(WuColorCube cube, long[,,] moment)
/// <summary>
/// Computes the volume of the cube in a specific moment.
/// </summary>
[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];
}
/// <summary>
/// Computes the volume of the cube in a specific moment. For the floating-point values.
/// </summary>
private static float VolumeFloat(WuColorCube cube, float[,,] moment)
/// <summary>
/// Computes the volume of the cube in a specific moment. For the floating-point values.
/// </summary>
[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];
}
/// <summary>
/// Splits the cube in given position, and color direction.
/// </summary>
private static long Top(WuColorCube cube, int direction, int position, long[,,] moment) =>
/// <summary>
/// Splits the cube in given position, and color direction.
/// </summary>
[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
/// <summary>
/// Splits the cube in a given color direction at its minimum.
/// </summary>
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
/// <summary>
/// Calculates statistical variance for a given cube.
/// </summary>
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
/// <summary>
/// Finds the optimal (maximal) position for the cut.
/// </summary>
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
/// <summary>
/// Cuts a cube with another one.
/// </summary>
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
/// <summary>
/// Marks all the tags with a given label.
/// </summary>
private static void Mark(WuColorCube cube, int label, byte[] tag)
private static void Mark(in WuColorCube cube, int label, Span<byte> tag)
{
for (var redIndex = cube.RedMinimum + 1; redIndex <= cube.RedMaximum; ++redIndex)
{

View file

@ -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 <http://www.gnu.org/licenses/>.
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
{
/// <summary>
/// Implementation of the WuQuantizer algorithm
/// </summary>
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;
/// <summary>
/// The constructor for the WuQauntizer
/// </summary>
/// <param name="sourceBitmap"></param>
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();
}
/// <inheritdoc/>
public void Dispose()
{
Dispose(true);
}
/// <summary>
/// Dispose implementation
/// </summary>
/// <param name="disposing">bool</param>
protected virtual void Dispose(bool disposing)
{
if (!disposing)
{
return;
}
if (_resultBitmap == null)
{
return;
}
_resultBitmap.Dispose();
_resultBitmap = null;
}
/// <summary>
/// Returns the number of colors
/// </summary>
/// <returns>int</returns>
public int GetColorCount()
{
return _colorCount;
}
/// <summary>
/// Reindex the 24/32 BPP (A)RGB image to a 8BPP
/// </summary>
/// <returns>Bitmap</returns>
public IBitmapWithNativeSupport SimpleReindex()
{
var colors = new List<Color>();
var lookup = new Dictionary<Color, byte>();
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;
}
/// <summary>
/// Get the image
/// </summary>
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<Color, byte>();
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;
}
/// <summary>
/// Converts the histogram to a series of moments.
/// </summary>
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];
}
}
}
}
/// <summary>
/// Computes the volume of the cube in a specific moment.
/// </summary>
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];
}
/// <summary>
/// Computes the volume of the cube in a specific moment. For the floating-point values.
/// </summary>
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];
}
/// <summary>
/// Splits the cube in given position, and color direction.
/// </summary>
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
};
/// <summary>
/// Splits the cube in a given color direction at its minimum.
/// </summary>
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
};
/// <summary>
/// Calculates statistical variance for a given cube.
/// </summary>
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;
}
/// <summary>
/// Finds the optimal (maximal) position for the cut.
/// </summary>
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;
}
/// <summary>
/// Cuts a cube with another one.
/// </summary>
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;
}
/// <summary>
/// Marks all the tags with a given label.
/// </summary>
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;
}
}
}
}
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
@ -49,7 +49,7 @@ namespace Greenshot.Gfx.Structs
/// Equal
/// </summary>
public static bool operator ==(Bgr32 left, Bgr32 right) => Equals(left, right);
/// <summary>
/// Not Equal
/// </summary>

View file

@ -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 <http://www.gnu.org/licenses/>.
@ -26,6 +26,34 @@ namespace Greenshot.Gfx.Structs
/// </summary>
public static class PixelExtensions
{
/// <inheritdoc />
public static Bgr24 BackgroundBlendColor { get; set; }
/// <summary>
/// Make a Brga32 from the specified color
/// </summary>
/// <param name="color">Color</param>
/// <returns>Bgr32</returns>
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,
};
}
/// <summary>
/// Make a Brga32 from the specified color
/// </summary>
@ -55,6 +83,27 @@ namespace Greenshot.Gfx.Structs
G = color.G,
B = color.B,
};
}
}
/// <summary>
/// Make a color from the specified Brg32
/// </summary>
/// <param name="color">Bgr32</param>
/// <returns>Color</returns>
public static Color ToColor(this Bgr32 color)
{
return Color.FromArgb(color.R, color.G, color.B);
}
/// <summary>
/// Make a color from the specified uint
/// </summary>
/// <param name="color">uint</param>
/// <returns>Color</returns>
public static Color ToColor(this uint color)
{
return Color.FromArgb((int)color);
}
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
@ -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")
};
}
}
/// <summary>
/// Returns the pixel format for this Unmanaged bitmap
/// </summary>
@ -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
/// </summary>
/// <returns>Bitmap</returns>
public Bitmap NativeBitmap
public virtual Bitmap NativeBitmap
{
get
{
@ -182,7 +184,7 @@ namespace Greenshot.Gfx
return _nativeBitmap;
}
}
/// <summary>
/// Convert this to a real bitmap
/// </summary>

View file

@ -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 <http://www.gnu.org/licenses/>.
@ -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<Bgr32>(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<Bgra32>(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<Bgr32>(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();

View file

@ -16,10 +16,10 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.12.0" />
<PackageReference Include="CommandLineParser" Version="2.7.82" />
<PackageReference Include="Microsoft.DotNet.PlatformAbstractions" Version="3.1.2" />
<PackageReference Include="BenchmarkDotNet" Version="0.12.1" />
<PackageReference Include="CommandLineParser" Version="2.8.0" />
<PackageReference Include="Microsoft.DotNet.PlatformAbstractions" Version="3.1.6" />
<PackageReference Include="SharpAvi" Version="2.1.2" />
<PackageReference Include="System.Memory" Version="4.5.3" />
<PackageReference Include="System.Memory" Version="4.5.4" />
</ItemGroup>
</Project>

View file

@ -28,15 +28,15 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.12.0" />
<PackageReference Include="CommandLineParser" Version="2.7.82" />
<PackageReference Include="BenchmarkDotNet" Version="0.12.1" />
<PackageReference Include="CommandLineParser" Version="2.8.0" />
<PackageReference Include="Dapplo.Log.XUnit" Version="1.3.26" />
<PackageReference Include="Dapplo.Windows" Version="0.11.15" />
<PackageReference Include="Dapplo.Windows" Version="0.11.17" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.abstractions" Version="2.0.3" />
<PackageReference Include="xunit.analyzers" Version="0.10.0" />
<PackageReference Include="xunit.core" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1">
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
@ -51,6 +51,9 @@
</ItemGroup>
<ItemGroup>
<None Update="TestFiles\paiS7wOGKN0XA1wIaq8qHNoWQqq64wnFu3svA9Ux.jpeg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="TestFiles\scroll-result.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>

View file

@ -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 <http://www.gnu.org/licenses/>.
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
/// </summary>
public class QuantizeTests
{
public QuantizeTests()
{
BitmapHelper.RegisterFormatReader<GenericGdiFormatReader>();
}
[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<Bgra32>(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<Bgr32>(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<Bgra32>(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");
}
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
@ -37,7 +37,7 @@ namespace Greenshot.Tests
{
LogSettings.RegisterDefaultLogger<XUnitLogger>(LogLevels.Verbose, testOutputHelper);
}
[Fact]
public void Test_Scale2X_UnmanagedBitmap()
{

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

View file

@ -34,16 +34,16 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.7.82" />
<PackageReference Include="CommandLineParser" Version="2.8.0" />
<PackageReference Include="Dapplo.CaliburnMicro.Dapp" Version="2.1.10" />
<PackageReference Include="Dapplo.Log.LogFile" Version="1.3.26" />
<PackageReference Include="Dapplo.Log.Loggers" Version="1.3.26" />
<PackageReference Include="Dapplo.Windows.Multimedia" Version="0.11.15" />
<PackageReference Include="Dapplo.Windows.Multimedia" Version="0.11.17" />
<PackageReference Include="gong-wpf-dragdrop" Version="2.2.0" />
<PackageReference Include="sqlite-net-pcl" Version="1.6.292" />
<PackageReference Include="Svg" Version="3.0.102" />
<PackageReference Include="Microsoft.AppCenter.Analytics" Version="3.0.0" />
<PackageReference Include="Microsoft.AppCenter.Crashes" Version="3.0.0" />
<PackageReference Include="sqlite-net-pcl" Version="1.7.335" />
<PackageReference Include="Svg" Version="3.1.1" />
<PackageReference Include="Microsoft.AppCenter.Analytics" Version="3.3.0" />
<PackageReference Include="Microsoft.AppCenter.Crashes" Version="3.3.0" />
<TrimmerRootAssembly Include="System.Diagnostics.Debug" />
</ItemGroup>

View file

@ -1,6 +1,7 @@
{
"sdk": {
"version": "3.1.100",
"rollForward": "latestPatch"
"rollForward": "latestMajor",
"allowPrerelease": true
}
}