Skip to content
This repository has been archived by the owner on Sep 6, 2023. It is now read-only.

Commit

Permalink
Merge pull request #796 from JimBobSquarePants/js/resize-improvements
Browse files Browse the repository at this point in the history
Resize quality improvements
  • Loading branch information
JimBobSquarePants authored Apr 16, 2020
2 parents 4b4e32f + d9cebb3 commit 1ee666c
Show file tree
Hide file tree
Showing 20 changed files with 715 additions and 569 deletions.
2 changes: 1 addition & 1 deletion build/NuSpecs/ImageProcessor.Web.nuspec
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<tags>Image Resize Crop Rotate Quality Watermark Gif Jpg Jpeg Bitmap Png Tiff ASP Cache EXIF</tags>
<dependencies>
<group targetFramework=".NETFramework4.5.2">
<dependency id="ImageProcessor" version="2.8.0" />
<dependency id="ImageProcessor" version="2.9.0" />
<dependency id="Microsoft.IO.RecyclableMemoryStream" version="1.2.2" />
</group>
</dependencies>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public override object ConvertFrom(CultureInfo culture, object value, Type prope
{
object result = base.ConvertFrom(culture, value, propertyType);

return result is int[] list ? new Point(list[0], list[1]) : result;
return result is int[] list && list.Length == 2 ? new Point(list[0], list[1]) : result;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="PointConverter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// The point converter.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace ImageProcessor.Web.Helpers
{
using System;
using System.Drawing;
using System.Globalization;

/// <summary>
/// The PointF converter.
/// </summary>
public class PointFConverter : GenericArrayTypeConverter<float>
{
/// <summary>
/// Converts the given object to the type of this converter, using the specified context and culture
/// information.
/// </summary>
/// <returns>
/// An <see cref="T:System.Object"/> that represents the converted value.
/// </returns>
/// <param name="culture">
/// The <see cref="T:System.Globalization.CultureInfo"/> to use as the current culture.
/// </param>
/// <param name="value">The <see cref="T:System.Object"/> to convert. </param>
/// <param name="propertyType">The property type that the converter will convert to.</param>
/// <exception cref="T:System.NotSupportedException">The conversion cannot be performed.</exception>
public override object ConvertFrom(CultureInfo culture, object value, Type propertyType)
{
object result = base.ConvertFrom(culture, value, propertyType);

return result is float[] list && list.Length == 2 ? new PointF(list[0], list[1]) : result;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public override object ConvertFrom(CultureInfo culture, object value, Type prope
{
object result = base.ConvertFrom(culture, value, propertyType);

return result is int[] list ? new Size(list[0], list[1]) : result;
return result is int[] list && list.Length == 2 ? new Size(list[0], list[1]) : result;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ namespace ImageProcessor.Web.Helpers
public sealed class QueryParamParser
{
/// <summary>
/// A new instance of the <see cref="QueryParamParser"/> class.
/// A new instance of the <see cref="QueryParamParser" /> class.
/// with lazy initialization.
/// </summary>
private static readonly Lazy<QueryParamParser> Lazy = new Lazy<QueryParamParser>(() => new QueryParamParser());

/// <summary>
/// Prevents a default instance of the <see cref="QueryParamParser"/> class from being created.
/// Prevents a default instance of the <see cref="QueryParamParser" /> class from being created.
/// </summary>
private QueryParamParser()
{
Expand All @@ -42,25 +42,22 @@ private QueryParamParser()
}

/// <summary>
/// Gets the current <see cref="QueryParamParser"/> instance.
/// Gets the current <see cref="QueryParamParser" /> instance.
/// </summary>
/// <value>
/// The <see cref="QueryParamParser" /> instance.
/// </value>
public static QueryParamParser Instance => Lazy.Value;

/// <summary>
/// Parses the given string value converting it to the given type.
/// </summary>
/// <param name="value">
/// The <see cref="string"/> value to parse.
/// </param>
/// <param name="culture">
/// The <see cref="CultureInfo"/> to use as the current culture.
/// <remarks>If not set will parse using <see cref="CultureInfo.InvariantCulture"/></remarks>
/// </param>
/// <typeparam name="T">
/// The <see cref="Type"/> to convert the string to.
/// </typeparam>
/// <typeparam name="T">The <see cref="Type" /> to convert the string to.</typeparam>
/// <param name="value">The <see cref="string" /> value to parse.</param>
/// <param name="culture">The <see cref="CultureInfo" /> to use as the current culture.
/// <remarks>If not set will parse using <see cref="CultureInfo.InvariantCulture" /></remarks></param>
/// <returns>
/// The <typeparamref name="T"/>.
/// The <typeparamref name="T" />.
/// </returns>
public T ParseValue<T>(string value, CultureInfo culture = null)
{
Expand All @@ -72,6 +69,12 @@ public T ParseValue<T>(string value, CultureInfo culture = null)
Type type = typeof(T);

IQueryParamConverter converter = QueryTypeDescriptor.GetConverter(type);
if (converter == null && Nullable.GetUnderlyingType(type) is Type underlyingType)
{
type = underlyingType;
converter = QueryTypeDescriptor.GetConverter(type);
}

try
{
// ReSharper disable once AssignNullToNotNullAttribute
Expand All @@ -87,12 +90,8 @@ public T ParseValue<T>(string value, CultureInfo culture = null)
/// <summary>
/// Adds a type converter to the parser.
/// </summary>
/// <param name="type">
/// The <see cref="Type"/> to add a converter for.
/// </param>
/// <param name="converterType">
/// The type of <see cref="IQueryParamConverter"/> to add.
/// </param>
/// <param name="type">The <see cref="Type" /> to add a converter for.</param>
/// <param name="converterType">The type of <see cref="IQueryParamConverter" /> to add.</param>
public void AddTypeConverter(Type type, Type converterType) => QueryTypeDescriptor.AddConverter(type, converterType);

/// <summary>
Expand All @@ -108,15 +107,19 @@ public T ParseValue<T>(string value, CultureInfo culture = null)
/// <summary>
/// Adds point converters.
/// </summary>
private void AddPointConverters() => this.AddTypeConverter(typeof(Point), typeof(PointConverter));
private void AddPointConverters()
{
this.AddTypeConverter(typeof(Point), typeof(PointConverter));
this.AddTypeConverter(typeof(PointF), typeof(PointFConverter));
}

/// <summary>
/// Adds point converters.
/// Adds size converters.
/// </summary>
private void AddSizeConverters() => this.AddTypeConverter(typeof(Size), typeof(SizeConverter));

/// <summary>
/// Add the generic converters
/// Add the generic converters.
/// </summary>
private void AddGenericConverters()
{
Expand Down
1 change: 1 addition & 0 deletions src/ImageProcessor.Web/ImageProcessor.Web.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
<Compile Include="Helpers\QuerystringParser\Converters\GenericConvertableConverter.cs" />
<Compile Include="Helpers\QuerystringParser\Converters\EnumConverter.cs" />
<Compile Include="Helpers\QuerystringParser\Converters\IQueryParamConverter.cs" />
<Compile Include="Helpers\QuerystringParser\Converters\PointFConverter.cs" />
<Compile Include="Helpers\QuerystringParser\Converters\SizeConverter.cs" />
<Compile Include="Helpers\QuerystringParser\Converters\PointConverter.cs" />
<Compile Include="Helpers\QuerystringParser\Converters\QueryParamConverter.cs" />
Expand Down
11 changes: 7 additions & 4 deletions src/ImageProcessor.Web/Processors/Resize.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,16 +75,19 @@ public int MatchRegexIndex(string queryString)
ResizeMode mode = QueryParamParser.Instance.ParseValue<ResizeMode>(queryCollection["mode"]);
AnchorPosition position = QueryParamParser.Instance.ParseValue<AnchorPosition>(queryCollection["anchor"]);
bool upscale = queryCollection["upscale"] == null || QueryParamParser.Instance.ParseValue<bool>(queryCollection["upscale"]);
float[] center = queryCollection["center"] != null
? QueryParamParser.Instance.ParseValue<float[]>(queryCollection["center"]) :
new float[] { };
PointF? center = QueryParamParser.Instance.ParseValue<PointF?>(queryCollection["center"]);
if (center.HasValue)
{
// Swap X/Y for backwards compatibility
center = new PointF(center.Value.Y, center.Value.X);
}

this.Processor.DynamicParameter = new ResizeLayer(size)
{
ResizeMode = mode,
AnchorPosition = position,
Upscale = upscale,
CenterCoordinates = center
Center = center
};

// Correctly parse any restrictions.
Expand Down
2 changes: 1 addition & 1 deletion src/ImageProcessor/Common/Extensions/FloatExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,6 @@ public static class FloatExtensions
/// <returns>
/// The <see cref="T:System.Byte"/>.
/// </returns>
public static byte ToByte(this float value) => Convert.ToByte(ImageMaths.Clamp(value, 0, 255));
public static byte ToByte(this float value) => Convert.ToByte(ImageMaths.Clamp(value, byte.MinValue, byte.MaxValue));
}
}
4 changes: 2 additions & 2 deletions src/ImageProcessor/ImageFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public class ImageFactory : IDisposable
/// Whether to preserve exif metadata. Defaults to false.
/// </param>
public ImageFactory(bool preserveExifData = false)
: this(preserveExifData, true)
: this(preserveExifData, false)
{
}

Expand Down Expand Up @@ -119,7 +119,7 @@ public ImageFactory(MetaDataMode metaDataMode, bool fixGamma)
}

/// <summary>
/// Finalizes an instance of the <see cref="ImageFactory">ImageFactory</see> class.
/// Finalizes an instance of the <see cref="ImageFactory"/> class.
/// </summary>
/// <remarks>
/// Use C# destructor syntax for finalization code.
Expand Down
1 change: 1 addition & 0 deletions src/ImageProcessor/ImageProcessor.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@
<Compile Include="Common\Exceptions\QuantizationException.cs" />
<Compile Include="Imaging\Quantizers\WuQuantizer\WuQuantizer.cs" />
<Compile Include="Imaging\Quantizers\WuQuantizer\WuQuantizerBase.cs" />
<Compile Include="Imaging\ResizeHelper.cs" />
<Compile Include="Imaging\ResizeLayer.cs" />
<Compile Include="Imaging\Filters\Photo\BlackWhiteMatrixFilter.cs" />
<Compile Include="Imaging\Filters\Photo\ColorMatrixes.cs" />
Expand Down
27 changes: 13 additions & 14 deletions src/ImageProcessor/Imaging/Helpers/Adjustments.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ namespace ImageProcessor.Imaging.Helpers
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;

using ImageProcessor.Common.Extensions;
Expand Down Expand Up @@ -329,7 +330,7 @@ private static byte[] GetLinearBytes()
byte[] ramp = new byte[256];
for (int x = 0; x < 256; ++x)
{
ramp[x] = (255f * SRGBToLinear(x / 255f)).ToByte();
ramp[x] = ((255 * SRGBToLinear(x / 255D)) + .5).ToByte();
}

return ramp;
Expand All @@ -347,7 +348,7 @@ private static byte[] GetSRGBBytes()
byte[] ramp = new byte[256];
for (int x = 0; x < 256; ++x)
{
ramp[x] = (255f * LinearToSRGB(x / 255f)).ToByte();
ramp[x] = ((255 * LinearToSRGB(x / 255D)) + .5).ToByte();
}

return ramp;
Expand All @@ -362,16 +363,15 @@ private static byte[] GetSRGBBytes()
/// <returns>
/// The <see cref="float"/>.
/// </returns>
private static float SRGBToLinear(float signal)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static double SRGBToLinear(double signal)
{
const float a = 0.055f;

if (signal <= 0.04045)
{
return signal / 12.92f;
return signal / 12.92;
}

return (float)Math.Pow((signal + a) / (1 + a), 2.4);
return Math.Pow((signal + 0.055) / 1.055, 2.4);
}

/// <summary>
Expand All @@ -381,18 +381,17 @@ private static float SRGBToLinear(float signal)
/// </summary>
/// <param name="signal">The signal value to convert.</param>
/// <returns>
/// The <see cref="float"/>.
/// The <see cref="double"/>.
/// </returns>
private static float LinearToSRGB(float signal)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static double LinearToSRGB(double signal)
{
const float a = 0.055f;

if (signal <= 0.0031308)
if (signal <= (0.04045 / 12.92))
{
return signal * 12.92f;
return signal * 12.92;
}

return ((float)((1 + a) * Math.Pow(signal, 0.4166667F))) - a;
return (1.055 * Math.Pow(signal, 1.0 / 2.4)) - 0.055;
}
}
}
6 changes: 3 additions & 3 deletions src/ImageProcessor/Imaging/Helpers/GraphicsHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@ public static void SetGraphicsOptions(Graphics graphics, bool blending = false,
{
// Highest quality resampling algorithm.
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;

// Ensure pixel offset is set.
graphics.PixelOffsetMode = PixelOffsetMode.Half;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
graphics.CompositingQuality = CompositingQuality.HighQuality;

if (smoothing)
{
// We want smooth edges when drawing.
graphics.SmoothingMode = SmoothingMode.AntiAlias;
}

if (blending || smoothing)
{
// Best combination for blending pixels.
Expand Down
Loading

0 comments on commit 1ee666c

Please sign in to comment.