Browse Source

Restore shared decoder, ICC, and Point APIs to upstream main

Restore ImageDecoderCore, DecoderOptions, IccProfile, Point, and PointTests.
Keep HEIF box-integrity policy inside HeifDecoderCore, use the existing ICC
serialization API, and replace AV1 Point shifts with coordinate shifts.
Include the PixelOperations formatting correction.

The current implementations passed Release net11.0 compilation, 55 Point
tests, 70 public HEIF encoder/grid tests, and 38 sequence tests. This commit
stages the shared API restorations and their caller changes only; the region
and component-domain ICC refactor remains in progress and is not claimed
as a verified codec checkpoint.
pull/2633/head
James Jackson-South 4 weeks ago
parent
commit
93f48a480d
  1. 2
      src/ImageSharp/Formats/DecoderOptions.cs
  2. 4
      src/ImageSharp/Formats/Heif/Av1/Av1CodecConfiguration.cs
  3. 12
      src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ModeDecision.cs
  4. 2
      src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ReferenceModeDecision.cs
  5. 2
      src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.cs
  6. 6
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileWriter.cs
  7. 60
      src/ImageSharp/Formats/Heif/HeifDecoderCore.cs
  8. 2
      src/ImageSharp/Formats/Heif/HeifEncoderCore.Sequence.cs
  9. 2
      src/ImageSharp/Formats/Heif/HeifItem.cs
  10. 12
      src/ImageSharp/Formats/Heif/HeifSequenceParser.cs
  11. 80
      src/ImageSharp/Formats/ImageDecoderCore.cs
  12. 16
      src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs
  13. 1
      src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs
  14. 36
      src/ImageSharp/Primitives/Point.cs
  15. 2
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1IntraSuperblockEncoderTests.cs
  16. 23
      tests/ImageSharp.Tests/Primitives/PointTests.cs

2
src/ImageSharp/Formats/DecoderOptions.cs

@ -78,7 +78,7 @@ public sealed class DecoderOptions
return false; return false;
} }
if (this.ColorProfileHandling != ColorProfileHandling.Convert) if (this.ColorProfileHandling == ColorProfileHandling.Preserve)
{ {
return false; return false;
} }

4
src/ImageSharp/Formats/Heif/Av1/Av1CodecConfiguration.cs

@ -382,7 +382,7 @@ internal sealed class Av1CodecConfiguration
contentLightLevel = dataObuContentLightLevel ?? this.configContentLightLevel; contentLightLevel = dataObuContentLightLevel ?? this.configContentLightLevel;
masteringDisplayColorVolume = dataObuMasteringDisplayColorVolume ?? this.configMasteringDisplayColorVolume; masteringDisplayColorVolume = dataObuMasteringDisplayColorVolume ?? this.configMasteringDisplayColorVolume;
} }
catch (Exception ex) when (ImageDecoderCore.ShouldIgnoreAncillarySegmentError(options, ex)) catch (Exception ex) when (HeifDecoderCore.ShouldIgnoreAncillarySegmentError(options, ex))
{ {
// Conflicting optional OBU metadata is discarded without weakening OBU framing or sequence-header checks. // Conflicting optional OBU metadata is discarded without weakening OBU framing or sequence-header checks.
} }
@ -563,7 +563,7 @@ internal sealed class Av1CodecConfiguration
masteringDisplayColorVolume = obuMasteringDisplayColorVolume; masteringDisplayColorVolume = obuMasteringDisplayColorVolume;
} }
} }
catch (Exception ex) when (ImageDecoderCore.ShouldIgnoreAncillarySegmentError(options, ex)) catch (Exception ex) when (HeifDecoderCore.ShouldIgnoreAncillarySegmentError(options, ex))
{ {
// The OBU payload remains bounded by the image-data scan; only its invalid optional metadata is discarded. // The OBU payload remains bounded by the image-data scan; only its invalid optional metadata is discarded.
} }

12
src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ModeDecision.cs

