mirror of
https://github.com/greenshot/greenshot
synced 2025-08-22 14:24:43 -07:00
Working state before .NET 5 upgrade
This commit is contained in:
parent
d4321b2ad6
commit
56ccfd4d2a
28 changed files with 1203 additions and 284 deletions
|
@ -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>
|
||||
|
|
|
@ -24,6 +24,6 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dapplo.Confluence" Version="0.9.7" />
|
||||
<PackageReference Include="Dapplo.Confluence" Version="1.0.16" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
@ -19,6 +19,6 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CliWrap" Version="3.0.0" />
|
||||
<PackageReference Include="CliWrap" Version="3.1.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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();
|
||||
|
|
54
src/Greenshot.Gfx/ChunkyBitmap.cs
Normal file
54
src/Greenshot.Gfx/ChunkyBitmap.cs
Normal 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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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.
|
||||
|
|
66
src/Greenshot.Gfx/Quantizer/WuColorCubeOld.cs
Normal file
66
src/Greenshot.Gfx/Quantizer/WuColorCubeOld.cs
Normal 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; }
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
{
|
||||
|
|
716
src/Greenshot.Gfx/Quantizer/WuQuantizerOld.cs
Normal file
716
src/Greenshot.Gfx/Quantizer/WuQuantizerOld.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 |
|
@ -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>
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"sdk": {
|
||||
"version": "3.1.100",
|
||||
"rollForward": "latestPatch"
|
||||
"rollForward": "latestMajor",
|
||||
"allowPrerelease": true
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue