Skip to content

Commit 6bb3624

Browse files
Merge branch 'main' into dependabot/github_actions/actions/checkout-6
2 parents 8f7dc2c + 67bac23 commit 6bb3624

18 files changed

Lines changed: 399 additions & 299 deletions

File tree

src/ImageSharp/ColorProfiles/Icc/Calculators/LutABCalculator.CalculationType.cs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,19 @@ namespace SixLabors.ImageSharp.ColorProfiles.Conversion.Icc;
55

66
internal partial class LutABCalculator
77
{
8+
/// <summary>
9+
/// Identifies the transform direction for the configured LUT calculator.
10+
/// </summary>
811
private enum CalculationType
912
{
10-
AtoB = 1 << 3,
11-
BtoA = 1 << 4,
13+
/// <summary>
14+
/// Converts from device space to PCS using ICC <c>mAB</c> stage order.
15+
/// </summary>
16+
AtoB,
1217

13-
SingleCurve = 1,
14-
CurveMatrix = 2,
15-
CurveClut = 3,
16-
Full = 4,
18+
/// <summary>
19+
/// Converts from PCS to device space using ICC <c>mBA</c> stage order.
20+
/// </summary>
21+
BtoA,
1722
}
1823
}

src/ImageSharp/ColorProfiles/Icc/Calculators/LutABCalculator.cs

Lines changed: 82 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -17,67 +17,106 @@ internal partial class LutABCalculator : IVector4Calculator
1717
private MatrixCalculator matrixCalculator;
1818
private ClutCalculator clutCalculator;
1919

20+
/// <summary>
21+
/// Initializes a new instance of the <see cref="LutABCalculator"/> class for an ICC <c>mAB</c> transform.
22+
/// </summary>
23+
/// <param name="entry">The parsed A-to-B LUT entry.</param>
2024
public LutABCalculator(IccLutAToBTagDataEntry entry)
2125
{
2226
Guard.NotNull(entry, nameof(entry));
2327
this.Init(entry.CurveA, entry.CurveB, entry.CurveM, entry.Matrix3x1, entry.Matrix3x3, entry.ClutValues);
24-
this.type |= CalculationType.AtoB;
28+
this.type = CalculationType.AtoB;
2529
}
2630

31+
/// <summary>
32+
/// Initializes a new instance of the <see cref="LutABCalculator"/> class for an ICC <c>mBA</c> transform.
33+
/// </summary>
34+
/// <param name="entry">The parsed B-to-A LUT entry.</param>
2735
public LutABCalculator(IccLutBToATagDataEntry entry)
2836
{
2937
Guard.NotNull(entry, nameof(entry));
3038
this.Init(entry.CurveA, entry.CurveB, entry.CurveM, entry.Matrix3x1, entry.Matrix3x3, entry.ClutValues);
31-
this.type |= CalculationType.BtoA;
39+
this.type = CalculationType.BtoA;
3240
}
3341

