mirror of https://github.com/SixLabors/ImageSharp
4 changed files with 308 additions and 0 deletions
@ -0,0 +1,83 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Numerics.Tensors; |
||||
|
using SixLabors.ImageSharp.Formats.Jxl.Cms; |
||||
|
using SixLabors.ImageSharp.Formats.Jxl.IO.Metadata; |
||||
|
using SixLabors.ImageSharp.Formats.Jxl.Memory.ImageTypes; |
||||
|
using SixLabors.ImageSharp.Formats.Jxl.Processing.Butteraugli; |
||||
|
using SixLabors.ImageSharp.Formats.Jxl.Processing.Image; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Encoder.Comparator; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Compares images using Google Butteraugli.
|
||||
|
/// </summary>
|
||||
|
internal sealed class ButteraugliJxlComparator(ButteraugliParameters parameters, JxlCmsInterface cms) |
||||
|
: JxlComparator |
||||
|
{ |
||||
|
private int width; |
||||
|
private int height; |
||||
|
private float intensityTarget; |
||||
|
private ButteraugliComparator? comparator; |
||||
|
|
||||
|
public override float GoodQualityScore => Butteraugli.Butteraugli.ButteraugliFuzzyInverse(1.5f); |
||||
|
|
||||
|
public override float BadQualityScore => Butteraugli.Butteraugli.ButteraugliFuzzyInverse(0.5f); |
||||
|
|
||||
|
public override void CompareWith(Configuration configuration, JxlImageBundle actual, JxlImageF? diffMap, out float score) |
||||
|
{ |
||||
|
DebugGuard.NotNull(this.comparator, nameof(this.comparator)); |
||||
|
|
||||
|
if (this.width != actual.XSize || this.height != actual.YSize) |
||||
|
{ |
||||
|
throw new InvalidOperationException("Image must have same size"); |
||||
|
} |
||||
|
|
||||
|
JxlImageMetadata metadata = actual.Metadata!; |
||||
|
JxlImageBundle store = new(metadata); |
||||
|
|
||||
|
JxlTransformsEncoder.TransformIfNeeded(configuration, actual, JxlColorEncoding.LinearSRgb(actual.IsGray), cms, store, out JxlImageBundle actualLinearSRgb); |
||||
|
|
||||
|
JxlImageF tempDiffmap = new(configuration, this.width, this.height); |
||||
|
JxlImage3F scaledActualLinearSRgb = actualLinearSRgb.Color!; |
||||
|
|
||||
|
if (this.intensityTarget != 0 && actual.Metadata!.IntensityTarget != this.intensityTarget) |
||||
|
{ |
||||
|
JxlImage3F scaledActualLinearSRgbStore = new(configuration, this.width, this.height); |
||||
|
scaledActualLinearSRgb = scaledActualLinearSRgbStore; |
||||
|
|
||||
|
float scale = actual.Metadata!.IntensityTarget / this.intensityTarget; |
||||
|
for (int c = 0; c < 3; ++c) |
||||
|
{ |
||||
|
for (int y = 0; y < this.height; ++y) |
||||
|
{ |
||||
|
Span<float> souceRow = actualLinearSRgb.Color!.PlaneRow(c, y); |
||||
|
Span<float> scaledRow = scaledActualLinearSRgb.PlaneRow(c, y); |
||||
|
TensorPrimitives.Multiply(souceRow, scale, scaledRow); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (!this.comparator.Diffmap(configuration, scaledActualLinearSRgb, tempDiffmap)) |
||||
|
{ |
||||
|
throw new InvalidOperationException("Diffmap failed"); |
||||
|
} |
||||
|
|
||||
|
score = Butteraugli.Butteraugli.ButteraugliScoreFromDiffmap(tempDiffmap, parameters); |
||||
|
diffMap?.Swap(tempDiffmap); |
||||
|
} |
||||
|
|
||||
|
public override void SetReferenceImage(Configuration configuration, JxlImageBundle reference) |
||||
|
{ |
||||
|
JxlImageMetadata metadata = reference.Metadata!; |
||||
|
JxlImageBundle store = new(metadata); |
||||
|
|
||||
|
JxlTransformsEncoder.TransformIfNeeded(configuration, reference, JxlColorEncoding.LinearSRgb(reference.IsGray), cms, store, out JxlImageBundle refLinearSRgb); |
||||
|
this.comparator = ButteraugliComparator.Make(configuration, refLinearSRgb.Color!, parameters); |
||||
|
|
||||
|
this.width = reference.XSize; |
||||
|
this.height = reference.YSize; |
||||
|
this.intensityTarget = reference.Metadata!.IntensityTarget; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using SixLabors.ImageSharp.Formats.Jxl.Memory.ImageTypes; |
||||
|
using SixLabors.ImageSharp.Formats.Jxl.Processing.Image; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Encoder.Comparator; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Compares images.
|
||||
|
/// </summary>
|
||||
|
internal abstract class JxlComparator |
||||
|
{ |
||||
|
public abstract float GoodQualityScore { get; } |
||||
|
|
||||
|
public abstract float BadQualityScore { get; } |
||||
|
|
||||
|
// Sets the reference image, the first to compare.
|
||||
|
public abstract void SetReferenceImage(Configuration configuration, JxlImageBundle reference); |
||||
|
|
||||
|
// Sets the actual image, the second to compare.
|
||||
|
public abstract void CompareWith( |
||||
|
Configuration configuration, |
||||
|
JxlImageBundle actual, |
||||
|
JxlImageF? diffMap, |
||||
|
out float score); |
||||
|
} |
||||
@ -0,0 +1,175 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Numerics.Tensors; |
||||
|
using SixLabors.ImageSharp.Formats.Jxl.Cms; |
||||
|
using SixLabors.ImageSharp.Formats.Jxl.IO.Metadata; |
||||
|
using SixLabors.ImageSharp.Formats.Jxl.Memory.ImageTypes; |
||||
|
using SixLabors.ImageSharp.Formats.Jxl.Processing.Image; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Encoder.Comparator; |
||||
|
|
||||
|
internal static class JxlComparatorScoreComputation |
||||
|
{ |
||||
|
private static void AlphaBlend(JxlImage3F input, int c, float backgroundLinear, JxlImageF alpha, JxlImage3F output) |
||||
|
{ |
||||
|
float background = (float)JxlGammaCorrect.LinearToSRgb8Direct(backgroundLinear); |
||||
|
|
||||
|
for (int y = 0; y < output.YSize; y++) |
||||
|
{ |
||||
|
ReadOnlySpan<float> rowA = alpha.GetRow(y); |
||||
|
ReadOnlySpan<float> rowI = input.PlaneRow(c, y); |
||||
|
Span<float> rowO = output.PlaneRow(c, y); |
||||
|
|
||||
|
for (int x = 0; x < output.XSize; x++) |
||||
|
{ |
||||
|
float a = rowA[x]; |
||||
|
|
||||
|
if (a <= 0.0f) |
||||
|
{ |
||||
|
rowO[x] = backgroundLinear; |
||||
|
} |
||||
|
else if (a >= 1.0f) |
||||
|
{ |
||||
|
rowO[x] = rowI[x]; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
float wFg = a; |
||||
|
float wBg = 1.0f - wFg; |
||||
|
float fg = wFg * (float)JxlGammaCorrect.LinearToSRgb8Direct(rowI[x]); |
||||
|
float bg = wBg * background; |
||||
|
|
||||
|
rowO[x] = (float)JxlGammaCorrect.SRgb8ToLinearDirect(fg + bg); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void AlphaBlend(float backgroundLinear, JxlImageBundle image) |
||||
|
{ |
||||
|
if (!image.ContainsAlpha) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
for (int c = 0; c < 3; c++) |
||||
|
{ |
||||
|
AlphaBlend(image.Color!, c, backgroundLinear, image.Alpha!, image.Color!); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void ComputeScoreImpl(Configuration configuration, JxlImageBundle rgb0, JxlImageBundle rgb1, JxlComparator comparator, JxlImageF? diffMap, out float score) |
||||
|
{ |
||||
|
comparator.SetReferenceImage(rgb0); |
||||
|
comparator.CompareWith(configuration, rgb1, diffMap, out score); |
||||
|
} |
||||
|
|
||||
|
public static void ComputeScore( |
||||
|
Configuration configuration, |
||||
|
JxlImageBundle rgb0, |
||||
|
JxlImageBundle rgb1, |
||||
|
JxlComparator comparator, |
||||
|
JxlCmsInterface cms, |
||||
|
out float score, |
||||
|
JxlImageF? diffMap = null, |
||||
|
bool ignoreAlpha = false) |
||||
|
{ |
||||
|
JxlImageMetadata metadata0 = rgb0.Metadata.Copy(); |
||||
|
|
||||
|
JxlImageBundle store0 = new(metadata0); |
||||
|
|
||||
|
JxlTransformsEncoder.TransformIfNeeded( |
||||
|
rgb0, |
||||
|
JxlColorEncoding.LinearSRgb(rgb0.IsGray), |
||||
|
cms, |
||||
|
pool, |
||||
|
store0, |
||||
|
out JxlImageBundle linearSrgb0); |
||||
|
|
||||
|
JxlImageMetadata metadata1 = rgb1.Metadata.Copy(); |
||||
|
|
||||
|
JxlImageBundle store1 = new(metadata1); |
||||
|
|
||||
|
JxlTransformsEncoder.TransformIfNeeded( |
||||
|
rgb1, |
||||
|
JxlColorEncoding.LinearSRgb(rgb1.IsGray), |
||||
|
cms, |
||||
|
pool, |
||||
|
store1, |
||||
|
out JxlImageBundle linearSrgb1); |
||||
|
|
||||
|
if (ignoreAlpha || (!rgb0.ContainsAlpha && !rgb1.ContainsAlpha)) |
||||
|
{ |
||||
|
ComputeScoreImpl( |
||||
|
configuration, |
||||
|
linearSrgb0, |
||||
|
linearSrgb1, |
||||
|
comparator, |
||||
|
diffMap, |
||||
|
out score); |
||||
|
} |
||||
|
|
||||
|
JxlImageF diffMapBlack = new(); |
||||
|
float distBlack; |
||||
|
{ |
||||
|
const float black = 0.0f; |
||||
|
|
||||
|
JxlImageBundle blendedBlack0 = linearSrgb0.Copy(configuration); |
||||
|
JxlImageBundle blendedBlack1 = linearSrgb1.Copy(configuration); |
||||
|
|
||||
|
AlphaBlend(black, blendedBlack0); |
||||
|
AlphaBlend(black, blendedBlack1); |
||||
|
|
||||
|
ComputeScoreImpl( |
||||
|
configuration, |
||||
|
blendedBlack0, |
||||
|
blendedBlack1, |
||||
|
comparator, |
||||
|
diffMapBlack, |
||||
|
out distBlack); |
||||
|
} |
||||
|
|
||||
|
JxlImageF diffMapWhite = new(); |
||||
|
float distWhite; |
||||
|
{ |
||||
|
const float white = 1.0f; |
||||
|
|
||||
|
JxlImageBundle blendedWhite0 = linearSrgb0.Copy(configuration); |
||||
|
JxlImageBundle blendedWhite1 = linearSrgb1.Copy(configuration); |
||||
|
|
||||
|
AlphaBlend(white, blendedWhite0); |
||||
|
AlphaBlend(white, blendedWhite1); |
||||
|
|
||||
|
ComputeScoreImpl( |
||||
|
configuration, |
||||
|
blendedWhite0, |
||||
|
blendedWhite1, |
||||
|
comparator, |
||||
|
diffMapWhite, |
||||
|
out distWhite); |
||||
|
} |
||||
|
|
||||
|
if (diffMap is not null) |
||||
|
{ |
||||
|
int xSize = rgb0.XSize; |
||||
|
int ySize = rgb0.YSize; |
||||
|
|
||||
|
JxlImageF outputDiffMap = new( |
||||
|
configuration, |
||||
|
xSize, |
||||
|
ySize); |
||||
|
|
||||
|
for (int y = 0; y < ySize; y++) |
||||
|
{ |
||||
|
ReadOnlySpan<float> rowBlack = diffMapBlack.GetRow(y); |
||||
|
ReadOnlySpan<float> rowWhite = diffMapWhite.GetRow(y); |
||||
|
Span<float> rowOut = diffMap.GetRow(y); |
||||
|
|
||||
|
TensorPrimitives.Max(rowBlack, rowWhite, rowOut); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
score = MathF.Max(distBlack, distWhite); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using SixLabors.ImageSharp.Formats.Jxl.Memory.ImageTypes; |
||||
|
using SixLabors.ImageSharp.Formats.Jxl.Processing.Image; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Encoder.Comparator; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Performs no image comparison at all.
|
||||
|
/// </summary>
|
||||
|
internal sealed class NullComparator : JxlComparator |
||||
|
{ |
||||
|
public override float GoodQualityScore => 0; |
||||
|
|
||||
|
public override float BadQualityScore => 0; |
||||
|
|
||||
|
public override void CompareWith(Configuration configuration, JxlImageBundle actual, JxlImageF? diffMap, out float score) => score = 0; |
||||
|
|
||||
|
public override void SetReferenceImage(Configuration configuration, JxlImageBundle reference) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue