Skip to content

Commit 33aa0c8

Browse files
committed
Apply ICC profile when decoding InterlacedRgba PNG
1 parent 7d06e6d commit 33aa0c8

File tree

9 files changed

+205
-55
lines changed

9 files changed

+205
-55
lines changed

src/ImageSharp/ColorProfiles/Rgb.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,17 @@ public static Rgb FromScaledVector4(Vector4 source)
100100
public Vector4 ToScaledVector4()
101101
=> new(this.AsVector3Unsafe(), 1F);
102102

103+
/// <summary>
104+
/// Expands the color into a generic ("scaled") <see cref="Vector4"/> representation
105+
/// with values scaled and usually clamped between <value>0</value> and <value>1</value>.
106+
/// The vector components are typically expanded in least to greatest significance order.
107+
/// </summary>
108+
/// <param name="alpha">The alpha component.</param>
109+
/// <returns>The <see cref="Vector4"/>.</returns>
110+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
111+
public Vector4 ToScaledVector4(float alpha)
112+
=> new(this.AsVector3Unsafe(), 1F);
113+
103114
/// <inheritdoc/>
104115
public static void ToScaledVector4(ReadOnlySpan<Rgb> source, Span<Vector4> destination)
105116
{

src/ImageSharp/Formats/Png/PngDecoderCore.cs

Lines changed: 55 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, Cance
216216
currentFrameControl = this.ReadFrameControlChunk(chunk.Data.GetSpan());
217217
break;
218218
case PngChunkType.FrameData:
219+
{
219220
if (frameCount >= this.maxFrames)
220221
{
221222
goto EOF;
@@ -233,13 +234,19 @@ protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, Cance
233234

234235
this.InitializeFrame(previousFrameControl, currentFrameControl.Value, image, previousFrame, out currentFrame);
235236

237+
if (this.Options.TryGetIccProfileForColorConversion(metadata.IccProfile, out IccProfile? iccProfile))
238+
{
239+
metadata.IccProfile = null;
240+
}
241+
236242
this.currentStream.Position += 4;
237243
this.ReadScanlines(
238244
chunk.Length - 4,
239245
currentFrame,
240246
pngMetadata,
241247
this.ReadNextFrameDataChunk,
242248
currentFrameControl.Value,
249+
iccProfile,
243250
cancellationToken);
244251

245252
// if current frame dispose is restore to previous, then from future frame's perspective, it never happened
@@ -250,7 +257,10 @@ protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, Cance
250257
}
251258

252259
break;
260+
}
261+
253262
case PngChunkType.Data:
263+
{
254264
pngMetadata.AnimateRootFrame = currentFrameControl != null;
255265
currentFrameControl ??= new FrameControl((uint)this.header.Width, (uint)this.header.Height);
256266
if (image is null)
@@ -261,12 +271,18 @@ protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, Cance
261271
AssignColorPalette(this.palette, this.paletteAlpha, pngMetadata);
262272
}
263273

274+
if (this.Options.TryGetIccProfileForColorConversion(metadata.IccProfile, out IccProfile? iccProfile))
275+
{
276+
metadata.IccProfile = null;
277+
}
278+
264279
this.ReadScanlines(
265280
chunk.Length,
266281
image.Frames.RootFrame,
267282
pngMetadata,
268283
this.ReadNextDataChunk,
269284
currentFrameControl.Value,
285+
iccProfile,
270286
cancellationToken);
271287
if (pngMetadata.AnimateRootFrame)
272288
{
@@ -280,6 +296,8 @@ protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, Cance
280296
}
281297

282298
break;
299+
}
300+
283301
case PngChunkType.Palette:
284302
this.palette = chunk.Data.GetSpan().ToArray();
285303
break;
@@ -327,9 +345,9 @@ protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, Cance
327345
PngThrowHelper.ThrowNoData();
328346
}
329347

330-
if (this.Options.TryGetIccProfileForColorConversion(metadata.IccProfile, out IccProfile? iccProfile))
348+
if (this.Options.TryGetIccProfileForColorConversion(metadata.IccProfile, out IccProfile? iccProfileToApply))
331349
{
332-
ApplyRgbaCompatibleIccProfile(image, iccProfile, CompactSrgbV4Profile.Profile);
350+
ApplyRgbaCompatibleIccProfile(image, iccProfileToApply, CompactSrgbV4Profile.Profile);
333351
}
334352

