diff --git a/src/ImageSharp/Formats/Jxl/Ac/JxlAcStrategyImage.cs b/src/ImageSharp/Formats/Jxl/Ac/JxlAcStrategyImage.cs index f41d186e19..0b2bdb74ae 100644 --- a/src/ImageSharp/Formats/Jxl/Ac/JxlAcStrategyImage.cs +++ b/src/ImageSharp/Formats/Jxl/Ac/JxlAcStrategyImage.cs @@ -2,42 +2,40 @@ // Licensed under the Six Labors Split License. using System.Diagnostics; -using SixLabors.ImageSharp.Formats.Jxl.Memory; +using SixLabors.ImageSharp.Formats.Jxl.Memory.ImageTypes; namespace SixLabors.ImageSharp.Formats.Jxl.Ac; -internal sealed class JxlAcStrategyImage +internal sealed class JxlAcStrategyImage : IDisposable { private const byte Invalid = byte.MaxValue; // 255 private JxlImageB? layers; - private readonly Memory row; - private readonly int stride; + private Memory row; + private int stride; - public JxlMemoryManager MemoryManager => this.layers.MemoryManager; + public int XSize => this.layers!.XSize; - public int XSize => this.layers.XSize; + public int YSize => this.layers!.YSize; - public int YSize => this.layers.YSize; - - public int PixelsPerRow => this.layers.PixelsPerRow; + public int PixelsPerRow => this.layers!.PixelsPerRow; public JxlAcStrategyRow GetRow(int y, int xPrefix = 0) { - ReadOnlyMemory layerRow = this.layers.GetRow(y); + ReadOnlyMemory layerRow = this.layers!.GetRowBytesMemory(y); ReadOnlyMemory row = layerRow[xPrefix..]; return new JxlAcStrategyRow(row); } - public static JxlAcStrategyImage Create(JxlMemoryManager memoryManager, int xSize, int ySize) + public static JxlAcStrategyImage Create(Configuration memoryManager, int xSize, int ySize) { JxlAcStrategyImage image = new() { - layers = JxlImageB.Create(memoryManager, xSize, ySize) + layers = new JxlImageB(memoryManager, xSize, ySize) }; - image.row = image.layers.GetRow(0); + image.row = image.layers.GetRowBytesMemory(0); image.stride = image.layers.PixelsPerRow; return image; @@ -48,11 +46,11 @@ internal sealed class JxlAcStrategyImage int value = 0; int compare = ((int)type << 1) | 1; - for (int y = 0; y < this.layers.YSize; y++) + for (int y = 0; y < this.layers!.YSize; y++) { - ReadOnlySpan row = this.layers.GetRowSpan(y); + ReadOnlySpan row = this.layers!.GetRow(y); - for (int x = 0; x < this.layers.XSize; x++) + for (int x = 0; x < this.layers!.XSize; x++) { if (row[x] == compare) { @@ -100,7 +98,7 @@ internal sealed class JxlAcStrategyImage #if DEBUG JxlAcStrategy strategy = new(type); - Debug.Assert(y + strategy.CoveredBlocksY <= this.layers.YSize, "Invalid range"); + Debug.Assert(y + strategy.CoveredBlocksY <= this.layers!.YSize, "Invalid range"); Debug.Assert(x + strategy.CoveredBlocksX <= this.layers.XSize, "Invalid range"); #endif @@ -112,4 +110,10 @@ internal sealed class JxlAcStrategyImage public void FillDct8() => this.FillDct8(in this.layers.GetRectangle()); public void FillInvalid() => this.FillImage(Invalid, this.layers); + + public void Dispose() + { + this.layers?.Dispose(); + GC.SuppressFinalize(this); + } } diff --git a/src/ImageSharp/Formats/Jxl/Memory/IJxlMemoryManager.cs b/src/ImageSharp/Formats/Jxl/Memory/IJxlMemoryManager.cs deleted file mode 100644 index c808388d28..0000000000 --- a/src/ImageSharp/Formats/Jxl/Memory/IJxlMemoryManager.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; - -namespace SixLabors.ImageSharp.Formats.Jxl.Memory; - -internal interface IJxlMemoryManager -{ - IMemoryOwner Allocate(int size); - IMemoryOwner Allocate(int size); -} diff --git a/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImage3B.cs b/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImage3B.cs new file mode 100644 index 0000000000..63a5c59c9f --- /dev/null +++ b/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImage3B.cs @@ -0,0 +1,14 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Jxl.Memory.ImageTypes; + +/// +/// Represents a three-plane, 2D raster image of type . +/// +internal sealed class JxlImage3B : JxlImage3 +{ + public JxlImage3B() + { + } +} diff --git a/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImage3F.cs b/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImage3F.cs new file mode 100644 index 0000000000..b456dfd9a7 --- /dev/null +++ b/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImage3F.cs @@ -0,0 +1,14 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Jxl.Memory.ImageTypes; + +/// +/// Represents a three-plane, 2D raster image of type . +/// +internal sealed class JxlImage3F : JxlImage3 +{ + public JxlImage3F() + { + } +} diff --git a/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImage3I.cs b/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImage3I.cs new file mode 100644 index 0000000000..0d9f6c8202 --- /dev/null +++ b/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImage3I.cs @@ -0,0 +1,14 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Jxl.Memory.ImageTypes; + +/// +/// Represents a three-plane, 2D raster image of type . +/// +internal sealed class JxlImage3I : JxlImage3 +{ + public JxlImage3I() + { + } +} diff --git a/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImage3S.cs b/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImage3S.cs new file mode 100644 index 0000000000..00615ff846 --- /dev/null +++ b/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImage3S.cs @@ -0,0 +1,14 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Jxl.Memory.ImageTypes; + +/// +/// Represents a three-plane, 2D raster image of type . +/// +internal sealed class JxlImage3S : JxlImage3 +{ + public JxlImage3S() + { + } +} diff --git a/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImage3U.cs b/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImage3U.cs new file mode 100644 index 0000000000..1921e86d33 --- /dev/null +++ b/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImage3U.cs @@ -0,0 +1,14 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Jxl.Memory.ImageTypes; + +/// +/// Represents a three-plane, 2D raster image of type . +/// +internal sealed class JxlImage3U : JxlImage3 +{ + public JxlImage3U() + { + } +} diff --git a/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImageB.cs b/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImageB.cs new file mode 100644 index 0000000000..0faba189ad --- /dev/null +++ b/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImageB.cs @@ -0,0 +1,34 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Diagnostics; + +namespace SixLabors.ImageSharp.Formats.Jxl.Memory.ImageTypes; + +/// +/// Represents a single-plane, 2D raster image of type . +/// +internal sealed class JxlImageB : JxlPlane +{ + public JxlImageB() + { + } + + public JxlImageB(int width, int height) + : base(width, height) + { + } + + public JxlImageB(Configuration configuration, int xSize, int ySize, int prePadding = 0) + : base(xSize, ySize) + => this.Allocate(configuration, prePadding); + + public Memory GetRowBytesMemory(int y) + { + Debug.Assert(y < this.YSize, "Attempted to access out-of-bounds Y coordinate"); + + Memory row = this.Bytes[(y * this.BytesPerRow)..]; + + return row; + } +} diff --git a/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImageF.cs b/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImageF.cs new file mode 100644 index 0000000000..6810e87df9 --- /dev/null +++ b/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImageF.cs @@ -0,0 +1,23 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Jxl.Memory.ImageTypes; + +/// +/// Represents a single-plane, 2D raster image of type . +/// +internal sealed class JxlImageF : JxlPlane +{ + public JxlImageF() + { + } + + public JxlImageF(int width, int height) + : base(width, height) + { + } + + public JxlImageF(Configuration configuration, int xSize, int ySize, int prePadding = 0) + : base(xSize, ySize) + => this.Allocate(configuration, prePadding); +} diff --git a/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImageI.cs b/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImageI.cs new file mode 100644 index 0000000000..0261ffd63c --- /dev/null +++ b/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImageI.cs @@ -0,0 +1,23 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Jxl.Memory.ImageTypes; + +/// +/// Represents a single-plane, 2D raster image of type . +/// +internal sealed class JxlImageI : JxlPlane +{ + public JxlImageI() + { + } + + public JxlImageI(int width, int height) + : base(width, height) + { + } + + public JxlImageI(Configuration configuration, int xSize, int ySize, int prePadding = 0) + : base(xSize, ySize) + => this.Allocate(configuration, prePadding); +} diff --git a/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImageS.cs b/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImageS.cs new file mode 100644 index 0000000000..b53716f12f --- /dev/null +++ b/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImageS.cs @@ -0,0 +1,23 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Jxl.Memory.ImageTypes; + +/// +/// Represents a single-plane, 2D raster image of type . +/// +internal sealed class JxlImageS : JxlPlane +{ + public JxlImageS() + { + } + + public JxlImageS(int width, int height) + : base(width, height) + { + } + + public JxlImageS(Configuration configuration, int xSize, int ySize, int prePadding = 0) + : base(xSize, ySize) + => this.Allocate(configuration, prePadding); +} diff --git a/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImageSB.cs b/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImageSB.cs new file mode 100644 index 0000000000..355c50785c --- /dev/null +++ b/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImageSB.cs @@ -0,0 +1,23 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Jxl.Memory.ImageTypes; + +/// +/// Represents a single-plane, 2D raster image of type . +/// +internal sealed class JxlImageSB : JxlPlane +{ + public JxlImageSB() + { + } + + public JxlImageSB(int width, int height) + : base(width, height) + { + } + + public JxlImageSB(Configuration configuration, int xSize, int ySize, int prePadding = 0) + : base(xSize, ySize) + => this.Allocate(configuration, prePadding); +} diff --git a/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImageU.cs b/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImageU.cs new file mode 100644 index 0000000000..d41669e4d1 --- /dev/null +++ b/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImageU.cs @@ -0,0 +1,23 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Jxl.Memory.ImageTypes; + +/// +/// Represents a single-plane, 2D raster image of type . +/// +internal sealed class JxlImageU : JxlPlane +{ + public JxlImageU() + { + } + + public JxlImageU(int width, int height) + : base(width, height) + { + } + + public JxlImageU(Configuration configuration, int xSize, int ySize, int prePadding = 0) + : base(xSize, ySize) + => this.Allocate(configuration, prePadding); +} diff --git a/src/ImageSharp/Formats/Jxl/Memory/JxlImage3{T}.cs b/src/ImageSharp/Formats/Jxl/Memory/JxlImage3{T}.cs new file mode 100644 index 0000000000..f46f6767b8 --- /dev/null +++ b/src/ImageSharp/Formats/Jxl/Memory/JxlImage3{T}.cs @@ -0,0 +1,85 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.Formats.Jxl.Memory; + +// NOTE: Do not seal this class. +internal class JxlImage3 + where T : unmanaged +{ + private const int PlaneCount = 3; + + private JxlPlane[] planes = new JxlPlane[3]; + + public JxlImage3() + { + } + + public JxlImage3(JxlImage3 other) + { + for (int i = 0; i < PlaneCount; i++) + { + this.planes[i] = other.planes[i]; + } + } + + public int XSize => this.planes[0].XSize; + + public int YSize => this.planes[0].YSize; + + public int BytesPerRow => this.planes[0].BytesPerRow; + + public int PixelsPerRow => this.planes[0].PixelsPerRow; + + public Span PlaneRow(int plane, int row) + { + this.PlaneRowBoundsCheck(plane, row); + + int rowOffset = row * this.planes[0].BytesPerRow; + Span rowSpan = MemoryMarshal.Cast(this.planes[plane].BytesSpan[rowOffset..]); + + return rowSpan; + } + + public JxlPlane Plane(int index) => this.planes[index]; + + public void Swap(JxlImage3 other) + { + for (int i = 0; i < PlaneCount; i++) + { + other.planes[i].Swap(this.planes[i]); + } + } + + public static JxlImage3 Create(Configuration configuration, int xSize, int ySize) + { + JxlPlane plane0 = JxlPlane.Create(configuration, xSize, ySize); + JxlPlane plane1 = JxlPlane.Create(configuration, xSize, ySize); + JxlPlane plane2 = JxlPlane.Create(configuration, xSize, ySize); + + return new JxlImage3() + { + planes = [plane0, plane1, plane2] + }; + } + + public bool ShrinkTo(int x, int y) + { + for (int i = 0; i < PlaneCount; i++) + { + if (!this.planes[i].ShrinkTo(x, y)) + { + return false; + } + } + + return true; + } + + [Conditional("DEBUG")] + private void PlaneRowBoundsCheck(int c, int y) => + Debug.Assert(c < PlaneCount && y < this.YSize, "The bounds check has failed"); +} diff --git a/src/ImageSharp/Formats/Jxl/Memory/JxlMemoryManager.cs b/src/ImageSharp/Formats/Jxl/Memory/JxlMemoryManager.cs deleted file mode 100644 index 778ef59106..0000000000 --- a/src/ImageSharp/Formats/Jxl/Memory/JxlMemoryManager.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; - -namespace SixLabors.ImageSharp.Formats.Jxl.Memory; - -/// -/// Allows allocation and deallocation of managed memory. -/// -internal sealed class JxlMemoryManager : IJxlMemoryManager -{ - public static readonly JxlMemoryManager Instance = new(); - - public IMemoryOwner Allocate(int size) => MemoryPool.Shared.Rent(size); - - public IMemoryOwner Allocate(int size) => this.Allocate(size); -} diff --git a/src/ImageSharp/Formats/Jxl/Memory/JxlPlaneBase.cs b/src/ImageSharp/Formats/Jxl/Memory/JxlPlaneBase.cs new file mode 100644 index 0000000000..9948ae790f --- /dev/null +++ b/src/ImageSharp/Formats/Jxl/Memory/JxlPlaneBase.cs @@ -0,0 +1,115 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Buffers; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.Formats.Jxl.Memory; + +// NOTE: Do not seal this type. +internal class JxlPlaneBase : IDisposable +{ + private IMemoryOwner? bytes; + + public JxlPlaneBase(int xSize, int ySize, int sizeOfT) + { + this.XSize = xSize; + this.YSize = ySize; + this.OriginalXSize = xSize; + this.OriginalYSize = ySize; + this.BytesPerRow = 0; + this.Size = sizeOfT; + } + + public JxlPlaneBase() + : this(0, 0, 0) + { + } + + public int BytesPerRow { get; private set; } + + public int XSize { get; private set; } + + public int YSize { get; private set; } + + public Memory Bytes => +#if DEBUG + this.bytes?.Memory ?? throw new InvalidOperationException("Bytes are missing"); +#else + return this.bytes!.Memory; +#endif + + public Span BytesSpan => this.Bytes.Span; + + protected int Size { get; set; } + + protected int OriginalXSize { get; set; } + + protected int OriginalYSize { get; set; } + + public bool Allocate(Configuration configuration, int prePadding) + { + if (this.bytes != null || this.BytesPerRow != 0) + { + return false; + } + + if (this.XSize == 0 || this.YSize == 0) + { + return true; + } + + int totalBytes = unchecked(this.YSize * this.BytesPerRow); + + this.bytes = configuration.MemoryAllocator.Allocate(totalBytes + (prePadding * this.Size)); + + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool ShrinkTo(int x, int y) + { + if (x <= this.OriginalXSize || y <= this.OriginalYSize) + { + return false; + } + + Debug.Assert(x <= this.OriginalXSize, "ShrinkTo cannot expand memory"); + Debug.Assert(y <= this.OriginalYSize, "ShrinkTo cannot expand memory"); + + this.XSize = x; + this.YSize = y; + + return true; + } + + protected Span GetRowBase(int y) + where T : unmanaged + { + Debug.Assert(y < this.YSize, "Attempted to access out-of-bounds Y coordinate"); + + Span row = this.Bytes.Span[(y * this.BytesPerRow)..]; + + return MemoryMarshal.Cast(row); + } + + protected void SetBytes(IMemoryOwner bytes) => this.bytes = bytes; + + public void Swap(JxlPlaneBase other) + { + (this.XSize, other.XSize) = (other.XSize, this.XSize); + (this.YSize, other.YSize) = (other.YSize, this.YSize); + (this.OriginalXSize, other.OriginalXSize) = (other.OriginalXSize, this.OriginalXSize); + (this.OriginalYSize, other.OriginalYSize) = (other.OriginalYSize, this.OriginalYSize); + (this.BytesPerRow, other.BytesPerRow) = (other.BytesPerRow, this.BytesPerRow); + (this.bytes, other.bytes) = (other.bytes, this.bytes); + } + + public void Dispose() + { + this.bytes?.Dispose(); + GC.SuppressFinalize(this); + } +} diff --git a/src/ImageSharp/Formats/Jxl/Memory/JxlPlane{T}.cs b/src/ImageSharp/Formats/Jxl/Memory/JxlPlane{T}.cs new file mode 100644 index 0000000000..6bb09c6002 --- /dev/null +++ b/src/ImageSharp/Formats/Jxl/Memory/JxlPlane{T}.cs @@ -0,0 +1,36 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Jxl.Memory; + +// NOTE: Do not seal this class. +internal class JxlPlane : JxlPlaneBase + where T : unmanaged +{ + public JxlPlane() + { + } + + public unsafe JxlPlane(int width, int height) + : base(width, height, sizeof(T)) + { + } + + public unsafe int PixelsPerRow => this.BytesPerRow / sizeof(T); + + public static JxlPlane Create(Configuration configuration, int xSize, int ySize, int prePadding = 0) + { + JxlPlane plane = new(xSize, ySize); + + bool allocated = plane.Allocate(configuration, prePadding); + + if (!allocated) + { + throw new InvalidOperationException("Failed to allocate a JPEG XL plane"); + } + + return plane; + } + + public Span GetRow(int y) => this.GetRowBase(y); +}