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