@ -88,7 +88,7 @@ internal static partial class Av1IntraSuperblockEncoder
ref int partitionIndex) ref int partitionIndex)
{ {
Av1EncoderCommon common = picture.Parent.Common; Av1EncoderCommon common = picture.Parent.Common;
Point modeInfoPosition = blockOrigin >> Av1Constants.ModeInfoSizeLog2; Point modeInfoPosition = new(blockOrigin.X >> Av1Constants.ModeInfoSizeLog2, blockOrigin.Y >> Av1Constants.ModeInfoSizeLog2);
if (modeInfoPosition.Y >= common.ModeInfoRowCount || modeInfoPosition.X >= common.ModeInfoColumnCount) if (modeInfoPosition.Y >= common.ModeInfoRowCount || modeInfoPosition.X >= common.ModeInfoColumnCount)
{ {
return; return;
@ -219,7 +219,7 @@ internal static partial class Av1IntraSuperblockEncoder
return preparedPartition; return preparedPartition;
} }
Point modeInfoPosition = blockOrigin >> Av1Constants.ModeInfoSizeLog2; Point modeInfoPosition = new(blockOrigin.X >> Av1Constants.ModeInfoSizeLog2, blockOrigin.Y >> Av1Constants.ModeInfoSizeLog2);
bool hasRows = bool hasRows =
modeInfoPosition.Y + blockSize.Get4x4HighCount() <= this.picture.Parent.Common.ModeInfoRowCount; modeInfoPosition.Y + blockSize.Get4x4HighCount() <= this.picture.Parent.Common.ModeInfoRowCount;
@ -479,7 +479,7 @@ internal static partial class Av1IntraSuperblockEncoder
private bool IsBlockOriginInsideFrame(Point blockOrigin) private bool IsBlockOriginInsideFrame(Point blockOrigin)
{ {
Point modeInfoPosition = blockOrigin >> Av1Constants.ModeInfoSizeLog2; Point modeInfoPosition = new(blockOrigin.X >> Av1Constants.ModeInfoSizeLog2, blockOrigin.Y >> Av1Constants.ModeInfoSizeLog2);
return modeInfoPosition.Y < this.picture.Parent.Common.ModeInfoRowCount && return modeInfoPosition.Y < this.picture.Parent.Common.ModeInfoRowCount &&
modeInfoPosition.X < this.picture.Parent.Common.ModeInfoColumnCount; modeInfoPosition.X < this.picture.Parent.Common.ModeInfoColumnCount;
} }
@ -596,7 +596,7 @@ internal static partial class Av1IntraSuperblockEncoder
}; };
modeInfo.CdefStrength = 0; modeInfo.CdefStrength = 0;
Point modeInfoPosition = blockOrigin >> Av1Constants.ModeInfoSizeLog2; Point modeInfoPosition = new(blockOrigin.X >> Av1Constants.ModeInfoSizeLog2, blockOrigin.Y >> Av1Constants.ModeInfoSizeLog2);
block.HasChroma = !this.source.IsMonochrome && block.HasChroma = !this.source.IsMonochrome &&
Av1TileReader.HasChroma(this.picture.Sequence.SequenceHeader, modeInfoPosition, blockSize); Av1TileReader.HasChroma(this.picture.Sequence.SequenceHeader, modeInfoPosition, blockSize);
@ -768,7 +768,7 @@ internal static partial class Av1IntraSuperblockEncoder
{ {
// Trial leaves must use the same reconstruction order as final leaves of this partition. // Trial leaves must use the same reconstruction order as final leaves of this partition.
this.SetBlockGeometry(blockOrigin, blockSize, partitionType); this.SetBlockGeometry(blockOrigin, blockSize, partitionType);
Point modeInfoPosition = blockOrigin >> Av1Constants.ModeInfoSizeLog2; Point modeInfoPosition = new(blockOrigin.X >> Av1Constants.ModeInfoSizeLog2, blockOrigin.Y >> Av1Constants.ModeInfoSizeLog2);
Av1TileWriter.SetModeInfoRowAndColumn( Av1TileWriter.SetModeInfoRowAndColumn(
this.picture, this.picture,
macroBlock, macroBlock,
@ -813,7 +813,7 @@ internal static partial class Av1IntraSuperblockEncoder
Av1BlockSize blockSize, Av1BlockSize blockSize,
Av1PartitionType partitionType) Av1PartitionType partitionType)
{ {
Point modeInfoPosition = blockOrigin >> Av1Constants.ModeInfoSizeLog2; Point modeInfoPosition = new(blockOrigin.X >> Av1Constants.ModeInfoSizeLog2, blockOrigin.Y >> Av1Constants.ModeInfoSizeLog2);
ref Av1MacroBlockModeInfo modeInfo = ref this.picture.GetMacroBlockModeInfo(modeInfoPosition); ref Av1MacroBlockModeInfo modeInfo = ref this.picture.GetMacroBlockModeInfo(modeInfoPosition);
modeInfo.Block = new Av1EncoderBlockModeInfo modeInfo.Block = new Av1EncoderBlockModeInfo
{ {

2
src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ReferenceModeDecision.cs

@ -480,7 +480,7 @@ internal static partial class Av1IntraSuperblockEncoder
this.blockWorkspace.GetInterPredictionWorkspace<TSample>(); this.blockWorkspace.GetInterPredictionWorkspace<TSample>();
ObuFrameHeader frameHeader = this.picture.Parent.FrameHeader; ObuFrameHeader frameHeader = this.picture.Parent.FrameHeader;
Point modeInfoPosition = blockOrigin >> Av1Constants.ModeInfoSizeLog2; Point modeInfoPosition = new(blockOrigin.X >> Av1Constants.ModeInfoSizeLog2, blockOrigin.Y >> Av1Constants.ModeInfoSizeLog2);
ref Av1ReferenceMotionVectors referenceMotionVectors = ref this.blockWorkspace.ReferenceMotionVectors; ref Av1ReferenceMotionVectors referenceMotionVectors = ref this.blockWorkspace.ReferenceMotionVectors;
referenceMotionVectors.Build( referenceMotionVectors.Build(
this.picture, this.picture,

2
src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.cs

@ -187,7 +187,7 @@ internal static partial class Av1IntraSuperblockEncoder
public void EncodePartitionTree(Point blockOrigin, Av1BlockSize blockSize) public void EncodePartitionTree(Point blockOrigin, Av1BlockSize blockSize)
{ {
Av1EncoderCommon common = this.picture.Parent.Common; Av1EncoderCommon common = this.picture.Parent.Common;
Point modeInfoPosition = blockOrigin >> Av1Constants.ModeInfoSizeLog2; Point modeInfoPosition = new(blockOrigin.X >> Av1Constants.ModeInfoSizeLog2, blockOrigin.Y >> Av1Constants.ModeInfoSizeLog2);
if (modeInfoPosition.Y >= common.ModeInfoRowCount || modeInfoPosition.X >= common.ModeInfoColumnCount) if (modeInfoPosition.Y >= common.ModeInfoRowCount || modeInfoPosition.X >= common.ModeInfoColumnCount)
{ {
return; return;

6
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileWriter.cs

@ -282,7 +282,7 @@ internal partial class Av1TileWriter
(childIndex & 1) * halfBlockSize, (childIndex & 1) * halfBlockSize,
(childIndex >> 1) * halfBlockSize); (childIndex >> 1) * halfBlockSize);
Point childModeInfoPosition = childOrigin >> Av1Constants.ModeInfoSizeLog2; Point childModeInfoPosition = new(childOrigin.X >> Av1Constants.ModeInfoSizeLog2, childOrigin.Y >> Av1Constants.ModeInfoSizeLog2);
if (childModeInfoPosition.Y >= common.ModeInfoRowCount || if (childModeInfoPosition.Y >= common.ModeInfoRowCount ||
childModeInfoPosition.X >= common.ModeInfoColumnCount) childModeInfoPosition.X >= common.ModeInfoColumnCount)
{ {
@ -791,7 +791,7 @@ internal partial class Av1TileWriter
out bool hasColumns) out bool hasColumns)
{ {
int halfBlockModeInfoCount = blockSize.Get4x4WideCount() >> 1; int halfBlockModeInfoCount = blockSize.Get4x4WideCount() >> 1;
Point modeInfoPosition = blockOrigin >> Av1Constants.ModeInfoSizeLog2; Point modeInfoPosition = new(blockOrigin.X >> Av1Constants.ModeInfoSizeLog2, blockOrigin.Y >> Av1Constants.ModeInfoSizeLog2);
hasRows = modeInfoPosition.Y + halfBlockModeInfoCount < pcs.Parent.Common.ModeInfoRowCount; hasRows = modeInfoPosition.Y + halfBlockModeInfoCount < pcs.Parent.Common.ModeInfoRowCount;
hasColumns = modeInfoPosition.X + halfBlockModeInfoCount < pcs.Parent.Common.ModeInfoColumnCount; hasColumns = modeInfoPosition.X + halfBlockModeInfoCount < pcs.Parent.Common.ModeInfoColumnCount;
int leftIndex = partitionContexts.GetLeftIndex(blockOrigin); int leftIndex = partitionContexts.GetLeftIndex(blockOrigin);
@ -2024,7 +2024,7 @@ internal partial class Av1TileWriter
Av1NeighborArrayUnit<byte> luma_dc_sign_level_coeff_na = pcs.LuminanceDcSignLevelCoefficientNeighbors[tile_idx]; Av1NeighborArrayUnit<byte> luma_dc_sign_level_coeff_na = pcs.LuminanceDcSignLevelCoefficientNeighbors[tile_idx];
Av1NeighborArrayUnit<byte> cr_dc_sign_level_coeff_na = pcs.CrDcSignLevelCoefficientNeighbors[tile_idx]; Av1NeighborArrayUnit<byte> cr_dc_sign_level_coeff_na = pcs.CrDcSignLevelCoefficientNeighbors[tile_idx];
Av1NeighborArrayUnit<byte> cb_dc_sign_level_coeff_na = pcs.CbDcSignLevelCoefficientNeighbors[tile_idx]; Av1NeighborArrayUnit<byte> cb_dc_sign_level_coeff_na = pcs.CbDcSignLevelCoefficientNeighbors[tile_idx];
Point modeInfoPosition = blockOrigin >> Av1Constants.ModeInfoSizeLog2; Point modeInfoPosition = new(blockOrigin.X >> Av1Constants.ModeInfoSizeLog2, blockOrigin.Y >> Av1Constants.ModeInfoSizeLog2);
ref Av1MacroBlockModeInfo mbmi = ref pcs.GetMacroBlockModeInfo(modeInfoPosition); ref Av1MacroBlockModeInfo mbmi = ref pcs.GetMacroBlockModeInfo(modeInfoPosition);
bool skip_coeff = mbmi.Block.Skip; bool skip_coeff = mbmi.Block.Skip;

60
src/ImageSharp/Formats/Heif/HeifDecoderCore.cs

@ -1289,12 +1289,12 @@ internal sealed class HeifDecoderCore : ImageDecoderCore
this.itemLinks.Add(link); this.itemLinks.Add(link);
} }
catch (Exception ex) when (linkType == Heif4CharCode.Cdsc && ImageDecoderCore.ShouldIgnoreAncillarySegmentError(this.Options, ex)) catch (Exception ex) when (linkType == Heif4CharCode.Cdsc && HeifDecoderCore.ShouldIgnoreAncillarySegmentError(this.Options, ex))
{ {
// A malformed descriptive link cannot change reconstructed pixels, so non-strict modes omit it. // A malformed descriptive link cannot change reconstructed pixels, so non-strict modes omit it.
bytesRead = referenceEnd; bytesRead = referenceEnd;
} }
catch (Exception ex) when (linkType != Heif4CharCode.Cdsc && ImageDecoderCore.ShouldIgnoreImageDataSegmentError(this.Options, ex)) catch (Exception ex) when (linkType != Heif4CharCode.Cdsc && HeifDecoderCore.ShouldIgnoreImageDataSegmentError(this.Options, ex))
{ {
// IgnoreImageData permits a malformed optional image relationship to be omitted while retaining // IgnoreImageData permits a malformed optional image relationship to be omitted while retaining
// independently reconstructable items and thumbnail fallbacks. // independently reconstructable items and thumbnail fallbacks.
@ -1440,7 +1440,7 @@ internal sealed class HeifDecoderCore : ImageDecoderCore
{ {
iccProfile = HeifPropertyParser.ParseIccProfile(profileData); iccProfile = HeifPropertyParser.ParseIccProfile(profileData);
} }
catch (Exception ex) when (ImageDecoderCore.ShouldIgnoreAncillarySegmentError(this.Options, ex)) catch (Exception ex) when (HeifDecoderCore.ShouldIgnoreAncillarySegmentError(this.Options, ex))
{ {
// Keep the understood property index without retaining invalid ancillary metadata. // Keep the understood property index without retaining invalid ancillary metadata.
} }
@ -1482,7 +1482,7 @@ internal sealed class HeifDecoderCore : ImageDecoderCore
{ {
pixelAspectRatio = HeifPropertyParser.ParsePixelAspectRatio(boxBuffer); pixelAspectRatio = HeifPropertyParser.ParsePixelAspectRatio(boxBuffer);
} }
catch (Exception ex) when (ImageDecoderCore.ShouldIgnoreAncillarySegmentError(this.Options, ex)) catch (Exception ex) when (HeifDecoderCore.ShouldIgnoreAncillarySegmentError(this.Options, ex))
{ {
// Keep the understood property index without retaining invalid ancillary metadata. // Keep the understood property index without retaining invalid ancillary metadata.
} }
@ -1555,7 +1555,7 @@ internal sealed class HeifDecoderCore : ImageDecoderCore
{ {
contentLightLevel = HeifPropertyParser.ParseContentLightLevel(boxBuffer); contentLightLevel = HeifPropertyParser.ParseContentLightLevel(boxBuffer);
} }
catch (Exception ex) when (ImageDecoderCore.ShouldIgnoreAncillarySegmentError(this.Options, ex)) catch (Exception ex) when (HeifDecoderCore.ShouldIgnoreAncillarySegmentError(this.Options, ex))
{ {
// Keep the understood property index without retaining invalid ancillary metadata. // Keep the understood property index without retaining invalid ancillary metadata.
} }
@ -1568,7 +1568,7 @@ internal sealed class HeifDecoderCore : ImageDecoderCore
{ {
masteringDisplayColorVolume = HeifPropertyParser.ParseMasteringDisplayColorVolume(boxBuffer); masteringDisplayColorVolume = HeifPropertyParser.ParseMasteringDisplayColorVolume(boxBuffer);
} }
catch (Exception ex) when (ImageDecoderCore.ShouldIgnoreAncillarySegmentError(this.Options, ex)) catch (Exception ex) when (HeifDecoderCore.ShouldIgnoreAncillarySegmentError(this.Options, ex))
{ {
// Keep the understood property index without retaining invalid ancillary metadata. // Keep the understood property index without retaining invalid ancillary metadata.
} }
@ -1581,7 +1581,7 @@ internal sealed class HeifDecoderCore : ImageDecoderCore
{ {
contentColorVolume = HeifPropertyParser.ParseContentColorVolume(boxBuffer); contentColorVolume = HeifPropertyParser.ParseContentColorVolume(boxBuffer);
} }
catch (Exception ex) when (ImageDecoderCore.ShouldIgnoreAncillarySegmentError(this.Options, ex)) catch (Exception ex) when (HeifDecoderCore.ShouldIgnoreAncillarySegmentError(this.Options, ex))
{ {
// Keep the understood property index without retaining invalid ancillary metadata. // Keep the understood property index without retaining invalid ancillary metadata.
} }
@ -1594,7 +1594,7 @@ internal sealed class HeifDecoderCore : ImageDecoderCore
{ {
ambientViewingEnvironment = HeifPropertyParser.ParseAmbientViewingEnvironment(boxBuffer); ambientViewingEnvironment = HeifPropertyParser.ParseAmbientViewingEnvironment(boxBuffer);
} }
catch (Exception ex) when (ImageDecoderCore.ShouldIgnoreAncillarySegmentError(this.Options, ex)) catch (Exception ex) when (HeifDecoderCore.ShouldIgnoreAncillarySegmentError(this.Options, ex))
{ {
// Keep the understood property index without retaining invalid ancillary metadata. // Keep the understood property index without retaining invalid ancillary metadata.
} }
@ -1607,7 +1607,7 @@ internal sealed class HeifDecoderCore : ImageDecoderCore
{ {
referenceViewingEnvironment = HeifPropertyParser.ParseReferenceViewingEnvironment(boxBuffer); referenceViewingEnvironment = HeifPropertyParser.ParseReferenceViewingEnvironment(boxBuffer);
} }
catch (Exception ex) when (ImageDecoderCore.ShouldIgnoreAncillarySegmentError(this.Options, ex)) catch (Exception ex) when (HeifDecoderCore.ShouldIgnoreAncillarySegmentError(this.Options, ex))
{ {
// Keep the understood property index without retaining invalid ancillary metadata. // Keep the understood property index without retaining invalid ancillary metadata.
} }
@ -1620,7 +1620,7 @@ internal sealed class HeifDecoderCore : ImageDecoderCore
{ {
nominalDiffuseWhite = HeifPropertyParser.ParseNominalDiffuseWhite(boxBuffer); nominalDiffuseWhite = HeifPropertyParser.ParseNominalDiffuseWhite(boxBuffer);
} }
catch (Exception ex) when (ImageDecoderCore.ShouldIgnoreAncillarySegmentError(this.Options, ex)) catch (Exception ex) when (HeifDecoderCore.ShouldIgnoreAncillarySegmentError(this.Options, ex))
{ {
// Keep the understood property index without retaining invalid ancillary metadata. // Keep the understood property index without retaining invalid ancillary metadata.
} }
@ -1683,7 +1683,7 @@ internal sealed class HeifDecoderCore : ImageDecoderCore
break; break;
} }
} }
catch (Exception ex) when (ImageDecoderCore.ShouldIgnoreImageDataSegmentError(this.Options, ex)) catch (Exception ex) when (HeifDecoderCore.ShouldIgnoreImageDataSegmentError(this.Options, ex))
{ {
// Invalid image properties retain their physical association index. Typed association handling ignores // Invalid image properties retain their physical association index. Typed association handling ignores
// the placeholder so another decodable item or the coded-image defaults can remain usable. // the placeholder so another decodable item or the coded-image defaults can remain usable.
@ -3153,4 +3153,42 @@ internal sealed class HeifDecoderCore : ImageDecoderCore
bytesRead = terminator + 1; bytesRead = terminator + 1;
return Encoding.UTF8.GetString(span[..terminator]); return Encoding.UTF8.GetString(span[..terminator]);
} }
/// <summary>
/// Determines whether a malformed ancillary box can be omitted under the selected integrity policy.
/// </summary>
public static bool ShouldIgnoreAncillarySegmentError(DecoderOptions options, Exception exception)
=> options.SegmentIntegrityHandling is not SegmentIntegrityHandling.Strict
&& IsRecoverableBoxError(exception);
/// <summary>
/// Determines whether a malformed image-data box can be omitted under the selected integrity policy.
/// </summary>
internal static bool ShouldIgnoreImageDataSegmentError(DecoderOptions options, Exception exception)
=> options.SegmentIntegrityHandling is SegmentIntegrityHandling.IgnoreImageData
&& IsRecoverableBoxError(exception);
/// <summary>
/// Identifies malformed-box failures eligible for the segment integrity policy.
/// </summary>
/// <param name="exception">The failure raised while reading the box.</param>
/// <returns>Whether the failure describes recoverable image content.</returns>
private static bool IsRecoverableBoxError(Exception exception)
=> exception is ImageFormatException
or InvalidIccProfileException
or InvalidImageContentException
or InvalidOperationException
or NotSupportedException;
/// <summary>
/// Rejects malformed image properties unless image-data errors are explicitly ignored.
/// </summary>
/// <param name="message">The description of the malformed property.</param>
private void ThrowOrIgnoreImageDataSegmentError(string message)
{
if (this.Options.SegmentIntegrityHandling is not SegmentIntegrityHandling.IgnoreImageData)
{
throw new InvalidImageContentException(message);
}
}
} }

2
src/ImageSharp/Formats/Heif/HeifEncoderCore.Sequence.cs

@ -339,7 +339,7 @@ internal sealed partial class HeifEncoderCore
IccProfile? iccProfile = image.Metadata.IccProfile; IccProfile? iccProfile = image.Metadata.IccProfile;
if (!this.encoder.SkipMetadata && iccProfile is not null) if (!this.encoder.SkipMetadata && iccProfile is not null)
{ {
iccProfileData = iccProfile.GetDataForWriting(); iccProfileData = iccProfile.ToByteArray();
} }
return new HeifSequenceEncoding( return new HeifSequenceEncoding(

2
src/ImageSharp/Formats/Heif/HeifItem.cs

@ -224,7 +224,7 @@ internal sealed class HeifItem(Heif4CharCode type, uint id)
{ {
// Exact-size container writing queries the payload length before copying it. Retaining the serialized // Exact-size container writing queries the payload length before copying it. Retaining the serialized
// view on this transient item prevents an entry-built profile from being serialized for both passes. // view on this transient item prevents an entry-built profile from being serialized for both passes.
this.serializedIccProfile = this.iccProfile.GetDataForWriting(); this.serializedIccProfile = this.iccProfile.ToByteArray();
} }
return this.serializedIccProfile; return this.serializedIccProfile;

12
src/ImageSharp/Formats/Heif/HeifSequenceParser.cs

@ -162,7 +162,7 @@ internal sealed class HeifSequenceParser
{ {
ValidateAlphaTrack(colorTrack, alphaTrack); ValidateAlphaTrack(colorTrack, alphaTrack);
} }
catch (Exception ex) when (ImageDecoderCore.ShouldIgnoreImageDataSegmentError(this.options, ex)) catch (Exception ex) when (HeifDecoderCore.ShouldIgnoreImageDataSegmentError(this.options, ex))
{ {
// Alpha is optional image data. IgnoreImageData permits a malformed auxiliary sequence to be omitted while // Alpha is optional image data. IgnoreImageData permits a malformed auxiliary sequence to be omitted while
// retaining the independently decodable color presentation. // retaining the independently decodable color presentation.
@ -304,7 +304,7 @@ internal sealed class HeifSequenceParser
stream.Position = metadata.Offset; stream.Position = metadata.Offset;
track.Metadata = this.metadataParser.Parse(stream, metadata.Length, scratch); track.Metadata = this.metadataParser.Parse(stream, metadata.Length, scratch);
} }
catch (Exception ex) when (ImageDecoderCore.ShouldIgnoreAncillarySegmentError(this.options, ex)) catch (Exception ex) when (HeifDecoderCore.ShouldIgnoreAncillarySegmentError(this.options, ex))
{ {
// The validated parent range lets decoding continue safely without this optional metadata box. // The validated parent range lets decoding continue safely without this optional metadata box.
} }
@ -963,7 +963,7 @@ internal sealed class HeifSequenceParser
{ {
ParseTrackImageProperty(stream, childLength, childType, track, scratch); ParseTrackImageProperty(stream, childLength, childType, track, scratch);
} }
catch (Exception ex) when (ImageDecoderCore.ShouldIgnoreAncillarySegmentError(this.options, ex)) catch (Exception ex) when (HeifDecoderCore.ShouldIgnoreAncillarySegmentError(this.options, ex))
{ {
// The complete child range remains known, so optional metadata can be discarded safely. // The complete child range remains known, so optional metadata can be discarded safely.
} }
@ -977,7 +977,7 @@ internal sealed class HeifSequenceParser
{ {
ParseTrackImageProperty(stream, childLength, childType, track, scratch); ParseTrackImageProperty(stream, childLength, childType, track, scratch);
} }
catch (Exception ex) when (ImageDecoderCore.ShouldIgnoreImageDataSegmentError(this.options, ex)) catch (Exception ex) when (HeifDecoderCore.ShouldIgnoreImageDataSegmentError(this.options, ex))
{ {
// IgnoreImageData permits a recoverable presentation property to be omitted. // IgnoreImageData permits a recoverable presentation property to be omitted.
} }
@ -1125,7 +1125,7 @@ internal sealed class HeifSequenceParser
prefix = ReadPrefixFromStart(stream, boxLength, scratch, 11, "color information"); prefix = ReadPrefixFromStart(stream, boxLength, scratch, 11, "color information");
track.CicpProfile = HeifPropertyParser.ParseCicpProfile(prefix[4..]); track.CicpProfile = HeifPropertyParser.ParseCicpProfile(prefix[4..]);
} }
catch (Exception ex) when (ImageDecoderCore.ShouldIgnoreImageDataSegmentError(this.options, ex)) catch (Exception ex) when (HeifDecoderCore.ShouldIgnoreImageDataSegmentError(this.options, ex))
{ {
// IgnoreImageData permits the decoder to fall back to the coded sequence's color description. // IgnoreImageData permits the decoder to fall back to the coded sequence's color description.
} }
@ -1150,7 +1150,7 @@ internal sealed class HeifSequenceParser
HeifBoxReader.ReadExactly(stream, profileData, "Stream length is not sufficient for box content."); HeifBoxReader.ReadExactly(stream, profileData, "Stream length is not sufficient for box content.");
track.IccProfile = HeifPropertyParser.ParseIccProfile(profileData); track.IccProfile = HeifPropertyParser.ParseIccProfile(profileData);
} }
catch (Exception ex) when (ImageDecoderCore.ShouldIgnoreAncillarySegmentError(this.options, ex)) catch (Exception ex) when (HeifDecoderCore.ShouldIgnoreAncillarySegmentError(this.options, ex))
{ {
// A malformed optional ICC profile does not invalidate the coded image outside strict mode. // A malformed optional ICC profile does not invalidate the coded image outside strict mode.
} }

80
src/ImageSharp/Formats/ImageDecoderCore.cs

@ -39,7 +39,7 @@ internal abstract class ImageDecoderCore
/// <param name="action">The action.</param> /// <param name="action">The action.</param>
protected void ExecuteAncillarySegmentAction(Action action) protected void ExecuteAncillarySegmentAction(Action action)
{ {
if (!ShouldIgnoreAncillarySegmentErrors(this.Options)) if (this.Options.SegmentIntegrityHandling is SegmentIntegrityHandling.Strict)
{ {
action(); action();
return; return;
@ -49,7 +49,12 @@ internal abstract class ImageDecoderCore
{ {
action(); action();
} }
catch (Exception ex) when (IsRecoverableSegmentError(ex)) catch (Exception ex) when (ex
is ImageFormatException
or InvalidIccProfileException
or InvalidImageContentException
or InvalidOperationException
or NotSupportedException)
{ {
// Intentionally ignored in non-strict segment integrity modes. // Intentionally ignored in non-strict segment integrity modes.
} }
@ -61,7 +66,7 @@ internal abstract class ImageDecoderCore
/// <param name="action">The action.</param> /// <param name="action">The action.</param>
protected void ExecuteImageDataSegmentAction(Action action) protected void ExecuteImageDataSegmentAction(Action action)
{ {
if (!ShouldIgnoreImageDataSegmentErrors(this.Options)) if (this.Options.SegmentIntegrityHandling is not SegmentIntegrityHandling.IgnoreImageData)
{ {
action(); action();
return; return;
@ -71,62 +76,21 @@ internal abstract class ImageDecoderCore
{ {
action(); action();
} }
catch (Exception ex) when (IsRecoverableSegmentError(ex)) catch (Exception ex) when (ex
is ImageFormatException
or InvalidIccProfileException
or InvalidImageContentException
or InvalidOperationException
or NotSupportedException)
{ {
// Intentionally ignored when image data integrity handling is set to IgnoreImageData. // Intentionally ignored when image data integrity handling is set to IgnoreImageData.
} }
} }
/// <summary>
/// Determines whether an exception represents a recoverable image segment error.
/// </summary>
/// <param name="exception">The exception raised while processing an image segment.</param>
/// <returns><see langword="true"/> when a segment integrity policy may ignore the exception.</returns>
public static bool IsRecoverableSegmentError(Exception exception)
=> exception is ImageFormatException
or InvalidIccProfileException
or InvalidImageContentException
or InvalidOperationException
or NotSupportedException;
/// <summary>
/// Determines whether the configured policy permits recoverable ancillary-segment errors to be ignored.
/// </summary>
/// <param name="options">The general decoder options.</param>
/// <returns><see langword="true"/> when recoverable ancillary-segment errors may be ignored.</returns>
public static bool ShouldIgnoreAncillarySegmentErrors(DecoderOptions options)
=> options.SegmentIntegrityHandling is not SegmentIntegrityHandling.Strict;
/// <summary>
/// Determines whether the configured policy permits recoverable image-data-segment errors to be ignored.
/// </summary>
/// <param name="options">The general decoder options.</param>
/// <returns><see langword="true"/> when recoverable image-data-segment errors may be ignored.</returns>
public static bool ShouldIgnoreImageDataSegmentErrors(DecoderOptions options)
=> options.SegmentIntegrityHandling is SegmentIntegrityHandling.IgnoreImageData;
/// <summary>
/// Determines whether an ancillary-segment exception may be ignored by the configured decoder policy.
/// </summary>
/// <param name="options">The general decoder options.</param>
/// <param name="exception">The exception raised while processing an ancillary segment.</param>
/// <returns><see langword="true"/> when decoding may continue without the ancillary segment.</returns>
public static bool ShouldIgnoreAncillarySegmentError(DecoderOptions options, Exception exception)
=> ShouldIgnoreAncillarySegmentErrors(options) && IsRecoverableSegmentError(exception);
/// <summary>
/// Determines whether an image-data-segment exception may be ignored by the configured decoder policy.
/// </summary>
/// <param name="options">The general decoder options.</param>
/// <param name="exception">The exception raised while processing an image-data segment.</param>
/// <returns><see langword="true"/> when decoding may continue without the image-data segment.</returns>
public static bool ShouldIgnoreImageDataSegmentError(DecoderOptions options, Exception exception)
=> ShouldIgnoreImageDataSegmentErrors(options) && IsRecoverableSegmentError(exception);
/// <summary> /// <summary>
/// Throws unless the decoder is running in a non-strict segment integrity mode. /// Throws unless the decoder is running in a non-strict segment integrity mode.
/// Use this when ancillary parsing must continue locally after the error rather than returning through /// Use this only from within <see cref="ExecuteAncillarySegmentAction"/> when local control flow
/// <see cref="ExecuteAncillarySegmentAction"/>. /// must continue after the error.
/// </summary> /// </summary>
/// <param name="message">The exception message.</param> /// <param name="message">The exception message.</param>
protected void ThrowOrIgnoreNonStrictSegmentError(string message) protected void ThrowOrIgnoreNonStrictSegmentError(string message)
@ -137,18 +101,6 @@ internal abstract class ImageDecoderCore
} }
} }
/// <summary>
/// Throws unless the decoder permits recoverable image-data segment errors to be ignored.
/// </summary>
/// <param name="message">The exception message.</param>
protected void ThrowOrIgnoreImageDataSegmentError(string message)
{
if (!ShouldIgnoreImageDataSegmentErrors(this.Options))
{
throw new InvalidImageContentException(message);
}
}
/// <summary> /// <summary>
/// Reads the raw image information from the specified stream. /// Reads the raw image information from the specified stream.
/// </summary> /// </summary>

16
src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs

@ -177,22 +177,6 @@ public sealed partial class IccProfile : IDeepCloneable<IccProfile>
return IccWriter.Write(this); return IccWriter.Write(this);
} }
/// <summary>
/// Gets the serialized profile storage for an encoder that consumes it before the operation returns.
/// </summary>
/// <returns>The existing raw profile storage, or a newly serialized profile when this instance was built from entries.</returns>
internal ReadOnlyMemory<byte> GetDataForWriting()
{
if (this.data is not null)
{
// Internal encoders only read this memory during the current operation, so the immutable view avoids
// cloning an already-owned profile before it is copied into format-specific output.
return this.data;
}
return IccWriter.Write(this);
}
private void InitializeHeader() private void InitializeHeader()
{ {
if (this.header != null) if (this.header != null)

1
src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs

@ -348,6 +348,7 @@ public partial class PixelOperations<TPixel>
/// <summary> /// <summary>
/// Bulk operation that packs 3 separate RGB channels to <paramref name="destination"/>. /// Bulk operation that packs 3 separate RGB channels to <paramref name="destination"/>.
/// The destination must contain at least as many pixels as each source channel.
/// </summary> /// </summary>
/// <param name="redChannel">A <see cref="ReadOnlySpan{T}"/> to the red values.</param> /// <param name="redChannel">A <see cref="ReadOnlySpan{T}"/> to the red values.</param>
/// <param name="greenChannel">A <see cref="ReadOnlySpan{T}"/> to the green values.</param> /// <param name="greenChannel">A <see cref="ReadOnlySpan{T}"/> to the green values.</param>

36
src/ImageSharp/Primitives/Point.cs

@ -145,24 +145,6 @@ public struct Point : IEquatable<Point>
public static Point operator /(Point left, int right) public static Point operator /(Point left, int right)
=> new(left.X / right, left.Y / right); => new(left.X / right, left.Y / right);
/// <summary>
/// Shift <see cref="Point"/> to the right by a <see cref="int"/> amount producing <see cref="Point"/>.
/// </summary>
/// <param name="left">Shifted value of type <see cref="Point"/>.</param>
/// <param name="right">Shifted amount of type <see cref="int"/>.</param>
/// <returns>Result of type <see cref="Point"/>.</returns>
public static Point operator >>(Point left, int right)
=> new(left.X >> right, left.Y >> right);
/// <summary>
/// Shift <see cref="Point"/> to the left by a <see cref="int"/> amount producing <see cref="Point"/>.
/// </summary>
/// <param name="left">Shifted value of type <see cref="Point"/>.</param>
/// <param name="right">Shifted amount of type <see cref="int"/>.</param>
/// <returns>Result of type <see cref="Point"/>.</returns>
public static Point operator <<(Point left, int right)
=> new(left.X << right, left.Y << right);
/// <summary> /// <summary>
/// Compares two <see cref="Point"/> objects for equality. /// Compares two <see cref="Point"/> objects for equality.
/// </summary> /// </summary>
@ -298,24 +280,6 @@ public struct Point : IEquatable<Point>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Offset(Point point) => this.Offset(point.X, point.Y); public void Offset(Point point) => this.Offset(point.X, point.Y);
/// <summary>
/// Shifts the coordinate value of this <see cref="Point"/> to the right with the specified amount.
/// </summary>
/// <param name="point">The point to shift.</param>
/// <param name="bitCount">The number of bits to shift to the right.</param>
/// <returns>The <see cref="Point"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Point ShiftRight(Point point, int bitCount) => new(unchecked(point.X >> bitCount), unchecked(point.Y >> bitCount));
/// <summary>
/// Shifts the coordinate value of this <see cref="Point"/> to the left with the specified amount.
/// </summary>
/// <param name="point">The point to shift.</param>
/// <param name="bitCount">The number of bits to shift to the left.</param>
/// <returns>The <see cref="Point"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Point ShiftLeft(Point point, int bitCount) => new(unchecked(point.X << bitCount), unchecked(point.Y << bitCount));
/// <inheritdoc/> /// <inheritdoc/>
public override readonly int GetHashCode() => HashCode.Combine(this.X, this.Y); public override readonly int GetHashCode() => HashCode.Combine(this.X, this.Y);

2
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1IntraSuperblockEncoderTests.cs

@ -670,7 +670,7 @@ public class Av1IntraSuperblockEncoderTests
const int Width = 8; const int Width = 8;
const int Height = 8; const int Height = 8;
Point blockOrigin = new(isIntraBlockCopy ? 320 : 0, 0); Point blockOrigin = new(isIntraBlockCopy ? 320 : 0, 0);
Point modeInfoPosition = blockOrigin >> Av1Constants.ModeInfoSizeLog2; Point modeInfoPosition = new(blockOrigin.X >> Av1Constants.ModeInfoSizeLog2, blockOrigin.Y >> Av1Constants.ModeInfoSizeLog2);
int frameWidth = blockOrigin.X + Width; int frameWidth = blockOrigin.X + Width;
int superblockIndex = blockOrigin.X / 64; int superblockIndex = blockOrigin.X / 64;
ObuColorConfig colorConfig = new() ObuColorConfig colorConfig = new()

23
tests/ImageSharp.Tests/Primitives/PointTests.cs

@ -4,7 +4,7 @@
using System.Globalization; using System.Globalization;
using System.Numerics; using System.Numerics;
namespace SixLabors.ImageSharp.Tests.Primitives; namespace SixLabors.ImageSharp.Tests;
public class PointTests public class PointTests
{ {
@ -112,27 +112,6 @@ public class PointTests
Assert.Equal(subExpected, Point.Subtract(p, s)); Assert.Equal(subExpected, Point.Subtract(p, s));
} }
[Theory]
[InlineData(int.MaxValue, int.MaxValue, 5)]
[InlineData(int.MinValue, int.MinValue, 4)]
[InlineData(int.MaxValue, int.MaxValue, 2)]
[InlineData(0, 0, 3)]
public void ShiftTest(int x, int y, int s)
{
Point rightExpected, leftExpected, p = new Point(x, y);
unchecked
{
rightExpected = new Point(x >> s, y >> s);
leftExpected = new Point(x << s, y << s);
}
Assert.Equal(rightExpected, p >> s);
Assert.Equal(leftExpected, p << s);
Assert.Equal(rightExpected, Point.ShiftRight(p, s));
Assert.Equal(leftExpected, Point.ShiftLeft(p, s));
}
[Theory] [Theory]
[InlineData(float.MaxValue, float.MinValue)] [InlineData(float.MaxValue, float.MinValue)]
[InlineData(float.MinValue, float.MinValue)] [InlineData(float.MinValue, float.MinValue)]

Loading…
Cancel
Save