335353
return image;
@@ -752,13 +770,15 @@ private int CalculateScanlineLength(int width)
752770
/// <param name="pngMetadata">The png metadata</param>
753771
/// <param name="getData">A delegate to get more data from the inner stream for <see cref="ZlibInflateStream"/>.</param>
754772
/// <param name="frameControl">The frame control</param>
773+
/// <param name="iccProfile">Optional ICC profile for color conversion.</param>
755774
/// <param name="cancellationToken">The cancellation token.</param>
756775
private void ReadScanlines<TPixel>(
757776
int chunkLength,
758777
ImageFrame<TPixel> image,
759778
PngMetadata pngMetadata,
760779
Func<int> getData,
761780
in FrameControl frameControl,
781+
IccProfile? iccProfile,
762782
CancellationToken cancellationToken)
763783
where TPixel : unmanaged, IPixel<TPixel>
764784
{
@@ -772,11 +792,11 @@ private void ReadScanlines<TPixel>(
772792

773793
if (this.header.InterlaceMethod is PngInterlaceMode.Adam7)
774794
{
775-
this.DecodeInterlacedPixelData(frameControl, dataStream, image, pngMetadata, cancellationToken);
795+
this.DecodeInterlacedPixelData(frameControl, dataStream, image, pngMetadata, iccProfile, cancellationToken);
776796
}
777797
else
778798
{
779-
this.DecodePixelData(frameControl, dataStream, image, pngMetadata, cancellationToken);
799+
this.DecodePixelData(frameControl, dataStream, image, pngMetadata, iccProfile, cancellationToken);
780800
}
781801
}
782802

@@ -788,12 +808,14 @@ private void ReadScanlines<TPixel>(
788808
/// <param name="compressedStream">The compressed pixel data stream.</param>
789809
/// <param name="imageFrame">The image frame to decode to.</param>
790810
/// <param name="pngMetadata">The png metadata</param>
811+
/// <param name="iccProfile">Optional ICC profile for color conversion.</param>
791812
/// <param name="cancellationToken">The CancellationToken</param>
792813
private void DecodePixelData<TPixel>(
793814
FrameControl frameControl,
794815
DeflateStream compressedStream,
795816
ImageFrame<TPixel> imageFrame,
796817
PngMetadata pngMetadata,
818+
IccProfile? iccProfile,
797819
CancellationToken cancellationToken)
798820
where TPixel : unmanaged, IPixel<TPixel>
799821
{
@@ -860,7 +882,7 @@ private void DecodePixelData<TPixel>(
860882
break;
861883
}
862884

863-
this.ProcessDefilteredScanline(frameControl, currentRow, scanSpan, imageFrame, pngMetadata, blendRowBuffer);
885+
this.ProcessDefilteredScanline(frameControl, currentRow, scanSpan, imageFrame, pngMetadata, blendRowBuffer, iccProfile);
864886
this.SwapScanlineBuffers();
865887
currentRow++;
866888
}
@@ -878,12 +900,14 @@ private void DecodePixelData<TPixel>(
878900
/// <param name="compressedStream">The compressed pixel data stream.</param>
879901
/// <param name="imageFrame">The current image frame.</param>
880902
/// <param name="pngMetadata">The png metadata.</param>
903+
/// <param name="iccProfile">Optional ICC profile for color conversion.</param>
881904
/// <param name="cancellationToken">The cancellation token.</param>
882905
private void DecodeInterlacedPixelData<TPixel>(
883906
in FrameControl frameControl,
884907
DeflateStream compressedStream,
885908
ImageFrame<TPixel> imageFrame,
886909
PngMetadata pngMetadata,
910+
IccProfile? iccProfile,
887911
CancellationToken cancellationToken)
888912
where TPixel : unmanaged, IPixel<TPixel>
889913
{
@@ -974,6 +998,7 @@ private void DecodeInterlacedPixelData<TPixel>(
974998
rowSpan,
975999
pngMetadata,
9761000
blendRowBuffer,
1001+
iccProfile,
9771002
pixelOffset: Adam7.FirstColumn[pass],
9781003
increment: Adam7.ColumnIncrement[pass]);
9791004

@@ -1012,13 +1037,15 @@ private void DecodeInterlacedPixelData<TPixel>(
10121037
/// <param name="pixels">The image</param>
10131038
/// <param name="pngMetadata">The png metadata.</param>
10141039
/// <param name="blendRowBuffer">A span used to temporarily hold the decoded row pixel data for alpha blending.</param>
1040+
/// <param name="iccProfile">Optional ICC profile for color conversion.</param>
10151041
private void ProcessDefilteredScanline<TPixel>(
10161042
in FrameControl frameControl,
10171043
int currentRow,
10181044
ReadOnlySpan<byte> scanline,
10191045
ImageFrame<TPixel> pixels,
10201046
PngMetadata pngMetadata,
1021-
Span<TPixel> blendRowBuffer)
1047+
Span<TPixel> blendRowBuffer,
1048+
IccProfile? iccProfile)
10221049
where TPixel : unmanaged, IPixel<TPixel>
10231050
{
10241051
Span<TPixel> destination = pixels.PixelBuffer.DangerousGetRowSpan(currentRow);
@@ -1052,7 +1079,8 @@ private void ProcessDefilteredScanline<TPixel>(
10521079
in frameControl,
10531080
scanlineSpan,
10541081
rowSpan,
1055-
pngMetadata.TransparentColor);
1082+
pngMetadata.TransparentColor,
1083+
iccProfile);
10561084

10571085
break;
10581086

@@ -1063,7 +1091,8 @@ private void ProcessDefilteredScanline<TPixel>(
10631091
scanlineSpan,
10641092
rowSpan,
10651093
(uint)this.bytesPerPixel,
1066-
(uint)this.bytesPerSample);
1094+
(uint)this.bytesPerSample,
1095+
iccProfile);
10671096

10681097
break;
10691098

@@ -1072,7 +1101,8 @@ private void ProcessDefilteredScanline<TPixel>(
10721101
in frameControl,
10731102
scanlineSpan,
10741103
rowSpan,
1075-
pngMetadata.ColorTable);
1104+
pngMetadata.ColorTable,
1105+
iccProfile);
10761106

10771107
break;
10781108

@@ -1085,7 +1115,8 @@ private void ProcessDefilteredScanline<TPixel>(
10851115
rowSpan,
10861116
this.bytesPerPixel,
10871117
this.bytesPerSample,
1088-
pngMetadata.TransparentColor);
1118+
pngMetadata.TransparentColor,
1119+
iccProfile);
10891120

10901121
break;
10911122

@@ -1097,7 +1128,8 @@ private void ProcessDefilteredScanline<TPixel>(
10971128
scanlineSpan,
10981129
rowSpan,
10991130
this.bytesPerPixel,
1100-
this.bytesPerSample);
1131+
this.bytesPerSample,
1132+
iccProfile);
11011133

