// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp { /// /// Represents a pixel-specific image frame containing all pixel data and . /// In case of animated formats like gif, it contains the single frame in a animation. /// In all other cases it is the only frame of the image. /// /// The pixel format. public sealed class ImageFrame : ImageFrame, IPixelSource where TPixel : struct, IPixel { private bool isDisposed; /// /// Initializes a new instance of the class. /// /// The configuration which allows altering default behaviour or extending the library. /// The width of the image in pixels. /// The height of the image in pixels. internal ImageFrame(Configuration configuration, int width, int height) : this(configuration, width, height, new ImageFrameMetadata()) { } /// /// Initializes a new instance of the class. /// /// The configuration which allows altering default behaviour or extending the library. /// The of the frame. /// The metadata. internal ImageFrame(Configuration configuration, Size size, ImageFrameMetadata metadata) : this(configuration, size.Width, size.Height, metadata) { } /// /// Initializes a new instance of the class. /// /// The configuration which allows altering default behaviour or extending the library. /// The width of the image in pixels. /// The height of the image in pixels. /// The metadata. internal ImageFrame(Configuration configuration, int width, int height, ImageFrameMetadata metadata) : base(configuration, width, height, metadata) { Guard.MustBeGreaterThan(width, 0, nameof(width)); Guard.MustBeGreaterThan(height, 0, nameof(height)); this.PixelBuffer = this.GetConfiguration().MemoryAllocator.Allocate2D(width, height, AllocationOptions.Clean); } /// /// Initializes a new instance of the class. /// /// The configuration which allows altering default behaviour or extending the library. /// The width of the image in pixels. /// The height of the image in pixels. /// The color to clear the image with. internal ImageFrame(Configuration configuration, int width, int height, TPixel backgroundColor) : this(configuration, width, height, backgroundColor, new ImageFrameMetadata()) { } /// /// Initializes a new instance of the class. /// /// The configuration which allows altering default behaviour or extending the library. /// The width of the image in pixels. /// The height of the image in pixels. /// The color to clear the image with. /// The metadata. internal ImageFrame(Configuration configuration, int width, int height, TPixel backgroundColor, ImageFrameMetadata metadata) : base(configuration, width, height, metadata) { Guard.MustBeGreaterThan(width, 0, nameof(width)); Guard.MustBeGreaterThan(height, 0, nameof(height)); this.PixelBuffer = this.GetConfiguration().MemoryAllocator.Allocate2D(width, height); this.Clear(backgroundColor); } /// /// Initializes a new instance of the class wrapping an existing buffer. /// /// The configuration providing initialization code which allows extending the library. /// The width of the image in pixels. /// The height of the image in pixels. /// The memory source. internal ImageFrame(Configuration configuration, int width, int height, MemorySource memorySource) : this(configuration, width, height, memorySource, new ImageFrameMetadata()) { } /// /// Initializes a new instance of the class wrapping an existing buffer. /// /// The configuration providing initialization code which allows extending the library. /// The width of the image in pixels. /// The height of the image in pixels. /// The memory source. /// The metadata. internal ImageFrame(Configuration configuration, int width, int height, MemorySource memorySource, ImageFrameMetadata metadata) : base(configuration, width, height, metadata) { Guard.MustBeGreaterThan(width, 0, nameof(width)); Guard.MustBeGreaterThan(height, 0, nameof(height)); this.PixelBuffer = new Buffer2D(memorySource, width, height); } /// /// Initializes a new instance of the class. /// /// The configuration which allows altering default behaviour or extending the library. /// The source. internal ImageFrame(Configuration configuration, ImageFrame source) : base(configuration, source.Width, source.Height, source.Metadata.DeepClone()) { Guard.NotNull(configuration, nameof(configuration)); Guard.NotNull(source, nameof(source)); this.PixelBuffer = this.GetConfiguration().MemoryAllocator.Allocate2D(source.PixelBuffer.Width, source.PixelBuffer.Height); source.PixelBuffer.GetSpan().CopyTo(this.PixelBuffer.GetSpan()); } /// /// Gets the image pixels. Not private as Buffer2D requires an array in its constructor. /// internal Buffer2D PixelBuffer { get; private set; } /// Buffer2D IPixelSource.PixelBuffer => this.PixelBuffer; /// /// Gets or sets the pixel at the specified position. /// /// The x-coordinate of the pixel. Must be greater than or equal to zero and less than the width of the image. /// The y-coordinate of the pixel. Must be greater than or equal to zero and less than the height of the image. /// The at the specified position. public TPixel this[int x, int y] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => this.PixelBuffer[x, y]; [MethodImpl(MethodImplOptions.AggressiveInlining)] set => this.PixelBuffer[x, y] = value; } /// /// Gets a reference to the pixel at the specified position. /// /// The x-coordinate of the pixel. Must be greater than or equal to zero and less than the width of the image. /// The y-coordinate of the pixel. Must be greater than or equal to zero and less than the height of the image. /// The at the specified position. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal ref TPixel GetPixelReference(int x, int y) => ref this.PixelBuffer[x, y]; /// /// Copies the pixels to a of the same size. /// /// The target pixel buffer accessor. internal void CopyTo(Buffer2D target) { if (this.Size() != target.Size()) { throw new ArgumentException("ImageFrame.CopyTo(): target must be of the same size!", nameof(target)); } this.GetPixelSpan().CopyTo(target.GetSpan()); } /// /// Switches the buffers used by the image and the pixelSource meaning that the Image will "own" the buffer from the pixelSource and the pixelSource will now own the Images buffer. /// /// The pixel source. internal void SwapOrCopyPixelsBufferFrom(ImageFrame pixelSource) { Guard.NotNull(pixelSource, nameof(pixelSource)); Buffer2D.SwapOrCopyContent(this.PixelBuffer, pixelSource.PixelBuffer); this.UpdateSize(this.PixelBuffer.Size()); } /// protected override void Dispose(bool disposing) { if (this.isDisposed) { return; } if (disposing) { this.PixelBuffer?.Dispose(); this.PixelBuffer = null; } this.isDisposed = true; } internal override void CopyPixelsTo(Span destination) { if (typeof(TPixel) == typeof(TDestinationPixel)) { Span dest1 = MemoryMarshal.Cast(destination); this.PixelBuffer.GetSpan().CopyTo(dest1); } PixelOperations.Instance.To(this.GetConfiguration(), this.PixelBuffer.GetSpan(), destination); } /// public override string ToString() => $"ImageFrame<{typeof(TPixel).Name}>({this.Width}x{this.Height})"; /// /// Clones the current instance. /// /// The internal ImageFrame Clone() => this.Clone(this.GetConfiguration()); /// /// Clones the current instance. /// /// The configuration providing initialization code which allows extending the library. /// The internal ImageFrame Clone(Configuration configuration) => new ImageFrame(configuration, this); /// /// Returns a copy of the image frame in the given pixel format. /// /// The pixel format. /// The internal ImageFrame CloneAs() where TPixel2 : struct, IPixel => this.CloneAs(this.GetConfiguration()); /// /// Returns a copy of the image frame in the given pixel format. /// /// The pixel format. /// The configuration providing initialization code which allows extending the library. /// The internal ImageFrame CloneAs(Configuration configuration) where TPixel2 : struct, IPixel { if (typeof(TPixel2) == typeof(TPixel)) { return this.Clone(configuration) as ImageFrame; } var target = new ImageFrame(configuration, this.Width, this.Height, this.Metadata.DeepClone()); ParallelRowIterator.IterateRows2( this.Bounds(), configuration, new RowAction(this, target, configuration)); return target; } /// /// Clears the bitmap. /// /// The value to initialize the bitmap with. internal void Clear(TPixel value) { Span span = this.GetPixelSpan(); if (value.Equals(default)) { span.Clear(); } else { span.Fill(value); } } /// /// A implementing the clone logic for . /// private readonly struct RowAction : IRowAction where TPixel2 : struct, IPixel { private readonly ImageFrame source; private readonly ImageFrame target; private readonly Configuration configuration; [MethodImpl(InliningOptions.ShortMethod)] public RowAction( ImageFrame source, ImageFrame target, Configuration configuration) { this.source = source; this.target = target; this.configuration = configuration; } /// [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y) { Span sourceRow = this.source.GetPixelRowSpan(y); Span targetRow = this.target.GetPixelRowSpan(y); PixelOperations.Instance.To(this.configuration, sourceRow, targetRow); } } } }