42+
/// <summary>
43+
/// Calculates the transformed value by applying the configured ICC LUT stages in specification order.
44+
/// </summary>
45+
/// <param name="value">The input value.</param>
46+
/// <returns>The transformed value.</returns>
3447
public Vector4 Calculate(Vector4 value)
3548
{
3649
switch (this.type)
3750
{
38-
case CalculationType.Full | CalculationType.AtoB:
39-
value = this.curveACalculator.Calculate(value);
40-
value = this.clutCalculator.Calculate(value);
41-
value = this.curveMCalculator.Calculate(value);
42-
value = this.matrixCalculator.Calculate(value);
43-
return this.curveBCalculator.Calculate(value);
44-
45-
case CalculationType.Full | CalculationType.BtoA:
46-
value = this.curveBCalculator.Calculate(value);
47-
value = this.matrixCalculator.Calculate(value);
48-
value = this.curveMCalculator.Calculate(value);
49-
value = this.clutCalculator.Calculate(value);
50-
return this.curveACalculator.Calculate(value);
51-
52-
case CalculationType.CurveClut | CalculationType.AtoB:
53-
value = this.curveACalculator.Calculate(value);
54-
value = this.clutCalculator.Calculate(value);
55-
return this.curveBCalculator.Calculate(value);
56-
57-
case CalculationType.CurveClut | CalculationType.BtoA:
58-
value = this.curveBCalculator.Calculate(value);
59-
value = this.clutCalculator.Calculate(value);
60-
return this.curveACalculator.Calculate(value);
61-
62-
case CalculationType.CurveMatrix | CalculationType.AtoB:
63-
value = this.curveMCalculator.Calculate(value);
64-
value = this.matrixCalculator.Calculate(value);
65-
return this.curveBCalculator.Calculate(value);
66-
67-
case CalculationType.CurveMatrix | CalculationType.BtoA:
68-
value = this.curveBCalculator.Calculate(value);
69-
value = this.matrixCalculator.Calculate(value);
70-
return this.curveMCalculator.Calculate(value);
71-
72-
case CalculationType.SingleCurve | CalculationType.AtoB:
73-
case CalculationType.SingleCurve | CalculationType.BtoA:
74-
return this.curveBCalculator.Calculate(value);
51+
case CalculationType.AtoB:
52+
// ICC mAB order: A, CLUT, M, Matrix, B.
53+
if (this.curveACalculator != null)
54+
{
55+
value = this.curveACalculator.Calculate(value);
56+
}
57+
58+
if (this.clutCalculator != null)
59+
{
60+
value = this.clutCalculator.Calculate(value);
61+
}
62+
63+
if (this.curveMCalculator != null)
64+
{
65+
value = this.curveMCalculator.Calculate(value);
66+
}
67+
68+
if (this.matrixCalculator != null)
69+
{
70+
value = this.matrixCalculator.Calculate(value);
71+
}
72+
73+
if (this.curveBCalculator != null)
74+
{
75+
value = this.curveBCalculator.Calculate(value);
76+
}
77+
78+
return value;
79+
80+
case CalculationType.BtoA:
81+
// ICC mBA order: B, Matrix, M, CLUT, A.
82+
if (this.curveBCalculator != null)
83+
{
84+
value = this.curveBCalculator.Calculate(value);
85+
}
86+
87+
if (this.matrixCalculator != null)
88+
{
89+
value = this.matrixCalculator.Calculate(value);
90+
}
91+
92+
if (this.curveMCalculator != null)
93+
{
94+
value = this.curveMCalculator.Calculate(value);
95+
}
96+
97+
if (this.clutCalculator != null)
98+
{
99+
value = this.clutCalculator.Calculate(value);
100+
}
101+
102+
if (this.curveACalculator != null)
103+
{
104+
value = this.curveACalculator.Calculate(value);
105+
}
106+
107+
return value;
75108

76109
default:
77110
throw new InvalidOperationException("Invalid calculation type");
78111
}
79112
}
80113