11021134
break;
11031135
}
@@ -1124,6 +1156,7 @@ private void ProcessDefilteredScanline<TPixel>(
11241156
/// <param name="destination">The current image row.</param>
11251157
/// <param name="pngMetadata">The png metadata.</param>
11261158
/// <param name="blendRowBuffer">A span used to temporarily hold the decoded row pixel data for alpha blending.</param>
1159+
/// <param name="iccProfile">Optional ICC profile for color conversion.</param>
11271160
/// <param name="pixelOffset">The column start index. Always 0 for none interlaced images.</param>
11281161
/// <param name="increment">The column increment. Always 1 for none interlaced images.</param>
11291162
private void ProcessInterlacedDefilteredScanline<TPixel>(
@@ -1132,6 +1165,7 @@ private void ProcessInterlacedDefilteredScanline<TPixel>(
11321165
Span<TPixel> destination,
11331166
PngMetadata pngMetadata,
11341167
Span<TPixel> blendRowBuffer,
1168+
IccProfile? iccProfile,
11351169
int pixelOffset = 0,
11361170
int increment = 1)
11371171
where TPixel : unmanaged, IPixel<TPixel>
@@ -1166,7 +1200,8 @@ private void ProcessInterlacedDefilteredScanline<TPixel>(
11661200
rowSpan,
11671201
(uint)pixelOffset,
11681202
(uint)increment,
1169-
pngMetadata.TransparentColor);
1203+
pngMetadata.TransparentColor,
1204+
iccProfile);
11701205

11711206
break;
11721207

@@ -1179,7 +1214,8 @@ private void ProcessInterlacedDefilteredScanline<TPixel>(
11791214
(uint)pixelOffset,
11801215
(uint)increment,
11811216
(uint)this.bytesPerPixel,
1182-
(uint)this.bytesPerSample);
1217+
(uint)this.bytesPerSample,
1218+
iccProfile);
11831219

11841220
break;
11851221

@@ -1190,7 +1226,8 @@ private void ProcessInterlacedDefilteredScanline<TPixel>(
11901226
rowSpan,
11911227
(uint)pixelOffset,
11921228
(uint)increment,
1193-
pngMetadata.ColorTable);
1229+
pngMetadata.ColorTable,
1230+
iccProfile);
11941231

11951232
break;
11961233

@@ -1205,7 +1242,8 @@ private void ProcessInterlacedDefilteredScanline<TPixel>(
12051242
(uint)increment,
12061243
this.bytesPerPixel,
12071244
this.bytesPerSample,
1208-
pngMetadata.TransparentColor);
1245+
pngMetadata.TransparentColor,
1246+
iccProfile);
12091247

12101248
break;
12111249

@@ -1219,7 +1257,8 @@ private void ProcessInterlacedDefilteredScanline<TPixel>(
12191257
(uint)pixelOffset,
12201258
(uint)increment,
12211259
this.bytesPerPixel,
1222-
this.bytesPerSample);
1260+
this.bytesPerSample,
1261+
iccProfile);
12231262

12241263
break;
12251264
}

0 commit comments

Comments
 (0)