114+
/// <summary>
115+
/// Creates calculators for the processing stages present in the LUT entry.
116+
/// </summary>
117+
/// <remarks>
118+
/// The tag entry classes already validate channel continuity, so this method only materializes the available stages.
119+
/// </remarks>
81120
private void Init(IccTagDataEntry[] curveA, IccTagDataEntry[] curveB, IccTagDataEntry[] curveM, Vector3? matrix3x1, Matrix4x4? matrix3x3, IccClut clut)
82121
{
83122
bool hasACurve = curveA != null;
@@ -86,26 +125,10 @@ private void Init(IccTagDataEntry[] curveA, IccTagDataEntry[] curveB, IccTagData
86125
bool hasMatrix = matrix3x1 != null && matrix3x3 != null;
87126
bool hasClut = clut != null;
88127

89-
if (hasBCurve && hasMatrix && hasMCurve && hasClut && hasACurve)
90-
{
91-
this.type = CalculationType.Full;
92-
}
93-
else if (hasBCurve && hasClut && hasACurve)
94-
{
95-
this.type = CalculationType.CurveClut;
96-
}
97-
else if (hasBCurve && hasMatrix && hasMCurve)
98-
{
99-
this.type = CalculationType.CurveMatrix;
100-
}
101-
else if (hasBCurve)
102-
{
103-
this.type = CalculationType.SingleCurve;
104-
}
105-
else
106-
{
107-
throw new InvalidIccProfileException("AToB or BToA tag has an invalid configuration");
108-
}
128+
Guard.IsTrue(
129+
hasACurve || hasBCurve || hasMCurve || hasMatrix || hasClut,
130+
"entry",
131+
"AToB or BToA tag must contain at least one processing element");
109132

110133
if (hasACurve)
111134
{

src/ImageSharp/ColorProfiles/Icc/IccConverterbase.Conversions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ private static IVector4Calculator InitA(IccProfile profile, IccProfileTag tag)
6060
IccLut16TagDataEntry lut16 => new LutEntryCalculator(lut16),
6161
IccLutAToBTagDataEntry lutAtoB => new LutABCalculator(lutAtoB),
6262
IccLutBToATagDataEntry lutBtoA => new LutABCalculator(lutBtoA),
63-
_ => throw new InvalidIccProfileException("Invalid entry."),
63+
_ => throw new InvalidIccProfileException($"Invalid entry {tag}."),
6464
};
6565

6666
private static IVector4Calculator InitD(IccProfile profile, IccProfileTag tag)

src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -131,30 +131,35 @@ protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, Cance
131131
try
132132
{
133133
int bytesPerColorMapEntry = this.ReadImageHeaders(stream, out bool inverted, out byte[] palette);
134+
ushort bitsPerPixel = this.infoHeader.BitsPerPixel;
134135

135136
image = new Image<TPixel>(this.configuration, this.infoHeader.Width, this.infoHeader.Height, this.metadata);
136137

137138
Buffer2D<TPixel> pixels = image.GetRootFramePixelBuffer();
138139

139140
switch (this.infoHeader.Compression)
140141
{
141-
case BmpCompression.RGB when this.infoHeader.BitsPerPixel is 32 && this.bmpMetadata.InfoHeaderType is BmpInfoHeaderType.WinVersion3:
142+
case BmpCompression.RGB when bitsPerPixel is 32 && this.bmpMetadata.InfoHeaderType is BmpInfoHeaderType.WinVersion3:
142143
this.ReadRgb32Slow(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);
143144

144145
break;
145-
case BmpCompression.RGB when this.infoHeader.BitsPerPixel is 32:
146+
147+
case BmpCompression.RGB when bitsPerPixel is 32:
146148
this.ReadRgb32Fast(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);
147149

148150
break;
149-
case BmpCompression.RGB when this.infoHeader.BitsPerPixel is 24:
151+
152+
case BmpCompression.RGB when bitsPerPixel is 24:
150153
this.ReadRgb24(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);
151154

152155
break;
153-
case BmpCompression.RGB when this.infoHeader.BitsPerPixel is 16:
156+
157+
case BmpCompression.RGB when bitsPerPixel is 16:
154158
this.ReadRgb16(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);
155159

156160
break;
157-
case BmpCompression.RGB when this.infoHeader.BitsPerPixel is <= 8 && this.processedAlphaMask:
161+
162+
case BmpCompression.RGB when bitsPerPixel is > 0 and <= 8 && this.processedAlphaMask:
158163
this.ReadRgbPaletteWithAlphaMask(
159164
stream,
160165
pixels,
@@ -166,7 +171,8 @@ protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, Cance
166171
inverted);
167172

168173
break;
169-
case BmpCompression.RGB when this.infoHeader.BitsPerPixel is <= 8:
174+
175+
case BmpCompression.RGB when bitsPerPixel is > 0 and <= 8:
170176
this.ReadRgbPalette(
171177
stream,
172178
pixels,
@@ -179,6 +185,10 @@ protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, Cance
179185

180186
break;
181187

188+
case BmpCompression.RGB when bitsPerPixel is <= 0 or > 32:
189+
BmpThrowHelper.ThrowInvalidImageContentException($"Invalid bits per pixel: {bitsPerPixel}");
190+
break;
191+
182192
case BmpCompression.RLE24:
183193
this.ReadRle24(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);
184194

src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,11 @@ internal void ParseStream(BufferedReadStream stream, SpectralConverter spectralC
519519
fileMarker = FindNextFileMarker(stream);
520520
}
521521

522+
if (!metadataOnly && this.Frame is null)
523+
{
524+
JpegThrowHelper.ThrowInvalidImageContentException("No readable SOFn (Start Of Frame) marker found.");
525+
}
526+
522527
this.Metadata.GetJpegMetadata().Interleaved = this.Frame.Interleaved;
523528
}
524529

src/ImageSharp/GraphicsOptions.cs

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@
66
namespace SixLabors.ImageSharp;
77

88
/// <summary>
9-
/// Options for influencing the drawing functions.
9+
/// Provides configuration for controlling how graphics operations are rendered,
10+
/// including antialiasing, pixel blending, alpha composition, and coverage thresholding.
1011
/// </summary>
1112
public class GraphicsOptions : IDeepCloneable<GraphicsOptions>
1213
{
13-
private int antialiasSubpixelDepth = 16;
14+
private float antialiasThreshold = .5F;
1415
private float blendPercentage = 1F;
1516

1617
/// <summary>
@@ -24,61 +25,62 @@ private GraphicsOptions(GraphicsOptions source)
2425
{
2526
this.AlphaCompositionMode = source.AlphaCompositionMode;
2627
this.Antialias = source.Antialias;
27-
this.AntialiasSubpixelDepth = source.AntialiasSubpixelDepth;
28+
this.AntialiasThreshold = source.AntialiasThreshold;
2829
this.BlendPercentage = source.BlendPercentage;
2930
this.ColorBlendingMode = source.ColorBlendingMode;
3031
}
3132

3233
/// <summary>
3334
/// Gets or sets a value indicating whether antialiasing should be applied.
34-
/// Defaults to true.
35+
/// When <see langword="true"/>, edges are rendered with smooth sub-pixel coverage.
36+
/// When <see langword="false"/>, coverage is snapped to binary (fully opaque or fully transparent)
37+
/// using <see cref="AntialiasThreshold"/> as the cutoff.
38+
/// Defaults to <see langword="true"/>.
3539
/// </summary>
3640
public bool Antialias { get; set; } = true;
3741

3842
/// <summary>
39-
/// Gets or sets a value indicating the number of subpixels to use while rendering with antialiasing enabled.
40-
/// Defaults to 16.
43+
/// Gets or sets the coverage threshold used when <see cref="Antialias"/> is <see langword="false"/>.
44+
/// Pixels with antialiased coverage above this value are rendered as fully opaque;
45+
/// pixels below are discarded. Valid range is 0 to 1. Lower values preserve more
46+
/// thin features at small sizes. Defaults to <c>0.5F</c>.
4147
/// </summary>
42-
public int AntialiasSubpixelDepth
48+
public float AntialiasThreshold
4349
{
44-
get
45-
{
46-
return this.antialiasSubpixelDepth;
47-
}
50+
get => this.antialiasThreshold;
4851

4952
set
5053
{
51-
Guard.MustBeGreaterThanOrEqualTo(value, 0, nameof(this.AntialiasSubpixelDepth));
52-
this.antialiasSubpixelDepth = value;
54+
Guard.MustBeBetweenOrEqualTo(value, 0F, 1F, nameof(this.AntialiasThreshold));
55+
this.antialiasThreshold = value;
5356
}
5457
}
5558

5659
/// <summary>
57-
/// Gets or sets a value between indicating the blending percentage to apply to the drawing operation.
58-
/// Range 0..1; Defaults to 1.
60+
/// Gets or sets the blending percentage applied to the drawing operation.
61+
/// A value of <c>1.0</c> applies the operation at full strength; <c>0.0</c> makes it invisible.
62+
/// Valid range is 0 to 1. Defaults to <c>1.0F</c>.
5963
/// </summary>
6064
public float BlendPercentage
6165
{
62-
get
63-
{
64-
return this.blendPercentage;
65-
}
66+
get => this.blendPercentage;
6667

6768
set
6869
{
69-
Guard.MustBeBetweenOrEqualTo(value, 0, 1F, nameof(this.BlendPercentage));
70+
Guard.MustBeBetweenOrEqualTo(value, 0F, 1F, nameof(this.BlendPercentage));
7071
this.blendPercentage = value;
7172
}
7273
}
7374

7475
/// <summary>
75-
/// Gets or sets a value indicating the color blending mode to apply to the drawing operation.
76+
/// Gets or sets the color blending mode used to combine source and destination pixel colors.
7677
/// Defaults to <see cref="PixelColorBlendingMode.Normal"/>.
7778
/// </summary>
7879
public PixelColorBlendingMode ColorBlendingMode { get; set; } = PixelColorBlendingMode.Normal;
7980

8081
/// <summary>
81-
/// Gets or sets a value indicating the alpha composition mode to apply to the drawing operation
82+
/// Gets or sets the alpha composition mode that determines how source and destination alpha
83+
/// channels are combined using Porter-Duff operators.
8284
/// Defaults to <see cref="PixelAlphaCompositionMode.SrcOver"/>.
8385
/// </summary>
8486
public PixelAlphaCompositionMode AlphaCompositionMode { get; set; } = PixelAlphaCompositionMode.SrcOver;

0 commit comments

Comments
 (0)