mirror of https://github.com/SixLabors/ImageSharp
34 changed files with 5679 additions and 0 deletions
@ -0,0 +1,16 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors |
|||
{ |
|||
/// <summary>
|
|||
/// Common constants used throughout the project.
|
|||
/// </summary>
|
|||
internal static class Constants |
|||
{ |
|||
/// <summary>
|
|||
/// The epsilon for comparing floating point numbers.
|
|||
/// </summary>
|
|||
public static readonly float Epsilon = 0.001f; |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors |
|||
{ |
|||
/// <summary>
|
|||
/// Utility class for common geometric functions.
|
|||
/// </summary>
|
|||
public static class GeometryUtilities |
|||
{ |
|||
/// <summary>
|
|||
/// Converts a degree (360-periodic) angle to a radian (2*Pi-periodic) angle.
|
|||
/// </summary>
|
|||
/// <param name="degree">The angle in degrees.</param>
|
|||
/// <returns>
|
|||
/// The <see cref="float"/> representing the degree as radians.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static float DegreeToRadian(float degree) => degree * (MathF.PI / 180F); |
|||
|
|||
/// <summary>
|
|||
/// Converts a radian (2*Pi-periodic) angle to a degree (360-periodic) angle.
|
|||
/// </summary>
|
|||
/// <param name="radian">The angle in radians.</param>
|
|||
/// <returns>
|
|||
/// The <see cref="float"/> representing the degree as radians.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static float RadianToDegree(float radian) => radian / (MathF.PI / 180F); |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.Memory |
|||
{ |
|||
/// <summary>
|
|||
/// Options for allocating buffers.
|
|||
/// </summary>
|
|||
public enum AllocationOptions |
|||
{ |
|||
/// <summary>
|
|||
/// Indicates that the buffer should just be allocated.
|
|||
/// </summary>
|
|||
None, |
|||
|
|||
/// <summary>
|
|||
/// Indicates that the allocated buffer should be cleaned following allocation.
|
|||
/// </summary>
|
|||
Clean |
|||
} |
|||
} |
|||
@ -0,0 +1,85 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
using System.Runtime.InteropServices; |
|||
using SixLabors.Memory.Internals; |
|||
|
|||
namespace SixLabors.Memory |
|||
{ |
|||
/// <summary>
|
|||
/// Contains <see cref="Buffer{T}"/> and <see cref="ManagedByteBuffer"/>.
|
|||
/// </summary>
|
|||
public partial class ArrayPoolMemoryAllocator |
|||
{ |
|||
/// <summary>
|
|||
/// The buffer implementation of <see cref="ArrayPoolMemoryAllocator"/>.
|
|||
/// </summary>
|
|||
private class Buffer<T> : ManagedBufferBase<T> |
|||
where T : struct |
|||
{ |
|||
/// <summary>
|
|||
/// The length of the buffer.
|
|||
/// </summary>
|
|||
private readonly int length; |
|||
|
|||
/// <summary>
|
|||
/// A weak reference to the source pool.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// By using a weak reference here, we are making sure that array pools and their retained arrays are always GC-ed
|
|||
/// after a call to <see cref="ArrayPoolMemoryAllocator.ReleaseRetainedResources"/>, regardless of having buffer instances still being in use.
|
|||
/// </remarks>
|
|||
private WeakReference<ArrayPool<byte>> sourcePoolReference; |
|||
|
|||
public Buffer(byte[] data, int length, ArrayPool<byte> sourcePool) |
|||
{ |
|||
this.Data = data; |
|||
this.length = length; |
|||
this.sourcePoolReference = new WeakReference<ArrayPool<byte>>(sourcePool); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the buffer as a byte array.
|
|||
/// </summary>
|
|||
protected byte[] Data { get; private set; } |
|||
|
|||
/// <inheritdoc />
|
|||
public override Span<T> GetSpan() => MemoryMarshal.Cast<byte, T>(this.Data.AsSpan()).Slice(0, this.length); |
|||
|
|||
/// <inheritdoc />
|
|||
protected override void Dispose(bool disposing) |
|||
{ |
|||
if (!disposing || this.Data is null || this.sourcePoolReference is null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (this.sourcePoolReference.TryGetTarget(out ArrayPool<byte> pool)) |
|||
{ |
|||
pool.Return(this.Data); |
|||
} |
|||
|
|||
this.sourcePoolReference = null; |
|||
this.Data = null; |
|||
} |
|||
|
|||
protected override object GetPinnableObject() => this.Data; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// The <see cref="IManagedByteBuffer"/> implementation of <see cref="ArrayPoolMemoryAllocator"/>.
|
|||
/// </summary>
|
|||
private sealed class ManagedByteBuffer : Buffer<byte>, IManagedByteBuffer |
|||
{ |
|||
public ManagedByteBuffer(byte[] data, int length, ArrayPool<byte> sourcePool) |
|||
: base(data, length, sourcePool) |
|||
{ |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public byte[] Array => this.Data; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,72 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.Memory |
|||
{ |
|||
/// <summary>
|
|||
/// Contains common factory methods and configuration constants.
|
|||
/// </summary>
|
|||
public partial class ArrayPoolMemoryAllocator |
|||
{ |
|||
/// <summary>
|
|||
/// The default value for: maximum size of pooled arrays in bytes.
|
|||
/// Currently set to 24MB, which is equivalent to 8 megapixels of raw RGBA32 data.
|
|||
/// </summary>
|
|||
internal const int DefaultMaxPooledBufferSizeInBytes = 24 * 1024 * 1024; |
|||
|
|||
/// <summary>
|
|||
/// The value for: The threshold to pool arrays in <see cref="largeArrayPool"/> which has less buckets for memory safety.
|
|||
/// </summary>
|
|||
private const int DefaultBufferSelectorThresholdInBytes = 8 * 1024 * 1024; |
|||
|
|||
/// <summary>
|
|||
/// The default bucket count for <see cref="largeArrayPool"/>.
|
|||
/// </summary>
|
|||
private const int DefaultLargePoolBucketCount = 6; |
|||
|
|||
/// <summary>
|
|||
/// The default bucket count for <see cref="normalArrayPool"/>.
|
|||
/// </summary>
|
|||
private const int DefaultNormalPoolBucketCount = 16; |
|||
|
|||
/// <summary>
|
|||
/// This is the default. Should be good for most use cases.
|
|||
/// </summary>
|
|||
/// <returns>The memory manager.</returns>
|
|||
public static ArrayPoolMemoryAllocator CreateDefault() |
|||
{ |
|||
return new ArrayPoolMemoryAllocator( |
|||
DefaultMaxPooledBufferSizeInBytes, |
|||
DefaultBufferSelectorThresholdInBytes, |
|||
DefaultLargePoolBucketCount, |
|||
DefaultNormalPoolBucketCount); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// For environments with very limited memory capabilities, only small buffers like image rows are pooled.
|
|||
/// </summary>
|
|||
/// <returns>The memory manager.</returns>
|
|||
public static ArrayPoolMemoryAllocator CreateWithMinimalPooling() |
|||
{ |
|||
return new ArrayPoolMemoryAllocator(64 * 1024, 32 * 1024, 8, 24); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// For environments with limited memory capabilities, only small array requests are pooled, which can result in reduced throughput.
|
|||
/// </summary>
|
|||
/// <returns>The memory manager.</returns>
|
|||
public static ArrayPoolMemoryAllocator CreateWithModeratePooling() |
|||
{ |
|||
return new ArrayPoolMemoryAllocator(1024 * 1024, 32 * 1024, 16, 24); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// For environments where memory capabilities are not an issue, the maximum amount of array requests are pooled which results in optimal throughput.
|
|||
/// </summary>
|
|||
/// <returns>The memory manager.</returns>
|
|||
public static ArrayPoolMemoryAllocator CreateWithAggressivePooling() |
|||
{ |
|||
return new ArrayPoolMemoryAllocator(128 * 1024 * 1024, 32 * 1024 * 1024, 16, 32); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,150 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.Memory |
|||
{ |
|||
/// <summary>
|
|||
/// Implements <see cref="MemoryAllocator"/> by allocating memory from <see cref="ArrayPool{T}"/>.
|
|||
/// </summary>
|
|||
public sealed partial class ArrayPoolMemoryAllocator : MemoryAllocator |
|||
{ |
|||
private readonly int maxArraysPerBucketNormalPool; |
|||
|
|||
private readonly int maxArraysPerBucketLargePool; |
|||
|
|||
/// <summary>
|
|||
/// The <see cref="ArrayPool{T}"/> for small-to-medium buffers which is not kept clean.
|
|||
/// </summary>
|
|||
private ArrayPool<byte> normalArrayPool; |
|||
|
|||
/// <summary>
|
|||
/// The <see cref="ArrayPool{T}"/> for huge buffers, which is not kept clean.
|
|||
/// </summary>
|
|||
private ArrayPool<byte> largeArrayPool; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ArrayPoolMemoryAllocator"/> class.
|
|||
/// </summary>
|
|||
public ArrayPoolMemoryAllocator() |
|||
: this(DefaultMaxPooledBufferSizeInBytes, DefaultBufferSelectorThresholdInBytes) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ArrayPoolMemoryAllocator"/> class.
|
|||
/// </summary>
|
|||
/// <param name="maxPoolSizeInBytes">The maximum size of pooled arrays. Arrays over the thershold are gonna be always allocated.</param>
|
|||
public ArrayPoolMemoryAllocator(int maxPoolSizeInBytes) |
|||
: this(maxPoolSizeInBytes, GetLargeBufferThresholdInBytes(maxPoolSizeInBytes)) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ArrayPoolMemoryAllocator"/> class.
|
|||
/// </summary>
|
|||
/// <param name="maxPoolSizeInBytes">The maximum size of pooled arrays. Arrays over the thershold are gonna be always allocated.</param>
|
|||
/// <param name="poolSelectorThresholdInBytes">Arrays over this threshold will be pooled in <see cref="largeArrayPool"/> which has less buckets for memory safety.</param>
|
|||
public ArrayPoolMemoryAllocator(int maxPoolSizeInBytes, int poolSelectorThresholdInBytes) |
|||
: this(maxPoolSizeInBytes, poolSelectorThresholdInBytes, DefaultLargePoolBucketCount, DefaultNormalPoolBucketCount) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ArrayPoolMemoryAllocator"/> class.
|
|||
/// </summary>
|
|||
/// <param name="maxPoolSizeInBytes">The maximum size of pooled arrays. Arrays over the thershold are gonna be always allocated.</param>
|
|||
/// <param name="poolSelectorThresholdInBytes">The threshold to pool arrays in <see cref="largeArrayPool"/> which has less buckets for memory safety.</param>
|
|||
/// <param name="maxArraysPerBucketLargePool">Max arrays per bucket for the large array pool.</param>
|
|||
/// <param name="maxArraysPerBucketNormalPool">Max arrays per bucket for the normal array pool.</param>
|
|||
public ArrayPoolMemoryAllocator(int maxPoolSizeInBytes, int poolSelectorThresholdInBytes, int maxArraysPerBucketLargePool, int maxArraysPerBucketNormalPool) |
|||
{ |
|||
Guard.MustBeGreaterThan(maxPoolSizeInBytes, 0, nameof(maxPoolSizeInBytes)); |
|||
Guard.MustBeLessThanOrEqualTo(poolSelectorThresholdInBytes, maxPoolSizeInBytes, nameof(poolSelectorThresholdInBytes)); |
|||
|
|||
this.MaxPoolSizeInBytes = maxPoolSizeInBytes; |
|||
this.PoolSelectorThresholdInBytes = poolSelectorThresholdInBytes; |
|||
this.maxArraysPerBucketLargePool = maxArraysPerBucketLargePool; |
|||
this.maxArraysPerBucketNormalPool = maxArraysPerBucketNormalPool; |
|||
|
|||
this.InitArrayPools(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the maximum size of pooled arrays in bytes.
|
|||
/// </summary>
|
|||
public int MaxPoolSizeInBytes { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the threshold to pool arrays in <see cref="largeArrayPool"/> which has less buckets for memory safety.
|
|||
/// </summary>
|
|||
public int PoolSelectorThresholdInBytes { get; } |
|||
|
|||
/// <inheritdoc />
|
|||
public override void ReleaseRetainedResources() |
|||
{ |
|||
this.InitArrayPools(); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public override IMemoryOwner<T> Allocate<T>(int length, AllocationOptions options = AllocationOptions.None) |
|||
{ |
|||
Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length)); |
|||
int itemSizeBytes = Unsafe.SizeOf<T>(); |
|||
int bufferSizeInBytes = length * itemSizeBytes; |
|||
if (bufferSizeInBytes < 0) |
|||
{ |
|||
throw new ArgumentOutOfRangeException( |
|||
nameof(length), |
|||
$"{nameof(ArrayPoolMemoryAllocator)} can not allocate {length} elements of {typeof(T).Name}."); |
|||
} |
|||
|
|||
ArrayPool<byte> pool = this.GetArrayPool(bufferSizeInBytes); |
|||
byte[] byteArray = pool.Rent(bufferSizeInBytes); |
|||
|
|||
var buffer = new Buffer<T>(byteArray, length, pool); |
|||
if (options == AllocationOptions.Clean) |
|||
{ |
|||
buffer.GetSpan().Clear(); |
|||
} |
|||
|
|||
return buffer; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public override IManagedByteBuffer AllocateManagedByteBuffer(int length, AllocationOptions options = AllocationOptions.None) |
|||
{ |
|||
Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length)); |
|||
|
|||
ArrayPool<byte> pool = this.GetArrayPool(length); |
|||
byte[] byteArray = pool.Rent(length); |
|||
|
|||
var buffer = new ManagedByteBuffer(byteArray, length, pool); |
|||
if (options == AllocationOptions.Clean) |
|||
{ |
|||
buffer.GetSpan().Clear(); |
|||
} |
|||
|
|||
return buffer; |
|||
} |
|||
|
|||
private static int GetLargeBufferThresholdInBytes(int maxPoolSizeInBytes) |
|||
{ |
|||
return maxPoolSizeInBytes / 4; |
|||
} |
|||
|
|||
private ArrayPool<byte> GetArrayPool(int bufferSizeInBytes) |
|||
{ |
|||
return bufferSizeInBytes <= this.PoolSelectorThresholdInBytes ? this.normalArrayPool : this.largeArrayPool; |
|||
} |
|||
|
|||
private void InitArrayPools() |
|||
{ |
|||
this.largeArrayPool = ArrayPool<byte>.Create(this.MaxPoolSizeInBytes, this.maxArraysPerBucketLargePool); |
|||
this.normalArrayPool = ArrayPool<byte>.Create(this.PoolSelectorThresholdInBytes, this.maxArraysPerBucketNormalPool); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Buffers; |
|||
|
|||
namespace SixLabors.Memory |
|||
{ |
|||
/// <summary>
|
|||
/// Represents a byte buffer backed by a managed array. Useful for interop with classic .NET API-s.
|
|||
/// </summary>
|
|||
public interface IManagedByteBuffer : IMemoryOwner<byte> |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the managed array backing this buffer instance.
|
|||
/// </summary>
|
|||
byte[] Array { get; } |
|||
} |
|||
} |
|||
@ -0,0 +1,61 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.Memory.Internals |
|||
{ |
|||
/// <summary>
|
|||
/// Wraps an array as an <see cref="IManagedByteBuffer"/> instance.
|
|||
/// </summary>
|
|||
/// <inheritdoc />
|
|||
internal class BasicArrayBuffer<T> : ManagedBufferBase<T> |
|||
where T : struct |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="BasicArrayBuffer{T}"/> class.
|
|||
/// </summary>
|
|||
/// <param name="array">The array.</param>
|
|||
/// <param name="length">The length of the buffer.</param>
|
|||
public BasicArrayBuffer(T[] array, int length) |
|||
{ |
|||
DebugGuard.MustBeLessThanOrEqualTo(length, array.Length, nameof(length)); |
|||
this.Array = array; |
|||
this.Length = length; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="BasicArrayBuffer{T}"/> class.
|
|||
/// </summary>
|
|||
/// <param name="array">The array.</param>
|
|||
public BasicArrayBuffer(T[] array) |
|||
: this(array, array.Length) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the array.
|
|||
/// </summary>
|
|||
public T[] Array { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the length.
|
|||
/// </summary>
|
|||
public int Length { get; } |
|||
|
|||
/// <inheritdoc />
|
|||
public override Span<T> GetSpan() => this.Array.AsSpan(0, this.Length); |
|||
|
|||
/// <inheritdoc />
|
|||
protected override void Dispose(bool disposing) |
|||
{ |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
protected override object GetPinnableObject() |
|||
{ |
|||
return this.Array; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.Memory.Internals |
|||
{ |
|||
/// <summary>
|
|||
/// Provides an <see cref="IManagedByteBuffer"/> based on <see cref="BasicArrayBuffer{T}"/>.
|
|||
/// </summary>
|
|||
internal sealed class BasicByteBuffer : BasicArrayBuffer<byte>, IManagedByteBuffer |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="BasicByteBuffer"/> class.
|
|||
/// </summary>
|
|||
/// <param name="array">The byte array.</param>
|
|||
internal BasicByteBuffer(byte[] array) |
|||
: base(array) |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,45 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Buffers; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace SixLabors.Memory.Internals |
|||
{ |
|||
/// <summary>
|
|||
/// Provides a base class for <see cref="IMemoryOwner{T}"/> implementations by implementing pinning logic for <see cref="MemoryManager{T}"/> adaption.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The element type.</typeparam>
|
|||
internal abstract class ManagedBufferBase<T> : MemoryManager<T> |
|||
where T : struct |
|||
{ |
|||
private GCHandle pinHandle; |
|||
|
|||
/// <inheritdoc />
|
|||
public override unsafe MemoryHandle Pin(int elementIndex = 0) |
|||
{ |
|||
if (!this.pinHandle.IsAllocated) |
|||
{ |
|||
this.pinHandle = GCHandle.Alloc(this.GetPinnableObject(), GCHandleType.Pinned); |
|||
} |
|||
|
|||
void* ptr = (void*)this.pinHandle.AddrOfPinnedObject(); |
|||
return new MemoryHandle(ptr, this.pinHandle); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public override void Unpin() |
|||
{ |
|||
if (this.pinHandle.IsAllocated) |
|||
{ |
|||
this.pinHandle.Free(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the object that should be pinned.
|
|||
/// </summary>
|
|||
/// <returns>The pinnable <see cref="object"/>.</returns>
|
|||
protected abstract object GetPinnableObject(); |
|||
} |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Buffers; |
|||
|
|||
namespace SixLabors.Memory |
|||
{ |
|||
/// <summary>
|
|||
/// Memory managers are used to allocate memory for image processing operations.
|
|||
/// </summary>
|
|||
public abstract class MemoryAllocator |
|||
{ |
|||
/// <summary>
|
|||
/// Allocates an <see cref="IMemoryOwner{T}" />, holding a <see cref="System.Memory{T}"/> of length <paramref name="length"/>.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">Type of the data stored in the buffer.</typeparam>
|
|||
/// <param name="length">Size of the buffer to allocate.</param>
|
|||
/// <param name="options">The allocation options.</param>
|
|||
/// <returns>A buffer of values of type <typeparamref name="T"/>.</returns>
|
|||
public abstract IMemoryOwner<T> Allocate<T>(int length, AllocationOptions options = AllocationOptions.None) |
|||
where T : struct; |
|||
|
|||
/// <summary>
|
|||
/// Allocates an <see cref="IManagedByteBuffer"/>.
|
|||
/// </summary>
|
|||
/// <param name="length">The requested buffer length.</param>
|
|||
/// <param name="options">The allocation options.</param>
|
|||
/// <returns>The <see cref="IManagedByteBuffer"/>.</returns>
|
|||
public abstract IManagedByteBuffer AllocateManagedByteBuffer(int length, AllocationOptions options = AllocationOptions.None); |
|||
|
|||
/// <summary>
|
|||
/// Releases all retained resources not being in use.
|
|||
/// Eg: by resetting array pools and letting GC to free the arrays.
|
|||
/// </summary>
|
|||
public virtual void ReleaseRetainedResources() |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
using SixLabors.Memory.Internals; |
|||
|
|||
namespace SixLabors.Memory |
|||
{ |
|||
/// <summary>
|
|||
/// Implements <see cref="MemoryAllocator"/> by newing up arrays by the GC on every allocation requests.
|
|||
/// </summary>
|
|||
public sealed class SimpleGcMemoryAllocator : MemoryAllocator |
|||
{ |
|||
/// <inheritdoc />
|
|||
public override IMemoryOwner<T> Allocate<T>(int length, AllocationOptions options = AllocationOptions.None) |
|||
{ |
|||
Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length)); |
|||
|
|||
return new BasicArrayBuffer<T>(new T[length]); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public override IManagedByteBuffer AllocateManagedByteBuffer(int length, AllocationOptions options = AllocationOptions.None) |
|||
{ |
|||
Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length)); |
|||
|
|||
return new BasicByteBuffer(new byte[length]); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,101 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Numerics; |
|||
|
|||
namespace SixLabors.Primitives |
|||
{ |
|||
/// <summary>
|
|||
/// Extension methods for the <see cref="Matrix3x2"/> struct.
|
|||
/// </summary>
|
|||
public static class Matrix3x2Extensions |
|||
{ |
|||
/// <summary>
|
|||
/// Creates a translation matrix from the given vector.
|
|||
/// </summary>
|
|||
/// <param name="position">The translation position.</param>
|
|||
/// <returns>A translation matrix.</returns>
|
|||
public static Matrix3x2 CreateTranslation(PointF position) => Matrix3x2.CreateTranslation(position); |
|||
|
|||
/// <summary>
|
|||
/// Creates a scale matrix that is offset by a given center point.
|
|||
/// </summary>
|
|||
/// <param name="xScale">Value to scale by on the X-axis.</param>
|
|||
/// <param name="yScale">Value to scale by on the Y-axis.</param>
|
|||
/// <param name="centerPoint">The center point.</param>
|
|||
/// <returns>A scaling matrix.</returns>
|
|||
public static Matrix3x2 CreateScale(float xScale, float yScale, PointF centerPoint) => Matrix3x2.CreateScale(xScale, yScale, centerPoint); |
|||
|
|||
/// <summary>
|
|||
/// Creates a scale matrix from the given vector scale.
|
|||
/// </summary>
|
|||
/// <param name="scales">The scale to use.</param>
|
|||
/// <returns>A scaling matrix.</returns>
|
|||
public static Matrix3x2 CreateScale(SizeF scales) => Matrix3x2.CreateScale(scales); |
|||
|
|||
/// <summary>
|
|||
/// Creates a scale matrix from the given vector scale with an offset from the given center point.
|
|||
/// </summary>
|
|||
/// <param name="scales">The scale to use.</param>
|
|||
/// <param name="centerPoint">The center offset.</param>
|
|||
/// <returns>A scaling matrix.</returns>
|
|||
public static Matrix3x2 CreateScale(SizeF scales, PointF centerPoint) => Matrix3x2.CreateScale(scales, centerPoint); |
|||
|
|||
/// <summary>
|
|||
/// Creates a scale matrix that scales uniformly with the given scale with an offset from the given center.
|
|||
/// </summary>
|
|||
/// <param name="scale">The uniform scale to use.</param>
|
|||
/// <param name="centerPoint">The center offset.</param>
|
|||
/// <returns>A scaling matrix.</returns>
|
|||
public static Matrix3x2 CreateScale(float scale, PointF centerPoint) => Matrix3x2.CreateScale(scale, centerPoint); |
|||
|
|||
/// <summary>
|
|||
/// Creates a skew matrix from the given angles in degrees.
|
|||
/// </summary>
|
|||
/// <param name="degreesX">The X angle, in degrees.</param>
|
|||
/// <param name="degreesY">The Y angle, in degrees.</param>
|
|||
/// <returns>A skew matrix.</returns>
|
|||
public static Matrix3x2 CreateSkewDegrees(float degreesX, float degreesY) => Matrix3x2.CreateSkew(GeometryUtilities.DegreeToRadian(degreesX), GeometryUtilities.DegreeToRadian(degreesY)); |
|||
|
|||
/// <summary>
|
|||
/// Creates a skew matrix from the given angles in radians and a center point.
|
|||
/// </summary>
|
|||
/// <param name="radiansX">The X angle, in radians.</param>
|
|||
/// <param name="radiansY">The Y angle, in radians.</param>
|
|||
/// <param name="centerPoint">The center point.</param>
|
|||
/// <returns>A skew matrix.</returns>
|
|||
public static Matrix3x2 CreateSkew(float radiansX, float radiansY, PointF centerPoint) => Matrix3x2.CreateSkew(radiansX, radiansY, centerPoint); |
|||
|
|||
/// <summary>
|
|||
/// Creates a skew matrix from the given angles in degrees and a center point.
|
|||
/// </summary>
|
|||
/// <param name="degreesX">The X angle, in degrees.</param>
|
|||
/// <param name="degreesY">The Y angle, in degrees.</param>
|
|||
/// <param name="centerPoint">The center point.</param>
|
|||
/// <returns>A skew matrix.</returns>
|
|||
public static Matrix3x2 CreateSkewDegrees(float degreesX, float degreesY, PointF centerPoint) => Matrix3x2.CreateSkew(GeometryUtilities.DegreeToRadian(degreesX), GeometryUtilities.DegreeToRadian(degreesY), centerPoint); |
|||
|
|||
/// <summary>
|
|||
/// Creates a rotation matrix using the given rotation in degrees.
|
|||
/// </summary>
|
|||
/// <param name="degrees">The amount of rotation, in degrees.</param>
|
|||
/// <returns>A rotation matrix.</returns>
|
|||
public static Matrix3x2 CreateRotationDegrees(float degrees) => Matrix3x2.CreateRotation(GeometryUtilities.DegreeToRadian(degrees)); |
|||
|
|||
/// <summary>
|
|||
/// Creates a rotation matrix using the given rotation in radians and a center point.
|
|||
/// </summary>
|
|||
/// <param name="radians">The amount of rotation, in radians.</param>
|
|||
/// <param name="centerPoint">The center point.</param>
|
|||
/// <returns>A rotation matrix.</returns>
|
|||
public static Matrix3x2 CreateRotation(float radians, PointF centerPoint) => Matrix3x2.CreateRotation(radians, centerPoint); |
|||
|
|||
/// <summary>
|
|||
/// Creates a rotation matrix using the given rotation in degrees and a center point.
|
|||
/// </summary>
|
|||
/// <param name="degrees">The amount of rotation, in degrees.</param>
|
|||
/// <param name="centerPoint">The center point.</param>
|
|||
/// <returns>A rotation matrix.</returns>
|
|||
public static Matrix3x2 CreateRotationDegrees(float degrees, PointF centerPoint) => Matrix3x2.CreateRotation(GeometryUtilities.DegreeToRadian(degrees), centerPoint); |
|||
} |
|||
} |
|||
@ -0,0 +1,288 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.ComponentModel; |
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.Primitives |
|||
{ |
|||
/// <summary>
|
|||
/// Represents an ordered pair of integer x- and y-coordinates that defines a point in
|
|||
/// a two-dimensional plane.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This struct is fully mutable. This is done (against the guidelines) for the sake of performance,
|
|||
/// as it avoids the need to create new values for modification operations.
|
|||
/// </remarks>
|
|||
public struct Point : IEquatable<Point> |
|||
{ |
|||
/// <summary>
|
|||
/// Represents a <see cref="Point"/> that has X and Y values set to zero.
|
|||
/// </summary>
|
|||
public static readonly Point Empty = default; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Point"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="value">The horizontal and vertical position of the point.</param>
|
|||
public Point(int value) |
|||
: this() |
|||
{ |
|||
this.X = LowInt16(value); |
|||
this.Y = HighInt16(value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Point"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="x">The horizontal position of the point.</param>
|
|||
/// <param name="y">The vertical position of the point.</param>
|
|||
public Point(int x, int y) |
|||
: this() |
|||
{ |
|||
this.X = x; |
|||
this.Y = y; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Point"/> struct from the given <see cref="Size"/>.
|
|||
/// </summary>
|
|||
/// <param name="size">The size.</param>
|
|||
public Point(Size size) |
|||
{ |
|||
this.X = size.Width; |
|||
this.Y = size.Height; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the x-coordinate of this <see cref="Point"/>.
|
|||
/// </summary>
|
|||
public int X { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the y-coordinate of this <see cref="Point"/>.
|
|||
/// </summary>
|
|||
public int Y { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether this <see cref="Point"/> is empty.
|
|||
/// </summary>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public bool IsEmpty => this.Equals(Empty); |
|||
|
|||
/// <summary>
|
|||
/// Creates a <see cref="PointF"/> with the coordinates of the specified <see cref="Point"/>.
|
|||
/// </summary>
|
|||
/// <param name="point">The point.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static implicit operator PointF(Point point) => new PointF(point.X, point.Y); |
|||
|
|||
/// <summary>
|
|||
/// Creates a <see cref="Vector2"/> with the coordinates of the specified <see cref="Point"/>.
|
|||
/// </summary>
|
|||
/// <param name="point">The point.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static implicit operator Vector2(Point point) => new Vector2(point.X, point.Y); |
|||
|
|||
/// <summary>
|
|||
/// Creates a <see cref="Size"/> with the coordinates of the specified <see cref="Point"/>.
|
|||
/// </summary>
|
|||
/// <param name="point">The point.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static explicit operator Size(Point point) => new Size(point.X, point.Y); |
|||
|
|||
/// <summary>
|
|||
/// Negates the given point by multiplying all values by -1.
|
|||
/// </summary>
|
|||
/// <param name="value">The source point.</param>
|
|||
/// <returns>The negated point.</returns>
|
|||
public static Point operator -(Point value) => new Point(-value.X, -value.Y); |
|||
|
|||
/// <summary>
|
|||
/// Translates a <see cref="Point"/> by a given <see cref="Size"/>.
|
|||
/// </summary>
|
|||
/// <param name="point">The point on the left hand of the operand.</param>
|
|||
/// <param name="size">The size on the right hand of the operand.</param>
|
|||
/// <returns>
|
|||
/// The <see cref="Point"/>.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Point operator +(Point point, Size size) => Add(point, size); |
|||
|
|||
/// <summary>
|
|||
/// Translates a <see cref="Point"/> by the negative of a given <see cref="Size"/>.
|
|||
/// </summary>
|
|||
/// <param name="point">The point on the left hand of the operand.</param>
|
|||
/// <param name="size">The size on the right hand of the operand.</param>
|
|||
/// <returns>The <see cref="Point"/>.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Point operator -(Point point, Size size) => Subtract(point, size); |
|||
|
|||
/// <summary>
|
|||
/// Multiplies <see cref="Point"/> by a <see cref="int"/> producing <see cref="Point"/>.
|
|||
/// </summary>
|
|||
/// <param name="left">Multiplier of type <see cref="int"/>.</param>
|
|||
/// <param name="right">Multiplicand of type <see cref="Point"/>.</param>
|
|||
/// <returns>Product of type <see cref="Point"/>.</returns>
|
|||
public static Point operator *(int left, Point right) => Multiply(right, left); |
|||
|
|||
/// <summary>
|
|||
/// Multiplies <see cref="Point"/> by a <see cref="int"/> producing <see cref="Point"/>.
|
|||
/// </summary>
|
|||
/// <param name="left">Multiplicand of type <see cref="Point"/>.</param>
|
|||
/// <param name="right">Multiplier of type <see cref="int"/>.</param>
|
|||
/// <returns>Product of type <see cref="Point"/>.</returns>
|
|||
public static Point operator *(Point left, int right) => Multiply(left, right); |
|||
|
|||
/// <summary>
|
|||
/// Divides <see cref="Point"/> by a <see cref="int"/> producing <see cref="Point"/>.
|
|||
/// </summary>
|
|||
/// <param name="left">Dividend of type <see cref="Point"/>.</param>
|
|||
/// <param name="right">Divisor of type <see cref="int"/>.</param>
|
|||
/// <returns>Result of type <see cref="Point"/>.</returns>
|
|||
public static Point operator /(Point left, int right) |
|||
=> new Point(left.X / right, left.Y / right); |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="Point"/> objects for equality.
|
|||
/// </summary>
|
|||
/// <param name="left">The <see cref="Point"/> on the left side of the operand.</param>
|
|||
/// <param name="right">The <see cref="Point"/> on the right side of the operand.</param>
|
|||
/// <returns>
|
|||
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static bool operator ==(Point left, Point right) => left.Equals(right); |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="Point"/> objects for inequality.
|
|||
/// </summary>
|
|||
/// <param name="left">The <see cref="Point"/> on the left side of the operand.</param>
|
|||
/// <param name="right">The <see cref="Point"/> on the right side of the operand.</param>
|
|||
/// <returns>
|
|||
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static bool operator !=(Point left, Point right) => !left.Equals(right); |
|||
|
|||
/// <summary>
|
|||
/// Translates a <see cref="Point"/> by the negative of a given <see cref="Size"/>.
|
|||
/// </summary>
|
|||
/// <param name="point">The point on the left hand of the operand.</param>
|
|||
/// <param name="size">The size on the right hand of the operand.</param>
|
|||
/// <returns>The <see cref="Point"/>.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Point Add(Point point, Size size) => new Point(unchecked(point.X + size.Width), unchecked(point.Y + size.Height)); |
|||
|
|||
/// <summary>
|
|||
/// Translates a <see cref="Point"/> by the negative of a given value.
|
|||
/// </summary>
|
|||
/// <param name="point">The point on the left hand of the operand.</param>
|
|||
/// <param name="value">The value on the right hand of the operand.</param>
|
|||
/// <returns>The <see cref="Point"/>.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Point Multiply(Point point, int value) => new Point(unchecked(point.X * value), unchecked(point.Y * value)); |
|||
|
|||
/// <summary>
|
|||
/// Translates a <see cref="Point"/> by the negative of a given <see cref="Size"/>.
|
|||
/// </summary>
|
|||
/// <param name="point">The point on the left hand of the operand.</param>
|
|||
/// <param name="size">The size on the right hand of the operand.</param>
|
|||
/// <returns>The <see cref="Point"/>.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Point Subtract(Point point, Size size) => new Point(unchecked(point.X - size.Width), unchecked(point.Y - size.Height)); |
|||
|
|||
/// <summary>
|
|||
/// Converts a <see cref="PointF"/> to a <see cref="Point"/> by performing a ceiling operation on all the coordinates.
|
|||
/// </summary>
|
|||
/// <param name="point">The point.</param>
|
|||
/// <returns>The <see cref="Point"/>.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Point Ceiling(PointF point) => new Point(unchecked((int)MathF.Ceiling(point.X)), unchecked((int)MathF.Ceiling(point.Y))); |
|||
|
|||
/// <summary>
|
|||
/// Converts a <see cref="PointF"/> to a <see cref="Point"/> by performing a round operation on all the coordinates.
|
|||
/// </summary>
|
|||
/// <param name="point">The point.</param>
|
|||
/// <returns>The <see cref="Point"/>.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Point Round(PointF point) => new Point(unchecked((int)MathF.Round(point.X)), unchecked((int)MathF.Round(point.Y))); |
|||
|
|||
/// <summary>
|
|||
/// Converts a <see cref="Vector2"/> to a <see cref="Point"/> by performing a round operation on all the coordinates.
|
|||
/// </summary>
|
|||
/// <param name="vector">The vector.</param>
|
|||
/// <returns>The <see cref="Point"/>.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Point Round(Vector2 vector) => new Point(unchecked((int)MathF.Round(vector.X)), unchecked((int)MathF.Round(vector.Y))); |
|||
|
|||
/// <summary>
|
|||
/// Converts a <see cref="PointF"/> to a <see cref="Point"/> by performing a truncate operation on all the coordinates.
|
|||
/// </summary>
|
|||
/// <param name="point">The point.</param>
|
|||
/// <returns>The <see cref="Point"/>.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Point Truncate(PointF point) => new Point(unchecked((int)point.X), unchecked((int)point.Y)); |
|||
|
|||
/// <summary>
|
|||
/// Transforms a point by a specified 3x2 matrix.
|
|||
/// </summary>
|
|||
/// <param name="point">The point to transform.</param>
|
|||
/// <param name="matrix">The transformation matrix used.</param>
|
|||
/// <returns>The transformed <see cref="PointF"/>.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Point Transform(Point point, Matrix3x2 matrix) => Round(Vector2.Transform(new Vector2(point.X, point.Y), matrix)); |
|||
|
|||
/// <summary>
|
|||
/// Deconstructs this point into two integers.
|
|||
/// </summary>
|
|||
/// <param name="x">The out value for X.</param>
|
|||
/// <param name="y">The out value for Y.</param>
|
|||
public void Deconstruct(out int x, out int y) |
|||
{ |
|||
x = this.X; |
|||
y = this.Y; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Translates this <see cref="Point"/> by the specified amount.
|
|||
/// </summary>
|
|||
/// <param name="dx">The amount to offset the x-coordinate.</param>
|
|||
/// <param name="dy">The amount to offset the y-coordinate.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public void Offset(int dx, int dy) |
|||
{ |
|||
unchecked |
|||
{ |
|||
this.X += dx; |
|||
this.Y += dy; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Translates this <see cref="Point"/> by the specified amount.
|
|||
/// </summary>
|
|||
/// <param name="point">The <see cref="Point"/> used offset this <see cref="Point"/>.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public void Offset(Point point) => this.Offset(point.X, point.Y); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override int GetHashCode() => HashCode.Combine(this.X, this.Y); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override string ToString() => $"Point [ X={this.X}, Y={this.Y} ]"; |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool Equals(object obj) => obj is Point other && this.Equals(other); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public bool Equals(Point other) => this.X.Equals(other.X) && this.Y.Equals(other.Y); |
|||
|
|||
private static short HighInt16(int n) => unchecked((short)((n >> 16) & 0xffff)); |
|||
|
|||
private static short LowInt16(int n) => unchecked((short)(n & 0xffff)); |
|||
} |
|||
} |
|||
@ -0,0 +1,293 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.ComponentModel; |
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.Primitives |
|||
{ |
|||
/// <summary>
|
|||
/// Represents an ordered pair of single precision floating point x- and y-coordinates that defines a point in
|
|||
/// a two-dimensional plane.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This struct is fully mutable. This is done (against the guidelines) for the sake of performance,
|
|||
/// as it avoids the need to create new values for modification operations.
|
|||
/// </remarks>
|
|||
public struct PointF : IEquatable<PointF> |
|||
{ |
|||
/// <summary>
|
|||
/// Represents a <see cref="PointF"/> that has X and Y values set to zero.
|
|||
/// </summary>
|
|||
public static readonly PointF Empty = default; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="PointF"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="x">The horizontal position of the point.</param>
|
|||
/// <param name="y">The vertical position of the point.</param>
|
|||
public PointF(float x, float y) |
|||
: this() |
|||
{ |
|||
this.X = x; |
|||
this.Y = y; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="PointF"/> struct from the given <see cref="SizeF"/>.
|
|||
/// </summary>
|
|||
/// <param name="size">The size.</param>
|
|||
public PointF(SizeF size) |
|||
{ |
|||
this.X = size.Width; |
|||
this.Y = size.Height; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the x-coordinate of this <see cref="PointF"/>.
|
|||
/// </summary>
|
|||
public float X { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the y-coordinate of this <see cref="PointF"/>.
|
|||
/// </summary>
|
|||
public float Y { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether this <see cref="PointF"/> is empty.
|
|||
/// </summary>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public bool IsEmpty => this.Equals(Empty); |
|||
|
|||
/// <summary>
|
|||
/// Creates a <see cref="Vector2"/> with the coordinates of the specified <see cref="PointF"/>.
|
|||
/// </summary>
|
|||
/// <param name="vector">The vector.</param>
|
|||
/// <returns>
|
|||
/// The <see cref="Vector2"/>.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static implicit operator PointF(Vector2 vector) => new PointF(vector.X, vector.Y); |
|||
|
|||
/// <summary>
|
|||
/// Creates a <see cref="Vector2"/> with the coordinates of the specified <see cref="PointF"/>.
|
|||
/// </summary>
|
|||
/// <param name="point">The point.</param>
|
|||
/// <returns>
|
|||
/// The <see cref="Vector2"/>.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static implicit operator Vector2(PointF point) => new Vector2(point.X, point.Y); |
|||
|
|||
/// <summary>
|
|||
/// Creates a <see cref="Point"/> with the coordinates of the specified <see cref="PointF"/> by truncating each of the coordinates.
|
|||
/// </summary>
|
|||
/// <param name="point">The point.</param>
|
|||
/// <returns>
|
|||
/// The <see cref="Point"/>.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static explicit operator Point(PointF point) => Point.Truncate(point); |
|||
|
|||
/// <summary>
|
|||
/// Negates the given point by multiplying all values by -1.
|
|||
/// </summary>
|
|||
/// <param name="value">The source point.</param>
|
|||
/// <returns>The negated point.</returns>
|
|||
public static PointF operator -(PointF value) => new PointF(-value.X, -value.Y); |
|||
|
|||
/// <summary>
|
|||
/// Translates a <see cref="PointF"/> by a given <see cref="SizeF"/>.
|
|||
/// </summary>
|
|||
/// <param name="point">The point on the left hand of the operand.</param>
|
|||
/// <param name="size">The size on the right hand of the operand.</param>
|
|||
/// <returns>
|
|||
/// The <see cref="PointF"/>.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static PointF operator +(PointF point, SizeF size) => Add(point, size); |
|||
|
|||
/// <summary>
|
|||
/// Translates a <see cref="PointF"/> by the negative of a given <see cref="SizeF"/>.
|
|||
/// </summary>
|
|||
/// <param name="point">The point on the left hand of the operand.</param>
|
|||
/// <param name="size">The size on the right hand of the operand.</param>
|
|||
/// <returns>The <see cref="PointF"/>.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static PointF operator -(PointF point, PointF size) => Subtract(point, size); |
|||
|
|||
/// <summary>
|
|||
/// Translates a <see cref="PointF"/> by a given <see cref="SizeF"/>.
|
|||
/// </summary>
|
|||
/// <param name="point">The point on the left hand of the operand.</param>
|
|||
/// <param name="size">The size on the right hand of the operand.</param>
|
|||
/// <returns>
|
|||
/// The <see cref="PointF"/>.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static PointF operator +(PointF point, PointF size) => Add(point, size); |
|||
|
|||
/// <summary>
|
|||
/// Translates a <see cref="PointF"/> by the negative of a given <see cref="SizeF"/>.
|
|||
/// </summary>
|
|||
/// <param name="point">The point on the left hand of the operand.</param>
|
|||
/// <param name="size">The size on the right hand of the operand.</param>
|
|||
/// <returns>The <see cref="PointF"/>.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static PointF operator -(PointF point, SizeF size) => Subtract(point, size); |
|||
|
|||
/// <summary>
|
|||
/// Multiplies <see cref="PointF"/> by a <see cref="float"/> producing <see cref="SizeF"/>.
|
|||
/// </summary>
|
|||
/// <param name="left">Multiplier of type <see cref="float"/>.</param>
|
|||
/// <param name="right">Multiplicand of type <see cref="SizeF"/>.</param>
|
|||
/// <returns>Product of type <see cref="SizeF"/>.</returns>
|
|||
public static PointF operator *(float left, PointF right) => Multiply(right, left); |
|||
|
|||
/// <summary>
|
|||
/// Multiplies <see cref="PointF"/> by a <see cref="float"/> producing <see cref="SizeF"/>.
|
|||
/// </summary>
|
|||
/// <param name="left">Multiplicand of type <see cref="PointF"/>.</param>
|
|||
/// <param name="right">Multiplier of type <see cref="float"/>.</param>
|
|||
/// <returns>Product of type <see cref="SizeF"/>.</returns>
|
|||
public static PointF operator *(PointF left, float right) => Multiply(left, right); |
|||
|
|||
/// <summary>
|
|||
/// Divides <see cref="PointF"/> by a <see cref="float"/> producing <see cref="SizeF"/>.
|
|||
/// </summary>
|
|||
/// <param name="left">Dividend of type <see cref="PointF"/>.</param>
|
|||
/// <param name="right">Divisor of type <see cref="int"/>.</param>
|
|||
/// <returns>Result of type <see cref="PointF"/>.</returns>
|
|||
public static PointF operator /(PointF left, float right) |
|||
=> new PointF(left.X / right, left.Y / right); |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="PointF"/> objects for equality.
|
|||
/// </summary>
|
|||
/// <param name="left">
|
|||
/// The <see cref="PointF"/> on the left side of the operand.
|
|||
/// </param>
|
|||
/// <param name="right">
|
|||
/// The <see cref="PointF"/> on the right side of the operand.
|
|||
/// </param>
|
|||
/// <returns>
|
|||
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static bool operator ==(PointF left, PointF right) => left.Equals(right); |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="PointF"/> objects for inequality.
|
|||
/// </summary>
|
|||
/// <param name="left">
|
|||
/// The <see cref="PointF"/> on the left side of the operand.
|
|||
/// </param>
|
|||
/// <param name="right">
|
|||
/// The <see cref="PointF"/> on the right side of the operand.
|
|||
/// </param>
|
|||
/// <returns>
|
|||
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static bool operator !=(PointF left, PointF right) => !left.Equals(right); |
|||
|
|||
/// <summary>
|
|||
/// Translates a <see cref="PointF"/> by the given <see cref="SizeF"/>.
|
|||
/// </summary>
|
|||
/// <param name="point">The point on the left hand of the operand.</param>
|
|||
/// <param name="size">The size on the right hand of the operand.</param>
|
|||
/// <returns>The <see cref="PointF"/>.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static PointF Add(PointF point, SizeF size) => new PointF(point.X + size.Width, point.Y + size.Height); |
|||
|
|||
/// <summary>
|
|||
/// Translates a <see cref="PointF"/> by the given <see cref="PointF"/>.
|
|||
/// </summary>
|
|||
/// <param name="point">The point on the left hand of the operand.</param>
|
|||
/// <param name="pointb">The point on the right hand of the operand.</param>
|
|||
/// <returns>The <see cref="PointF"/>.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static PointF Add(PointF point, PointF pointb) => new PointF(point.X + pointb.X, point.Y + pointb.Y); |
|||
|
|||
/// <summary>
|
|||
/// Translates a <see cref="PointF"/> by the negative of a given <see cref="SizeF"/>.
|
|||
/// </summary>
|
|||
/// <param name="point">The point on the left hand of the operand.</param>
|
|||
/// <param name="size">The size on the right hand of the operand.</param>
|
|||
/// <returns>The <see cref="PointF"/>.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static PointF Subtract(PointF point, SizeF size) => new PointF(point.X - size.Width, point.Y - size.Height); |
|||
|
|||
/// <summary>
|
|||
/// Translates a <see cref="PointF"/> by the negative of a given <see cref="PointF"/>.
|
|||
/// </summary>
|
|||
/// <param name="point">The point on the left hand of the operand.</param>
|
|||
/// <param name="pointb">The point on the right hand of the operand.</param>
|
|||
/// <returns>The <see cref="PointF"/>.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static PointF Subtract(PointF point, PointF pointb) => new PointF(point.X - pointb.X, point.Y - pointb.Y); |
|||
|
|||
/// <summary>
|
|||
/// Translates a <see cref="PointF"/> by the multiplying the X and Y by the given value.
|
|||
/// </summary>
|
|||
/// <param name="point">The point on the left hand of the operand.</param>
|
|||
/// <param name="right">The value on the right hand of the operand.</param>
|
|||
/// <returns>The <see cref="PointF"/>.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static PointF Multiply(PointF point, float right) => new PointF(point.X * right, point.Y * right); |
|||
|
|||
/// <summary>
|
|||
/// Transforms a point by a specified 3x2 matrix.
|
|||
/// </summary>
|
|||
/// <param name="point">The point to transform.</param>
|
|||
/// <param name="matrix">The transformation matrix used.</param>
|
|||
/// <returns>The transformed <see cref="PointF"/>.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static PointF Transform(PointF point, Matrix3x2 matrix) => Vector2.Transform(point, matrix); |
|||
|
|||
/// <summary>
|
|||
/// Deconstructs this point into two floats.
|
|||
/// </summary>
|
|||
/// <param name="x">The out value for X.</param>
|
|||
/// <param name="y">The out value for Y.</param>
|
|||
public void Deconstruct(out float x, out float y) |
|||
{ |
|||
x = this.X; |
|||
y = this.Y; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Translates this <see cref="PointF"/> by the specified amount.
|
|||
/// </summary>
|
|||
/// <param name="dx">The amount to offset the x-coordinate.</param>
|
|||
/// <param name="dy">The amount to offset the y-coordinate.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public void Offset(float dx, float dy) |
|||
{ |
|||
this.X += dx; |
|||
this.Y += dy; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Translates this <see cref="PointF"/> by the specified amount.
|
|||
/// </summary>
|
|||
/// <param name="point">The <see cref="PointF"/> used offset this <see cref="PointF"/>.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public void Offset(PointF point) => this.Offset(point.X, point.Y); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override int GetHashCode() => HashCode.Combine(this.X, this.Y); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override string ToString() => $"PointF [ X={this.X}, Y={this.Y} ]"; |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool Equals(object obj) => obj is PointF && this.Equals((PointF)obj); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public bool Equals(PointF other) => this.X.Equals(other.X) && this.Y.Equals(other.Y); |
|||
} |
|||
} |
|||
@ -0,0 +1,463 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.ComponentModel; |
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.Primitives |
|||
{ |
|||
/// <summary>
|
|||
/// Stores a set of four integers that represent the location and size of a rectangle.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This struct is fully mutable. This is done (against the guidelines) for the sake of performance,
|
|||
/// as it avoids the need to create new values for modification operations.
|
|||
/// </remarks>
|
|||
public struct Rectangle : IEquatable<Rectangle> |
|||
{ |
|||
/// <summary>
|
|||
/// Represents a <see cref="Rectangle"/> that has X, Y, Width, and Height values set to zero.
|
|||
/// </summary>
|
|||
public static readonly Rectangle Empty = default; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Rectangle"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="x">The horizontal position of the rectangle.</param>
|
|||
/// <param name="y">The vertical position of the rectangle.</param>
|
|||
/// <param name="width">The width of the rectangle.</param>
|
|||
/// <param name="height">The height of the rectangle.</param>
|
|||
public Rectangle(int x, int y, int width, int height) |
|||
{ |
|||
this.X = x; |
|||
this.Y = y; |
|||
this.Width = width; |
|||
this.Height = height; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Rectangle"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="point">
|
|||
/// The <see cref="Point"/> which specifies the rectangles point in a two-dimensional plane.
|
|||
/// </param>
|
|||
/// <param name="size">
|
|||
/// The <see cref="Size"/> which specifies the rectangles height and width.
|
|||
/// </param>
|
|||
public Rectangle(Point point, Size size) |
|||
{ |
|||
this.X = point.X; |
|||
this.Y = point.Y; |
|||
this.Width = size.Width; |
|||
this.Height = size.Height; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the x-coordinate of this <see cref="Rectangle"/>.
|
|||
/// </summary>
|
|||
public int X { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the y-coordinate of this <see cref="Rectangle"/>.
|
|||
/// </summary>
|
|||
public int Y { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the width of this <see cref="Rectangle"/>.
|
|||
/// </summary>
|
|||
public int Width { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the height of this <see cref="Rectangle"/>.
|
|||
/// </summary>
|
|||
public int Height { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the coordinates of the upper-left corner of the rectangular region represented by this <see cref="Rectangle"/>.
|
|||
/// </summary>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public Point Location |
|||
{ |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
get => new Point(this.X, this.Y); |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
set |
|||
{ |
|||
this.X = value.X; |
|||
this.Y = value.Y; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the size of this <see cref="Rectangle"/>.
|
|||
/// </summary>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public Size Size |
|||
{ |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
get => new Size(this.Width, this.Height); |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
set |
|||
{ |
|||
this.Width = value.Width; |
|||
this.Height = value.Height; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether this <see cref="Rectangle"/> is empty.
|
|||
/// </summary>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public bool IsEmpty => this.Equals(Empty); |
|||
|
|||
/// <summary>
|
|||
/// Gets the y-coordinate of the top edge of this <see cref="Rectangle"/>.
|
|||
/// </summary>
|
|||
public int Top => this.Y; |
|||
|
|||
/// <summary>
|
|||
/// Gets the x-coordinate of the right edge of this <see cref="Rectangle"/>.
|
|||
/// </summary>
|
|||
public int Right |
|||
{ |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
get => unchecked(this.X + this.Width); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the y-coordinate of the bottom edge of this <see cref="Rectangle"/>.
|
|||
/// </summary>
|
|||
public int Bottom |
|||
{ |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
get => unchecked(this.Y + this.Height); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the x-coordinate of the left edge of this <see cref="Rectangle"/>.
|
|||
/// </summary>
|
|||
public int Left => this.X; |
|||
|
|||
/// <summary>
|
|||
/// Creates a <see cref="RectangleF"/> with the coordinates of the specified <see cref="Rectangle"/>.
|
|||
/// </summary>
|
|||
/// <param name="rectangle">The rectangle.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static implicit operator RectangleF(Rectangle rectangle) => new RectangleF(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height); |
|||
|
|||
/// <summary>
|
|||
/// Creates a <see cref="Vector4"/> with the coordinates of the specified <see cref="Rectangle"/>.
|
|||
/// </summary>
|
|||
/// <param name="rectangle">The rectangle.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static implicit operator Vector4(Rectangle rectangle) => new Vector4(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height); |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="Rectangle"/> objects for equality.
|
|||
/// </summary>
|
|||
/// <param name="left">The <see cref="Rectangle"/> on the left side of the operand.</param>
|
|||
/// <param name="right">The <see cref="Rectangle"/> on the right side of the operand.</param>
|
|||
/// <returns>
|
|||
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static bool operator ==(Rectangle left, Rectangle right) => left.Equals(right); |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="Rectangle"/> objects for inequality.
|
|||
/// </summary>
|
|||
/// <param name="left">The <see cref="Rectangle"/> on the left side of the operand.</param>
|
|||
/// <param name="right">The <see cref="Rectangle"/> on the right side of the operand.</param>
|
|||
/// <returns>
|
|||
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static bool operator !=(Rectangle left, Rectangle right) => !left.Equals(right); |
|||
|
|||
/// <summary>
|
|||
/// Creates a new <see cref="Rectangle"/> with the specified location and size. </summary>
|
|||
/// <param name="left">The left coordinate of the rectangle.</param>
|
|||
/// <param name="top">The top coordinate of the rectangle.</param>
|
|||
/// <param name="right">The right coordinate of the rectangle.</param>
|
|||
/// <param name="bottom">The bottom coordinate of the rectangle.</param>
|
|||
/// <returns>The <see cref="Rectangle"/>.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
|
|||
// ReSharper disable once InconsistentNaming
|
|||
public static Rectangle FromLTRB(int left, int top, int right, int bottom) => new Rectangle(left, top, unchecked(right - left), unchecked(bottom - top)); |
|||
|
|||
/// <summary>
|
|||
/// Returns the center point of the given <see cref="Rectangle"/>.
|
|||
/// </summary>
|
|||
/// <param name="rectangle">The rectangle.</param>
|
|||
/// <returns>The <see cref="Point"/>.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Point Center(Rectangle rectangle) => new Point(rectangle.Left + (rectangle.Width / 2), rectangle.Top + (rectangle.Height / 2)); |
|||
|
|||
/// <summary>
|
|||
/// Creates a rectangle that represents the intersection between <paramref name="a"/> and
|
|||
/// <paramref name="b"/>. If there is no intersection, an empty rectangle is returned.
|
|||
/// </summary>
|
|||
/// <param name="a">The first rectangle.</param>
|
|||
/// <param name="b">The second rectangle.</param>
|
|||
/// <returns>The <see cref="Rectangle"/>.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Rectangle Intersect(Rectangle a, Rectangle b) |
|||
{ |
|||
int x1 = Math.Max(a.X, b.X); |
|||
int x2 = Math.Min(a.Right, b.Right); |
|||
int y1 = Math.Max(a.Y, b.Y); |
|||
int y2 = Math.Min(a.Bottom, b.Bottom); |
|||
|
|||
if (x2 >= x1 && y2 >= y1) |
|||
{ |
|||
return new Rectangle(x1, y1, x2 - x1, y2 - y1); |
|||
} |
|||
|
|||
return Empty; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates a <see cref="Rectangle"/> that is inflated by the specified amount.
|
|||
/// </summary>
|
|||
/// <param name="rectangle">The rectangle.</param>
|
|||
/// <param name="x">The amount to inflate the width by.</param>
|
|||
/// <param name="y">The amount to inflate the height by.</param>
|
|||
/// <returns>A new <see cref="Rectangle"/>.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Rectangle Inflate(Rectangle rectangle, int x, int y) |
|||
{ |
|||
Rectangle r = rectangle; |
|||
r.Inflate(x, y); |
|||
return r; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a <see cref="RectangleF"/> to a <see cref="Rectangle"/> by performing a ceiling operation on all the coordinates.
|
|||
/// </summary>
|
|||
/// <param name="rectangle">The rectangle.</param>
|
|||
/// <returns>The <see cref="Rectangle"/>.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Rectangle Ceiling(RectangleF rectangle) |
|||
{ |
|||
unchecked |
|||
{ |
|||
return new Rectangle( |
|||
(int)MathF.Ceiling(rectangle.X), |
|||
(int)MathF.Ceiling(rectangle.Y), |
|||
(int)MathF.Ceiling(rectangle.Width), |
|||
(int)MathF.Ceiling(rectangle.Height)); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Transforms a rectangle by the given matrix.
|
|||
/// </summary>
|
|||
/// <param name="rectangle">The source rectangle.</param>
|
|||
/// <param name="matrix">The transformation matrix.</param>
|
|||
/// <returns>A transformed rectangle.</returns>
|
|||
public static RectangleF Transform(Rectangle rectangle, Matrix3x2 matrix) |
|||
{ |
|||
PointF bottomRight = Point.Transform(new Point(rectangle.Right, rectangle.Bottom), matrix); |
|||
PointF topLeft = Point.Transform(rectangle.Location, matrix); |
|||
return new RectangleF(topLeft, new SizeF(bottomRight - topLeft)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a <see cref="RectangleF"/> to a <see cref="Rectangle"/> by performing a truncate operation on all the coordinates.
|
|||
/// </summary>
|
|||
/// <param name="rectangle">The rectangle.</param>
|
|||
/// <returns>The <see cref="Rectangle"/>.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Rectangle Truncate(RectangleF rectangle) |
|||
{ |
|||
unchecked |
|||
{ |
|||
return new Rectangle( |
|||
(int)rectangle.X, |
|||
(int)rectangle.Y, |
|||
(int)rectangle.Width, |
|||
(int)rectangle.Height); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a <see cref="RectangleF"/> to a <see cref="Rectangle"/> by performing a round operation on all the coordinates.
|
|||
/// </summary>
|
|||
/// <param name="rectangle">The rectangle.</param>
|
|||
/// <returns>The <see cref="Rectangle"/>.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Rectangle Round(RectangleF rectangle) |
|||
{ |
|||
unchecked |
|||
{ |
|||
return new Rectangle( |
|||
(int)MathF.Round(rectangle.X), |
|||
(int)MathF.Round(rectangle.Y), |
|||
(int)MathF.Round(rectangle.Width), |
|||
(int)MathF.Round(rectangle.Height)); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates a rectangle that represents the union between <paramref name="a"/> and <paramref name="b"/>.
|
|||
/// </summary>
|
|||
/// <param name="a">The first rectangle.</param>
|
|||
/// <param name="b">The second rectangle.</param>
|
|||
/// <returns>The <see cref="Rectangle"/>.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Rectangle Union(Rectangle a, Rectangle b) |
|||
{ |
|||
int x1 = Math.Min(a.X, b.X); |
|||
int x2 = Math.Max(a.Right, b.Right); |
|||
int y1 = Math.Min(a.Y, b.Y); |
|||
int y2 = Math.Max(a.Bottom, b.Bottom); |
|||
|
|||
return new Rectangle(x1, y1, x2 - x1, y2 - y1); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Deconstructs this rectangle into four integers.
|
|||
/// </summary>
|
|||
/// <param name="x">The out value for X.</param>
|
|||
/// <param name="y">The out value for Y.</param>
|
|||
/// <param name="width">The out value for the width.</param>
|
|||
/// <param name="height">The out value for the height.</param>
|
|||
public void Deconstruct(out int x, out int y, out int width, out int height) |
|||
{ |
|||
x = this.X; |
|||
y = this.Y; |
|||
width = this.Width; |
|||
height = this.Height; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates a Rectangle that represents the intersection between this Rectangle and the <paramref name="rectangle"/>.
|
|||
/// </summary>
|
|||
/// <param name="rectangle">The rectangle.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public void Intersect(Rectangle rectangle) |
|||
{ |
|||
Rectangle result = Intersect(rectangle, this); |
|||
|
|||
this.X = result.X; |
|||
this.Y = result.Y; |
|||
this.Width = result.Width; |
|||
this.Height = result.Height; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Inflates this <see cref="Rectangle"/> by the specified amount.
|
|||
/// </summary>
|
|||
/// <param name="width">The width.</param>
|
|||
/// <param name="height">The height.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public void Inflate(int width, int height) |
|||
{ |
|||
unchecked |
|||
{ |
|||
this.X -= width; |
|||
this.Y -= height; |
|||
|
|||
this.Width += 2 * width; |
|||
this.Height += 2 * height; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Inflates this <see cref="Rectangle"/> by the specified amount.
|
|||
/// </summary>
|
|||
/// <param name="size">The size.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public void Inflate(Size size) => this.Inflate(size.Width, size.Height); |
|||
|
|||
/// <summary>
|
|||
/// Determines if the specfied point is contained within the rectangular region defined by
|
|||
/// this <see cref="Rectangle"/>.
|
|||
/// </summary>
|
|||
/// <param name="x">The x-coordinate of the given point.</param>
|
|||
/// <param name="y">The y-coordinate of the given point.</param>
|
|||
/// <returns>The <see cref="bool"/>.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public bool Contains(int x, int y) => this.X <= x && x < this.Right && this.Y <= y && y < this.Bottom; |
|||
|
|||
/// <summary>
|
|||
/// Determines if the specified point is contained within the rectangular region defined by this <see cref="Rectangle"/> .
|
|||
/// </summary>
|
|||
/// <param name="point">The point.</param>
|
|||
/// <returns>The <see cref="bool"/>.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public bool Contains(Point point) => this.Contains(point.X, point.Y); |
|||
|
|||
/// <summary>
|
|||
/// Determines if the rectangular region represented by <paramref name="rectangle"/> is entirely contained
|
|||
/// within the rectangular region represented by this <see cref="Rectangle"/> .
|
|||
/// </summary>
|
|||
/// <param name="rectangle">The rectangle.</param>
|
|||
/// <returns>The <see cref="bool"/>.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public bool Contains(Rectangle rectangle) => |
|||
(this.X <= rectangle.X) && (rectangle.Right <= this.Right) && |
|||
(this.Y <= rectangle.Y) && (rectangle.Bottom <= this.Bottom); |
|||
|
|||
/// <summary>
|
|||
/// Determines if the specfied <see cref="Rectangle"/> intersects the rectangular region defined by
|
|||
/// this <see cref="Rectangle"/>.
|
|||
/// </summary>
|
|||
/// <param name="rectangle">The other Rectange. </param>
|
|||
/// <returns>The <see cref="bool"/>.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public bool IntersectsWith(Rectangle rectangle) => |
|||
(rectangle.X < this.Right) && (this.X < rectangle.Right) && |
|||
(rectangle.Y < this.Bottom) && (this.Y < rectangle.Bottom); |
|||
|
|||
/// <summary>
|
|||
/// Adjusts the location of this rectangle by the specified amount.
|
|||
/// </summary>
|
|||
/// <param name="point">The point.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public void Offset(Point point) => this.Offset(point.X, point.Y); |
|||
|
|||
/// <summary>
|
|||
/// Adjusts the location of this rectangle by the specified amount.
|
|||
/// </summary>
|
|||
/// <param name="dx">The amount to offset the x-coordinate.</param>
|
|||
/// <param name="dy">The amount to offset the y-coordinate.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public void Offset(int dx, int dy) |
|||
{ |
|||
unchecked |
|||
{ |
|||
this.X += dx; |
|||
this.Y += dy; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override int GetHashCode() |
|||
{ |
|||
return HashCode.Combine(this.X, this.Y, this.Width, this.Height); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override string ToString() |
|||
{ |
|||
return $"Rectangle [ X={this.X}, Y={this.Y}, Width={this.Width}, Height={this.Height} ]"; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool Equals(object obj) => obj is Rectangle other && this.Equals(other); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public bool Equals(Rectangle other) => |
|||
this.X.Equals(other.X) && |
|||
this.Y.Equals(other.Y) && |
|||
this.Width.Equals(other.Width) && |
|||
this.Height.Equals(other.Height); |
|||
} |
|||
} |
|||
@ -0,0 +1,396 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.ComponentModel; |
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.Primitives |
|||
{ |
|||
/// <summary>
|
|||
/// Stores a set of four single precision floating points that represent the location and size of a rectangle.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This struct is fully mutable. This is done (against the guidelines) for the sake of performance,
|
|||
/// as it avoids the need to create new values for modification operations.
|
|||
/// </remarks>
|
|||
public struct RectangleF : IEquatable<RectangleF> |
|||
{ |
|||
/// <summary>
|
|||
/// Represents a <see cref="RectangleF"/> that has X, Y, Width, and Height values set to zero.
|
|||
/// </summary>
|
|||
public static readonly RectangleF Empty = default; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="RectangleF"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="x">The horizontal position of the rectangle.</param>
|
|||
/// <param name="y">The vertical position of the rectangle.</param>
|
|||
/// <param name="width">The width of the rectangle.</param>
|
|||
/// <param name="height">The height of the rectangle.</param>
|
|||
public RectangleF(float x, float y, float width, float height) |
|||
{ |
|||
this.X = x; |
|||
this.Y = y; |
|||
this.Width = width; |
|||
this.Height = height; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="RectangleF"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="point">
|
|||
/// The <see cref="Point"/> which specifies the rectangles point in a two-dimensional plane.
|
|||
/// </param>
|
|||
/// <param name="size">
|
|||
/// The <see cref="Size"/> which specifies the rectangles height and width.
|
|||
/// </param>
|
|||
public RectangleF(PointF point, SizeF size) |
|||
{ |
|||
this.X = point.X; |
|||
this.Y = point.Y; |
|||
this.Width = size.Width; |
|||
this.Height = size.Height; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the x-coordinate of this <see cref="RectangleF"/>.
|
|||
/// </summary>
|
|||
public float X { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the y-coordinate of this <see cref="RectangleF"/>.
|
|||
/// </summary>
|
|||
public float Y { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the width of this <see cref="RectangleF"/>.
|
|||
/// </summary>
|
|||
public float Width { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the height of this <see cref="RectangleF"/>.
|
|||
/// </summary>
|
|||
public float Height { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the coordinates of the upper-left corner of the rectangular region represented by this <see cref="RectangleF"/>.
|
|||
/// </summary>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public PointF Location |
|||
{ |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
get => new PointF(this.X, this.Y); |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
set |
|||
{ |
|||
this.X = value.X; |
|||
this.Y = value.Y; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the size of this <see cref="RectangleF"/>.
|
|||
/// </summary>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public SizeF Size |
|||
{ |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
get => new SizeF(this.Width, this.Height); |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
set |
|||
{ |
|||
this.Width = value.Width; |
|||
this.Height = value.Height; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether this <see cref="RectangleF"/> is empty.
|
|||
/// </summary>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public bool IsEmpty => (this.Width <= 0) || (this.Height <= 0); |
|||
|
|||
/// <summary>
|
|||
/// Gets the y-coordinate of the top edge of this <see cref="RectangleF"/>.
|
|||
/// </summary>
|
|||
public float Top => this.Y; |
|||
|
|||
/// <summary>
|
|||
/// Gets the x-coordinate of the right edge of this <see cref="RectangleF"/>.
|
|||
/// </summary>
|
|||
public float Right |
|||
{ |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
get => this.X + this.Width; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the y-coordinate of the bottom edge of this <see cref="RectangleF"/>.
|
|||
/// </summary>
|
|||
public float Bottom |
|||
{ |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
get => this.Y + this.Height; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the x-coordinate of the left edge of this <see cref="RectangleF"/>.
|
|||
/// </summary>
|
|||
public float Left => this.X; |
|||
|
|||
/// <summary>
|
|||
/// Creates a <see cref="Rectangle"/> with the coordinates of the specified <see cref="RectangleF"/> by truncating each coordinate.
|
|||
/// </summary>
|
|||
/// <param name="rectangle">The rectangle.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static explicit operator Rectangle(RectangleF rectangle) => Rectangle.Truncate(rectangle); |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="RectangleF"/> objects for equality.
|
|||
/// </summary>
|
|||
/// <param name="left">The <see cref="RectangleF"/> on the left side of the operand.</param>
|
|||
/// <param name="right">The <see cref="RectangleF"/> on the right side of the operand.</param>
|
|||
/// <returns>
|
|||
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static bool operator ==(RectangleF left, RectangleF right) => left.Equals(right); |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="RectangleF"/> objects for inequality.
|
|||
/// </summary>
|
|||
/// <param name="left">The <see cref="RectangleF"/> on the left side of the operand.</param>
|
|||
/// <param name="right">The <see cref="RectangleF"/> on the right side of the operand.</param>
|
|||
/// <returns>
|
|||
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static bool operator !=(RectangleF left, RectangleF right) => !left.Equals(right); |
|||
|
|||
/// <summary>
|
|||
/// Creates a new <see cref="RectangleF"/> with the specified location and size. </summary>
|
|||
/// <param name="left">The left coordinate of the rectangle.</param>
|
|||
/// <param name="top">The top coordinate of the rectangle.</param>
|
|||
/// <param name="right">The right coordinate of the rectangle.</param>
|
|||
/// <param name="bottom">The bottom coordinate of the rectangle.</param>
|
|||
/// <returns>The <see cref="RectangleF"/>.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
|
|||
// ReSharper disable once InconsistentNaming
|
|||
public static RectangleF FromLTRB(float left, float top, float right, float bottom) => new RectangleF(left, top, right - left, bottom - top); |
|||
|
|||
/// <summary>
|
|||
/// Returns the center point of the given <see cref="RectangleF"/>.
|
|||
/// </summary>
|
|||
/// <param name="rectangle">The rectangle.</param>
|
|||
/// <returns>The <see cref="Point"/>.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static PointF Center(RectangleF rectangle) => new PointF(rectangle.Left + (rectangle.Width / 2), rectangle.Top + (rectangle.Height / 2)); |
|||
|
|||
/// <summary>
|
|||
/// Creates a rectangle that represents the intersection between <paramref name="a"/> and
|
|||
/// <paramref name="b"/>. If there is no intersection, an empty rectangle is returned.
|
|||
/// </summary>
|
|||
/// <param name="a">The first rectangle.</param>
|
|||
/// <param name="b">The second rectangle.</param>
|
|||
/// <returns>The <see cref="RectangleF"/>.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static RectangleF Intersect(RectangleF a, RectangleF b) |
|||
{ |
|||
float x1 = MathF.Max(a.X, b.X); |
|||
float x2 = MathF.Min(a.Right, b.Right); |
|||
float y1 = MathF.Max(a.Y, b.Y); |
|||
float y2 = MathF.Min(a.Bottom, b.Bottom); |
|||
|
|||
if (x2 >= x1 && y2 >= y1) |
|||
{ |
|||
return new RectangleF(x1, y1, x2 - x1, y2 - y1); |
|||
} |
|||
|
|||
return Empty; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates a <see cref="RectangleF"/> that is inflated by the specified amount.
|
|||
/// </summary>
|
|||
/// <param name="rectangle">The rectangle.</param>
|
|||
/// <param name="x">The amount to inflate the width by.</param>
|
|||
/// <param name="y">The amount to inflate the height by.</param>
|
|||
/// <returns>A new <see cref="RectangleF"/>.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static RectangleF Inflate(RectangleF rectangle, float x, float y) |
|||
{ |
|||
RectangleF r = rectangle; |
|||
r.Inflate(x, y); |
|||
return r; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Transforms a rectangle by the given matrix.
|
|||
/// </summary>
|
|||
/// <param name="rectangle">The source rectangle.</param>
|
|||
/// <param name="matrix">The transformation matrix.</param>
|
|||
/// <returns>A transformed <see cref="RectangleF"/>.</returns>
|
|||
public static RectangleF Transform(RectangleF rectangle, Matrix3x2 matrix) |
|||
{ |
|||
PointF bottomRight = PointF.Transform(new PointF(rectangle.Right, rectangle.Bottom), matrix); |
|||
PointF topLeft = PointF.Transform(rectangle.Location, matrix); |
|||
return new RectangleF(topLeft, new SizeF(bottomRight - topLeft)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates a rectangle that represents the union between <paramref name="a"/> and <paramref name="b"/>.
|
|||
/// </summary>
|
|||
/// <param name="a">The first rectangle.</param>
|
|||
/// <param name="b">The second rectangle.</param>
|
|||
/// <returns>The <see cref="RectangleF"/>.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static RectangleF Union(RectangleF a, RectangleF b) |
|||
{ |
|||
float x1 = MathF.Min(a.X, b.X); |
|||
float x2 = MathF.Max(a.Right, b.Right); |
|||
float y1 = MathF.Min(a.Y, b.Y); |
|||
float y2 = MathF.Max(a.Bottom, b.Bottom); |
|||
|
|||
return new RectangleF(x1, y1, x2 - x1, y2 - y1); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Deconstructs this rectangle into four floats.
|
|||
/// </summary>
|
|||
/// <param name="x">The out value for X.</param>
|
|||
/// <param name="y">The out value for Y.</param>
|
|||
/// <param name="width">The out value for the width.</param>
|
|||
/// <param name="height">The out value for the height.</param>
|
|||
public void Deconstruct(out float x, out float y, out float width, out float height) |
|||
{ |
|||
x = this.X; |
|||
y = this.Y; |
|||
width = this.Width; |
|||
height = this.Height; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates a RectangleF that represents the intersection between this RectangleF and the <paramref name="rectangle"/>.
|
|||
/// </summary>
|
|||
/// <param name="rectangle">The rectangle.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public void Intersect(RectangleF rectangle) |
|||
{ |
|||
RectangleF result = Intersect(rectangle, this); |
|||
|
|||
this.X = result.X; |
|||
this.Y = result.Y; |
|||
this.Width = result.Width; |
|||
this.Height = result.Height; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Inflates this <see cref="RectangleF"/> by the specified amount.
|
|||
/// </summary>
|
|||
/// <param name="width">The width.</param>
|
|||
/// <param name="height">The height.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public void Inflate(float width, float height) |
|||
{ |
|||
this.X -= width; |
|||
this.Y -= height; |
|||
|
|||
this.Width += 2 * width; |
|||
this.Height += 2 * height; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Inflates this <see cref="RectangleF"/> by the specified amount.
|
|||
/// </summary>
|
|||
/// <param name="size">The size.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public void Inflate(SizeF size) => this.Inflate(size.Width, size.Height); |
|||
|
|||
/// <summary>
|
|||
/// Determines if the specfied point is contained within the rectangular region defined by
|
|||
/// this <see cref="RectangleF"/>.
|
|||
/// </summary>
|
|||
/// <param name="x">The x-coordinate of the given point.</param>
|
|||
/// <param name="y">The y-coordinate of the given point.</param>
|
|||
/// <returns>The <see cref="bool"/>.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public bool Contains(float x, float y) => this.X <= x && x < this.Right && this.Y <= y && y < this.Bottom; |
|||
|
|||
/// <summary>
|
|||
/// Determines if the specified point is contained within the rectangular region defined by this <see cref="RectangleF"/> .
|
|||
/// </summary>
|
|||
/// <param name="point">The point.</param>
|
|||
/// <returns>The <see cref="bool"/>.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public bool Contains(PointF point) => this.Contains(point.X, point.Y); |
|||
|
|||
/// <summary>
|
|||
/// Determines if the rectangular region represented by <paramref name="rectangle"/> is entirely contained
|
|||
/// within the rectangular region represented by this <see cref="RectangleF"/> .
|
|||
/// </summary>
|
|||
/// <param name="rectangle">The rectangle.</param>
|
|||
/// <returns>The <see cref="bool"/>.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public bool Contains(RectangleF rectangle) => |
|||
(this.X <= rectangle.X) && (rectangle.Right <= this.Right) && |
|||
(this.Y <= rectangle.Y) && (rectangle.Bottom <= this.Bottom); |
|||
|
|||
/// <summary>
|
|||
/// Determines if the specfied <see cref="RectangleF"/> intersects the rectangular region defined by
|
|||
/// this <see cref="RectangleF"/>.
|
|||
/// </summary>
|
|||
/// <param name="rectangle">The other Rectange. </param>
|
|||
/// <returns>The <see cref="bool"/>.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public bool IntersectsWith(RectangleF rectangle) => |
|||
(rectangle.X < this.Right) && (this.X < rectangle.Right) && |
|||
(rectangle.Y < this.Bottom) && (this.Y < rectangle.Bottom); |
|||
|
|||
/// <summary>
|
|||
/// Adjusts the location of this rectangle by the specified amount.
|
|||
/// </summary>
|
|||
/// <param name="point">The point.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public void Offset(PointF point) => this.Offset(point.X, point.Y); |
|||
|
|||
/// <summary>
|
|||
/// Adjusts the location of this rectangle by the specified amount.
|
|||
/// </summary>
|
|||
/// <param name="dx">The amount to offset the x-coordinate.</param>
|
|||
/// <param name="dy">The amount to offset the y-coordinate.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public void Offset(float dx, float dy) |
|||
{ |
|||
this.X += dx; |
|||
this.Y += dy; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override int GetHashCode() |
|||
{ |
|||
return HashCode.Combine(this.X, this.Y, this.Width, this.Height); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override string ToString() |
|||
{ |
|||
return $"RectangleF [ X={this.X}, Y={this.Y}, Width={this.Width}, Height={this.Height} ]"; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool Equals(object obj) => obj is RectangleF other && this.Equals(other); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public bool Equals(RectangleF other) => |
|||
this.X.Equals(other.X) && |
|||
this.Y.Equals(other.Y) && |
|||
this.Width.Equals(other.Width) && |
|||
this.Height.Equals(other.Height); |
|||
} |
|||
} |
|||
@ -0,0 +1,296 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.ComponentModel; |
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.Primitives |
|||
{ |
|||
/// <summary>
|
|||
/// Stores an ordered pair of integers, which specify a height and width.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This struct is fully mutable. This is done (against the guidelines) for the sake of performance,
|
|||
/// as it avoids the need to create new values for modification operations.
|
|||
/// </remarks>
|
|||
public struct Size : IEquatable<Size> |
|||
{ |
|||
/// <summary>
|
|||
/// Represents a <see cref="Size"/> that has Width and Height values set to zero.
|
|||
/// </summary>
|
|||
public static readonly Size Empty = default; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Size"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="value">The width and height of the size.</param>
|
|||
public Size(int value) |
|||
: this() |
|||
{ |
|||
this.Width = value; |
|||
this.Height = value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Size"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="width">The width of the size.</param>
|
|||
/// <param name="height">The height of the size.</param>
|
|||
public Size(int width, int height) |
|||
{ |
|||
this.Width = width; |
|||
this.Height = height; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Size"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="size">The size.</param>
|
|||
public Size(Size size) |
|||
: this() |
|||
{ |
|||
this.Width = size.Width; |
|||
this.Height = size.Height; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Size"/> struct from the given <see cref="Point"/>.
|
|||
/// </summary>
|
|||
/// <param name="point">The point.</param>
|
|||
public Size(Point point) |
|||
{ |
|||
this.Width = point.X; |
|||
this.Height = point.Y; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the width of this <see cref="Size"/>.
|
|||
/// </summary>
|
|||
public int Width { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the height of this <see cref="Size"/>.
|
|||
/// </summary>
|
|||
public int Height { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether this <see cref="Size"/> is empty.
|
|||
/// </summary>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public bool IsEmpty => this.Equals(Empty); |
|||
|
|||
/// <summary>
|
|||
/// Creates a <see cref="SizeF"/> with the dimensions of the specified <see cref="Size"/>.
|
|||
/// </summary>
|
|||
/// <param name="size">The point.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static implicit operator SizeF(Size size) => new SizeF(size.Width, size.Height); |
|||
|
|||
/// <summary>
|
|||
/// Converts the given <see cref="Size"/> into a <see cref="Point"/>.
|
|||
/// </summary>
|
|||
/// <param name="size">The size.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static explicit operator Point(Size size) => new Point(size.Width, size.Height); |
|||
|
|||
/// <summary>
|
|||
/// Computes the sum of adding two sizes.
|
|||
/// </summary>
|
|||
/// <param name="left">The size on the left hand of the operand.</param>
|
|||
/// <param name="right">The size on the right hand of the operand.</param>
|
|||
/// <returns>
|
|||
/// The <see cref="Size"/>.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Size operator +(Size left, Size right) => Add(left, right); |
|||
|
|||
/// <summary>
|
|||
/// Computes the difference left by subtracting one size from another.
|
|||
/// </summary>
|
|||
/// <param name="left">The size on the left hand of the operand.</param>
|
|||
/// <param name="right">The size on the right hand of the operand.</param>
|
|||
/// <returns>
|
|||
/// The <see cref="Size"/>.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Size operator -(Size left, Size right) => Subtract(left, right); |
|||
|
|||
/// <summary>
|
|||
/// Multiplies a <see cref="Size"/> by an <see cref="int"/> producing <see cref="Size"/>.
|
|||
/// </summary>
|
|||
/// <param name="left">Multiplier of type <see cref="int"/>.</param>
|
|||
/// <param name="right">Multiplicand of type <see cref="Size"/>.</param>
|
|||
/// <returns>Product of type <see cref="Size"/>.</returns>
|
|||
public static Size operator *(int left, Size right) => Multiply(right, left); |
|||
|
|||
/// <summary>
|
|||
/// Multiplies <see cref="Size"/> by an <see cref="int"/> producing <see cref="Size"/>.
|
|||
/// </summary>
|
|||
/// <param name="left">Multiplicand of type <see cref="Size"/>.</param>
|
|||
/// <param name="right">Multiplier of type <see cref="int"/>.</param>
|
|||
/// <returns>Product of type <see cref="Size"/>.</returns>
|
|||
public static Size operator *(Size left, int right) => Multiply(left, right); |
|||
|
|||
/// <summary>
|
|||
/// Divides <see cref="Size"/> by an <see cref="int"/> producing <see cref="Size"/>.
|
|||
/// </summary>
|
|||
/// <param name="left">Dividend of type <see cref="Size"/>.</param>
|
|||
/// <param name="right">Divisor of type <see cref="int"/>.</param>
|
|||
/// <returns>Result of type <see cref="Size"/>.</returns>
|
|||
public static Size operator /(Size left, int right) => new Size(unchecked(left.Width / right), unchecked(left.Height / right)); |
|||
|
|||
/// <summary>
|
|||
/// Multiplies <see cref="Size"/> by a <see cref="float"/> producing <see cref="SizeF"/>.
|
|||
/// </summary>
|
|||
/// <param name="left">Multiplier of type <see cref="float"/>.</param>
|
|||
/// <param name="right">Multiplicand of type <see cref="Size"/>.</param>
|
|||
/// <returns>Product of type <see cref="SizeF"/>.</returns>
|
|||
public static SizeF operator *(float left, Size right) => Multiply(right, left); |
|||
|
|||
/// <summary>
|
|||
/// Multiplies <see cref="Size"/> by a <see cref="float"/> producing <see cref="SizeF"/>.
|
|||
/// </summary>
|
|||
/// <param name="left">Multiplicand of type <see cref="Size"/>.</param>
|
|||
/// <param name="right">Multiplier of type <see cref="float"/>.</param>
|
|||
/// <returns>Product of type <see cref="SizeF"/>.</returns>
|
|||
public static SizeF operator *(Size left, float right) => Multiply(left, right); |
|||
|
|||
/// <summary>
|
|||
/// Divides <see cref="Size"/> by a <see cref="float"/> producing <see cref="SizeF"/>.
|
|||
/// </summary>
|
|||
/// <param name="left">Dividend of type <see cref="Size"/>.</param>
|
|||
/// <param name="right">Divisor of type <see cref="int"/>.</param>
|
|||
/// <returns>Result of type <see cref="SizeF"/>.</returns>
|
|||
public static SizeF operator /(Size left, float right) |
|||
=> new SizeF(left.Width / right, left.Height / right); |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="Size"/> objects for equality.
|
|||
/// </summary>
|
|||
/// <param name="left">
|
|||
/// The <see cref="Size"/> on the left side of the operand.
|
|||
/// </param>
|
|||
/// <param name="right">
|
|||
/// The <see cref="Size"/> on the right side of the operand.
|
|||
/// </param>
|
|||
/// <returns>
|
|||
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static bool operator ==(Size left, Size right) => left.Equals(right); |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="Size"/> objects for inequality.
|
|||
/// </summary>
|
|||
/// <param name="left">
|
|||
/// The <see cref="Size"/> on the left side of the operand.
|
|||
/// </param>
|
|||
/// <param name="right">
|
|||
/// The <see cref="Size"/> on the right side of the operand.
|
|||
/// </param>
|
|||
/// <returns>
|
|||
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static bool operator !=(Size left, Size right) => !left.Equals(right); |
|||
|
|||
/// <summary>
|
|||
/// Performs vector addition of two <see cref="Size"/> objects.
|
|||
/// </summary>
|
|||
/// <param name="left">The size on the left hand of the operand.</param>
|
|||
/// <param name="right">The size on the right hand of the operand.</param>
|
|||
/// <returns>The <see cref="Size"/>.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Size Add(Size left, Size right) => new Size(unchecked(left.Width + right.Width), unchecked(left.Height + right.Height)); |
|||
|
|||
/// <summary>
|
|||
/// Contracts a <see cref="Size"/> by another <see cref="Size"/>.
|
|||
/// </summary>
|
|||
/// <param name="left">The size on the left hand of the operand.</param>
|
|||
/// <param name="right">The size on the right hand of the operand.</param>
|
|||
/// <returns>The <see cref="Size"/>.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Size Subtract(Size left, Size right) => new Size(unchecked(left.Width - right.Width), unchecked(left.Height - right.Height)); |
|||
|
|||
/// <summary>
|
|||
/// Converts a <see cref="SizeF"/> to a <see cref="Size"/> by performing a ceiling operation on all the dimensions.
|
|||
/// </summary>
|
|||
/// <param name="size">The size.</param>
|
|||
/// <returns>The <see cref="Size"/>.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Size Ceiling(SizeF size) => new Size(unchecked((int)MathF.Ceiling(size.Width)), unchecked((int)MathF.Ceiling(size.Height))); |
|||
|
|||
/// <summary>
|
|||
/// Converts a <see cref="SizeF"/> to a <see cref="Size"/> by performing a round operation on all the dimensions.
|
|||
/// </summary>
|
|||
/// <param name="size">The size.</param>
|
|||
/// <returns>The <see cref="Size"/>.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Size Round(SizeF size) => new Size(unchecked((int)MathF.Round(size.Width)), unchecked((int)MathF.Round(size.Height))); |
|||
|
|||
/// <summary>
|
|||
/// Transforms a size by the given matrix.
|
|||
/// </summary>
|
|||
/// <param name="size">The source size.</param>
|
|||
/// <param name="matrix">The transformation matrix.</param>
|
|||
/// <returns>A transformed size.</returns>
|
|||
public static SizeF Transform(Size size, Matrix3x2 matrix) |
|||
{ |
|||
var v = Vector2.Transform(new Vector2(size.Width, size.Height), matrix); |
|||
|
|||
return new SizeF(v.X, v.Y); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a <see cref="SizeF"/> to a <see cref="Size"/> by performing a round operation on all the dimensions.
|
|||
/// </summary>
|
|||
/// <param name="size">The size.</param>
|
|||
/// <returns>The <see cref="Size"/>.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Size Truncate(SizeF size) => new Size(unchecked((int)size.Width), unchecked((int)size.Height)); |
|||
|
|||
/// <summary>
|
|||
/// Deconstructs this size into two integers.
|
|||
/// </summary>
|
|||
/// <param name="width">The out value for the width.</param>
|
|||
/// <param name="height">The out value for the height.</param>
|
|||
public void Deconstruct(out int width, out int height) |
|||
{ |
|||
width = this.Width; |
|||
height = this.Height; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override int GetHashCode() => HashCode.Combine(this.Width, this.Height); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override string ToString() => $"Size [ Width={this.Width}, Height={this.Height} ]"; |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool Equals(object obj) => obj is Size other && this.Equals(other); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public bool Equals(Size other) => this.Width.Equals(other.Width) && this.Height.Equals(other.Height); |
|||
|
|||
/// <summary>
|
|||
/// Multiplies <see cref="Size"/> by an <see cref="int"/> producing <see cref="Size"/>.
|
|||
/// </summary>
|
|||
/// <param name="size">Multiplicand of type <see cref="Size"/>.</param>
|
|||
/// <param name="multiplier">Multiplier of type <see cref="int"/>.</param>
|
|||
/// <returns>Product of type <see cref="Size"/>.</returns>
|
|||
private static Size Multiply(Size size, int multiplier) => |
|||
new Size(unchecked(size.Width * multiplier), unchecked(size.Height * multiplier)); |
|||
|
|||
/// <summary>
|
|||
/// Multiplies <see cref="Size"/> by a <see cref="float"/> producing <see cref="SizeF"/>.
|
|||
/// </summary>
|
|||
/// <param name="size">Multiplicand of type <see cref="Size"/>.</param>
|
|||
/// <param name="multiplier">Multiplier of type <see cref="float"/>.</param>
|
|||
/// <returns>Product of type SizeF.</returns>
|
|||
private static SizeF Multiply(Size size, float multiplier) => |
|||
new SizeF(size.Width * multiplier, size.Height * multiplier); |
|||
} |
|||
} |
|||
@ -0,0 +1,233 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.ComponentModel; |
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.Primitives |
|||
{ |
|||
/// <summary>
|
|||
/// Stores an ordered pair of single precision floating points, which specify a height and width.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This struct is fully mutable. This is done (against the guidelines) for the sake of performance,
|
|||
/// as it avoids the need to create new values for modification operations.
|
|||
/// </remarks>
|
|||
public struct SizeF : IEquatable<SizeF> |
|||
{ |
|||
/// <summary>
|
|||
/// Represents a <see cref="SizeF"/> that has Width and Height values set to zero.
|
|||
/// </summary>
|
|||
public static readonly SizeF Empty = default; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="SizeF"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="width">The width of the size.</param>
|
|||
/// <param name="height">The height of the size.</param>
|
|||
public SizeF(float width, float height) |
|||
{ |
|||
this.Width = width; |
|||
this.Height = height; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="SizeF"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="size">The size.</param>
|
|||
public SizeF(SizeF size) |
|||
: this() |
|||
{ |
|||
this.Width = size.Width; |
|||
this.Height = size.Height; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="SizeF"/> struct from the given <see cref="PointF"/>.
|
|||
/// </summary>
|
|||
/// <param name="point">The point.</param>
|
|||
public SizeF(PointF point) |
|||
{ |
|||
this.Width = point.X; |
|||
this.Height = point.Y; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the width of this <see cref="SizeF"/>.
|
|||
/// </summary>
|
|||
public float Width { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the height of this <see cref="SizeF"/>.
|
|||
/// </summary>
|
|||
public float Height { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether this <see cref="SizeF"/> is empty.
|
|||
/// </summary>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public bool IsEmpty => this.Equals(Empty); |
|||
|
|||
/// <summary>
|
|||
/// Creates a <see cref="Vector2"/> with the coordinates of the specified <see cref="PointF"/>.
|
|||
/// </summary>
|
|||
/// <param name="point">The point.</param>
|
|||
/// <returns>
|
|||
/// The <see cref="Vector2"/>.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static implicit operator Vector2(SizeF point) => new Vector2(point.Width, point.Height); |
|||
|
|||
/// <summary>
|
|||
/// Creates a <see cref="Size"/> with the dimensions of the specified <see cref="SizeF"/> by truncating each of the dimensions.
|
|||
/// </summary>
|
|||
/// <param name="size">The size.</param>
|
|||
/// <returns>
|
|||
/// The <see cref="Size"/>.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static explicit operator Size(SizeF size) => new Size(unchecked((int)size.Width), unchecked((int)size.Height)); |
|||
|
|||
/// <summary>
|
|||
/// Converts the given <see cref="SizeF"/> into a <see cref="PointF"/>.
|
|||
/// </summary>
|
|||
/// <param name="size">The size.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static explicit operator PointF(SizeF size) => new PointF(size.Width, size.Height); |
|||
|
|||
/// <summary>
|
|||
/// Computes the sum of adding two sizes.
|
|||
/// </summary>
|
|||
/// <param name="left">The size on the left hand of the operand.</param>
|
|||
/// <param name="right">The size on the right hand of the operand.</param>
|
|||
/// <returns>
|
|||
/// The <see cref="SizeF"/>.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static SizeF operator +(SizeF left, SizeF right) => Add(left, right); |
|||
|
|||
/// <summary>
|
|||
/// Computes the difference left by subtracting one size from another.
|
|||
/// </summary>
|
|||
/// <param name="left">The size on the left hand of the operand.</param>
|
|||
/// <param name="right">The size on the right hand of the operand.</param>
|
|||
/// <returns>
|
|||
/// The <see cref="SizeF"/>.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static SizeF operator -(SizeF left, SizeF right) => Subtract(left, right); |
|||
|
|||
/// <summary>
|
|||
/// Multiplies <see cref="SizeF"/> by a <see cref="float"/> producing <see cref="SizeF"/>.
|
|||
/// </summary>
|
|||
/// <param name="left">Multiplier of type <see cref="float"/>.</param>
|
|||
/// <param name="right">Multiplicand of type <see cref="SizeF"/>.</param>
|
|||
/// <returns>Product of type <see cref="SizeF"/>.</returns>
|
|||
public static SizeF operator *(float left, SizeF right) => Multiply(right, left); |
|||
|
|||
/// <summary>
|
|||
/// Multiplies <see cref="SizeF"/> by a <see cref="float"/> producing <see cref="SizeF"/>.
|
|||
/// </summary>
|
|||
/// <param name="left">Multiplicand of type <see cref="SizeF"/>.</param>
|
|||
/// <param name="right">Multiplier of type <see cref="float"/>.</param>
|
|||
/// <returns>Product of type <see cref="SizeF"/>.</returns>
|
|||
public static SizeF operator *(SizeF left, float right) => Multiply(left, right); |
|||
|
|||
/// <summary>
|
|||
/// Divides <see cref="SizeF"/> by a <see cref="float"/> producing <see cref="SizeF"/>.
|
|||
/// </summary>
|
|||
/// <param name="left">Dividend of type <see cref="SizeF"/>.</param>
|
|||
/// <param name="right">Divisor of type <see cref="int"/>.</param>
|
|||
/// <returns>Result of type <see cref="SizeF"/>.</returns>
|
|||
public static SizeF operator /(SizeF left, float right) |
|||
=> new SizeF(left.Width / right, left.Height / right); |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="SizeF"/> objects for equality.
|
|||
/// </summary>
|
|||
/// <param name="left">The size on the left hand of the operand.</param>
|
|||
/// <param name="right">The size on the right hand of the operand.</param>
|
|||
/// <returns>
|
|||
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static bool operator ==(SizeF left, SizeF right) => left.Equals(right); |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="SizeF"/> objects for inequality.
|
|||
/// </summary>
|
|||
/// <param name="left">The size on the left hand of the operand.</param>
|
|||
/// <param name="right">The size on the right hand of the operand.</param>
|
|||
/// <returns>
|
|||
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static bool operator !=(SizeF left, SizeF right) => !left.Equals(right); |
|||
|
|||
/// <summary>
|
|||
/// Performs vector addition of two <see cref="SizeF"/> objects.
|
|||
/// </summary>
|
|||
/// <param name="left">The size on the left hand of the operand.</param>
|
|||
/// <param name="right">The size on the right hand of the operand.</param>
|
|||
/// <returns>The <see cref="SizeF"/>.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static SizeF Add(SizeF left, SizeF right) => new SizeF(left.Width + right.Width, left.Height + right.Height); |
|||
|
|||
/// <summary>
|
|||
/// Contracts a <see cref="SizeF"/> by another <see cref="SizeF"/>.
|
|||
/// </summary>
|
|||
/// <param name="left">The size on the left hand of the operand.</param>
|
|||
/// <param name="right">The size on the right hand of the operand.</param>
|
|||
/// <returns>The <see cref="SizeF"/>.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static SizeF Subtract(SizeF left, SizeF right) => new SizeF(left.Width - right.Width, left.Height - right.Height); |
|||
|
|||
/// <summary>
|
|||
/// Transforms a size by the given matrix.
|
|||
/// </summary>
|
|||
/// <param name="size">The source size.</param>
|
|||
/// <param name="matrix">The transformation matrix.</param>
|
|||
/// <returns>A transformed size.</returns>
|
|||
public static SizeF Transform(SizeF size, Matrix3x2 matrix) |
|||
{ |
|||
var v = Vector2.Transform(new Vector2(size.Width, size.Height), matrix); |
|||
|
|||
return new SizeF(v.X, v.Y); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Deconstructs this size into two floats.
|
|||
/// </summary>
|
|||
/// <param name="width">The out value for the width.</param>
|
|||
/// <param name="height">The out value for the height.</param>
|
|||
public void Deconstruct(out float width, out float height) |
|||
{ |
|||
width = this.Width; |
|||
height = this.Height; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override int GetHashCode() => HashCode.Combine(this.Width, this.Height); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override string ToString() => $"SizeF [ Width={this.Width}, Height={this.Height} ]"; |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool Equals(object obj) => obj is SizeF && this.Equals((SizeF)obj); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public bool Equals(SizeF other) => this.Width.Equals(other.Width) && this.Height.Equals(other.Height); |
|||
|
|||
/// <summary>
|
|||
/// Multiplies <see cref="SizeF"/> by a <see cref="float"/> producing <see cref="SizeF"/>.
|
|||
/// </summary>
|
|||
/// <param name="size">Multiplicand of type <see cref="SizeF"/>.</param>
|
|||
/// <param name="multiplier">Multiplier of type <see cref="float"/>.</param>
|
|||
/// <returns>Product of type SizeF.</returns>
|
|||
private static SizeF Multiply(SizeF size, float multiplier) => |
|||
new SizeF(size.Width * multiplier, size.Height * multiplier); |
|||
} |
|||
} |
|||
@ -0,0 +1,234 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
// tell this file to enable debug conditional method calls, i.e. all the debug guard calls
|
|||
#define DEBUG
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Diagnostics; |
|||
using System.Linq; |
|||
using System.Reflection; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.Helpers.Tests |
|||
{ |
|||
public class DebugGuardTests |
|||
{ |
|||
private class Foo |
|||
{ |
|||
} |
|||
|
|||
[Fact] |
|||
public void AllStaticMethodsOnOnDebugGuardHaveDEBUGConditional() |
|||
{ |
|||
IEnumerable<MethodInfo> methods = typeof(DebugGuard).GetTypeInfo().GetMethods() |
|||
.Where(x => x.IsStatic); |
|||
|
|||
foreach (MethodInfo m in methods) |
|||
{ |
|||
IEnumerable<ConditionalAttribute> attribs = m.GetCustomAttributes<ConditionalAttribute>(); |
|||
Assert.True(attribs.Select(x => x.ConditionString).Contains("DEBUG"), $"Method '{m.Name}' does not have [Conditional(\"DEBUG\")] set."); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void NotNull_WhenNull_Throws() |
|||
{ |
|||
Foo foo = null; |
|||
Assert.Throws<ArgumentNullException>(() => Guard.NotNull(foo, nameof(foo))); |
|||
} |
|||
|
|||
[Fact] |
|||
public void NotNull_WhenNotNull() |
|||
{ |
|||
var foo = new Foo(); |
|||
Guard.NotNull(foo, nameof(foo)); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(null, true)] |
|||
[InlineData("", true)] |
|||
[InlineData(" ", true)] |
|||
[InlineData("$", false)] |
|||
[InlineData("lol", false)] |
|||
public void NotNullOrWhiteSpace(string str, bool shouldThrow) |
|||
{ |
|||
if (shouldThrow) |
|||
{ |
|||
Assert.ThrowsAny<ArgumentException>(() => Guard.NotNullOrWhiteSpace(str, nameof(str))); |
|||
} |
|||
else |
|||
{ |
|||
Guard.NotNullOrWhiteSpace(str, nameof(str)); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(true)] |
|||
[InlineData(false)] |
|||
public void IsTrue(bool value) |
|||
{ |
|||
if (!value) |
|||
{ |
|||
Assert.Throws<ArgumentException>(() => Guard.IsTrue(value, nameof(value), "Boo!")); |
|||
} |
|||
else |
|||
{ |
|||
Guard.IsTrue(value, nameof(value), "Boo."); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(true)] |
|||
[InlineData(false)] |
|||
public void IsFalse(bool value) |
|||
{ |
|||
if (value) |
|||
{ |
|||
Assert.Throws<ArgumentException>(() => Guard.IsFalse(value, nameof(value), "Boo!")); |
|||
} |
|||
else |
|||
{ |
|||
Guard.IsFalse(value, nameof(value), "Boo."); |
|||
} |
|||
} |
|||
|
|||
public static readonly TheoryData<int, int, bool> SizeCheckData = new TheoryData<int, int, bool> |
|||
{ |
|||
{ 0, 0, false }, |
|||
{ 1, 1, false }, |
|||
{ 1, 0, false }, |
|||
{ 13, 13, false }, |
|||
{ 20, 13, false }, |
|||
{ 12, 13, true }, |
|||
{ 0, 1, true }, |
|||
}; |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(SizeCheckData))] |
|||
public void MustBeSizedAtLeast(int length, int minLength, bool shouldThrow) |
|||
{ |
|||
int[] data = new int[length]; |
|||
|
|||
if (shouldThrow) |
|||
{ |
|||
Assert.Throws<ArgumentException>(() => Guard.MustBeSizedAtLeast((Span<int>)data, minLength, nameof(data))); |
|||
Assert.Throws<ArgumentException>(() => Guard.MustBeSizedAtLeast((ReadOnlySpan<int>)data, minLength, nameof(data))); |
|||
} |
|||
else |
|||
{ |
|||
Guard.MustBeSizedAtLeast((Span<int>)data, minLength, nameof(data)); |
|||
Guard.MustBeSizedAtLeast((ReadOnlySpan<int>)data, minLength, nameof(data)); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(SizeCheckData))] |
|||
public void DestinationShouldNotBeTooShort(int destLength, int sourceLength, bool shouldThrow) |
|||
{ |
|||
int[] dest = new int[destLength]; |
|||
int[] source = new int[sourceLength]; |
|||
|
|||
if (shouldThrow) |
|||
{ |
|||
Assert.Throws<ArgumentException>(() => Guard.DestinationShouldNotBeTooShort((Span<int>)source, (Span<int>)dest, nameof(dest))); |
|||
Assert.Throws<ArgumentException>(() => Guard.DestinationShouldNotBeTooShort((ReadOnlySpan<int>)source, (Span<int>)dest, nameof(dest))); |
|||
} |
|||
else |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort((Span<int>)source, (Span<int>)dest, nameof(dest)); |
|||
Guard.DestinationShouldNotBeTooShort((ReadOnlySpan<int>)source, (Span<int>)dest, nameof(dest)); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void MustBeLessThan_IsLess_ThrowsNoException() |
|||
{ |
|||
DebugGuard.MustBeLessThan(0, 1, "myParamName"); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(2, 1)] |
|||
[InlineData(1, 1)] |
|||
public void MustBeLessThan_IsGreaterOrEqual_ThrowsNoException(int value, int max) |
|||
{ |
|||
ArgumentOutOfRangeException exception = Assert.Throws<ArgumentOutOfRangeException>( |
|||
() => DebugGuard.MustBeLessThan(value, max, "myParamName")); |
|||
|
|||
Assert.Equal("myParamName", exception.ParamName); |
|||
Assert.Contains($"Value {value} must be less than {max}.", exception.Message); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(0, 1)] |
|||
[InlineData(1, 1)] |
|||
public void MustBeLessThanOrEqualTo_IsLessOrEqual_ThrowsNoException(int value, int max) |
|||
{ |
|||
DebugGuard.MustBeLessThanOrEqualTo(value, max, "myParamName"); |
|||
} |
|||
|
|||
[Fact] |
|||
public void MustBeLessThanOrEqualTo_IsGreater_ThrowsNoException() |
|||
{ |
|||
ArgumentOutOfRangeException exception = Assert.Throws<ArgumentOutOfRangeException>(() => DebugGuard.MustBeLessThanOrEqualTo(2, 1, "myParamName")); |
|||
|
|||
Assert.Equal("myParamName", exception.ParamName); |
|||
Assert.Contains($"Value 2 must be less than or equal to 1.", exception.Message); |
|||
} |
|||
|
|||
[Fact] |
|||
public void MustBeGreaterThan_IsGreater_ThrowsNoException() |
|||
{ |
|||
DebugGuard.MustBeGreaterThan(2, 1, "myParamName"); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(1, 2)] |
|||
[InlineData(1, 1)] |
|||
public void MustBeGreaterThan_IsLessOrEqual_ThrowsNoException(int value, int min) |
|||
{ |
|||
ArgumentOutOfRangeException exception = Assert.Throws<ArgumentOutOfRangeException>( |
|||
() => DebugGuard.MustBeGreaterThan(value, min, "myParamName")); |
|||
|
|||
Assert.Equal("myParamName", exception.ParamName); |
|||
Assert.Contains($"Value {value} must be greater than {min}.", exception.Message); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(2, 1)] |
|||
[InlineData(1, 1)] |
|||
public void MustBeGreaterThanOrEqualTo_IsGreaterOrEqual_ThrowsNoException(int value, int min) |
|||
{ |
|||
DebugGuard.MustBeGreaterThanOrEqualTo(value, min, "myParamName"); |
|||
} |
|||
|
|||
[Fact] |
|||
public void MustBeGreaterThanOrEqualTo_IsLess_ThrowsNoException() |
|||
{ |
|||
ArgumentOutOfRangeException exception = Assert.Throws<ArgumentOutOfRangeException>( |
|||
() => DebugGuard.MustBeGreaterThanOrEqualTo(1, 2, "myParamName")); |
|||
|
|||
Assert.Equal("myParamName", exception.ParamName); |
|||
Assert.Contains($"Value 1 must be greater than or equal to 2.", exception.Message); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(new int[] { 1, 2 }, 1)] |
|||
[InlineData(new int[] { 1, 2 }, 2)] |
|||
public void MustBeSizedAtLeast_Array_LengthIsGreaterOrEqual_ThrowsNoException(int[] value, int minLength) |
|||
{ |
|||
DebugGuard.MustBeSizedAtLeast<int>(value, minLength, "myParamName"); |
|||
} |
|||
|
|||
[Fact] |
|||
public void MustBeSizedAtLeast_Array_LengthIsLess_ThrowsException() |
|||
{ |
|||
ArgumentException exception = Assert.Throws<ArgumentException>( |
|||
() => DebugGuard.MustBeSizedAtLeast<int>(new int[] { 1, 2 }, 3, "myParamName")); |
|||
|
|||
Assert.Equal("myParamName", exception.ParamName); |
|||
Assert.Contains($"The size must be at least 3.", exception.Message); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,59 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Numerics; |
|||
|
|||
namespace SixLabors.Tests.Helpers |
|||
{ |
|||
/// <summary>
|
|||
/// Allows the comparison of single-precision floating point values by precision.
|
|||
/// </summary>
|
|||
public struct FloatRoundingComparer : IEqualityComparer<float>, IEqualityComparer<Vector4> |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="FloatRoundingComparer"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="precision">The number of decimal places (valid values: 0-7).</param>
|
|||
public FloatRoundingComparer(int precision) |
|||
{ |
|||
Guard.MustBeBetweenOrEqualTo(precision, 0, 7, nameof(precision)); |
|||
this.Precision = precision; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of decimal places (valid values: 0-7).
|
|||
/// </summary>
|
|||
public int Precision { get; } |
|||
|
|||
/// <inheritdoc />
|
|||
public bool Equals(float x, float y) |
|||
{ |
|||
float xp = (float)Math.Round(x, this.Precision, MidpointRounding.AwayFromZero); |
|||
float yp = (float)Math.Round(y, this.Precision, MidpointRounding.AwayFromZero); |
|||
|
|||
return Comparer<float>.Default.Compare(xp, yp) == 0; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public bool Equals(Vector4 x, Vector4 y) |
|||
{ |
|||
return this.Equals(x.X, y.X) && this.Equals(x.Y, y.Y) && this.Equals(x.Z, y.Z) && this.Equals(x.W, y.W); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public int GetHashCode(float obj) |
|||
{ |
|||
unchecked |
|||
{ |
|||
int hashCode = obj.GetHashCode(); |
|||
hashCode = (hashCode * 397) ^ this.Precision.GetHashCode(); |
|||
return hashCode; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public int GetHashCode(Vector4 obj) => HashCode.Combine(obj, this.Precision); |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.Tests.Helpers |
|||
{ |
|||
public class GeometryUtilitiesTests |
|||
{ |
|||
[Fact] |
|||
public void Convert_Degree_To_Radian() |
|||
=> Assert.Equal((float)(Math.PI / 2D), GeometryUtilities.DegreeToRadian(90F), new FloatRoundingComparer(6)); |
|||
|
|||
[Fact] |
|||
public void Convert_Radian_To_Degree() |
|||
=> Assert.Equal(60F, GeometryUtilities.RadianToDegree((float)(Math.PI / 3D)), new FloatRoundingComparer(5)); |
|||
} |
|||
} |
|||
@ -0,0 +1,248 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.Helpers.Tests |
|||
{ |
|||
public class GuardTests |
|||
{ |
|||
private class Foo |
|||
{ |
|||
} |
|||
|
|||
[Fact] |
|||
public void NotNull_WhenNull_Throws() |
|||
{ |
|||
Foo foo = null; |
|||
Assert.Throws<ArgumentNullException>(() => Guard.NotNull(foo, nameof(foo))); |
|||
} |
|||
|
|||
[Fact] |
|||
public void NotNull_WhenNotNull() |
|||
{ |
|||
Foo foo = new Foo(); |
|||
Guard.NotNull(foo, nameof(foo)); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(null, true)] |
|||
[InlineData("", true)] |
|||
[InlineData(" ", true)] |
|||
[InlineData("$", false)] |
|||
[InlineData("lol", false)] |
|||
public void NotNullOrWhiteSpace(string str, bool shouldThrow) |
|||
{ |
|||
if (shouldThrow) |
|||
{ |
|||
Assert.ThrowsAny<ArgumentException>(() => Guard.NotNullOrWhiteSpace(str, nameof(str))); |
|||
} |
|||
else |
|||
{ |
|||
Guard.NotNullOrWhiteSpace(str, nameof(str)); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(true)] |
|||
[InlineData(false)] |
|||
public void IsTrue(bool value) |
|||
{ |
|||
if (!value) |
|||
{ |
|||
Assert.Throws<ArgumentException>(() => Guard.IsTrue(value, nameof(value), "Boo!")); |
|||
} |
|||
else |
|||
{ |
|||
Guard.IsTrue(value, nameof(value), "Boo."); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(true)] |
|||
[InlineData(false)] |
|||
public void IsFalse(bool value) |
|||
{ |
|||
if (value) |
|||
{ |
|||
Assert.Throws<ArgumentException>(() => Guard.IsFalse(value, nameof(value), "Boo!")); |
|||
} |
|||
else |
|||
{ |
|||
Guard.IsFalse(value, nameof(value), "Boo."); |
|||
} |
|||
} |
|||
|
|||
public static readonly TheoryData<int, int, bool> SizeCheckData = new TheoryData<int, int, bool> |
|||
{ |
|||
{ 0, 0, false }, |
|||
{ 1, 1, false }, |
|||
{ 1, 0, false }, |
|||
{ 13, 13, false }, |
|||
{ 20, 13, false }, |
|||
{ 12, 13, true }, |
|||
{ 0, 1, true }, |
|||
}; |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(SizeCheckData))] |
|||
public void MustBeSizedAtLeast(int length, int minLength, bool shouldThrow) |
|||
{ |
|||
int[] data = new int[length]; |
|||
|
|||
if (shouldThrow) |
|||
{ |
|||
Assert.Throws<ArgumentException>(() => Guard.MustBeSizedAtLeast((Span<int>)data, minLength, nameof(data))); |
|||
Assert.Throws<ArgumentException>(() => Guard.MustBeSizedAtLeast((ReadOnlySpan<int>)data, minLength, nameof(data))); |
|||
} |
|||
else |
|||
{ |
|||
Guard.MustBeSizedAtLeast((Span<int>)data, minLength, nameof(data)); |
|||
Guard.MustBeSizedAtLeast((ReadOnlySpan<int>)data, minLength, nameof(data)); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(SizeCheckData))] |
|||
public void DestinationShouldNotBeTooShort(int destLength, int sourceLength, bool shouldThrow) |
|||
{ |
|||
int[] dest = new int[destLength]; |
|||
int[] source = new int[sourceLength]; |
|||
|
|||
if (shouldThrow) |
|||
{ |
|||
Assert.Throws<ArgumentException>(() => Guard.DestinationShouldNotBeTooShort((Span<int>)source, (Span<int>)dest, nameof(dest))); |
|||
Assert.Throws<ArgumentException>(() => Guard.DestinationShouldNotBeTooShort((ReadOnlySpan<int>)source, (Span<int>)dest, nameof(dest))); |
|||
} |
|||
else |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort((Span<int>)source, (Span<int>)dest, nameof(dest)); |
|||
Guard.DestinationShouldNotBeTooShort((ReadOnlySpan<int>)source, (Span<int>)dest, nameof(dest)); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void MustBeLessThan_IsLess_ThrowsNoException() |
|||
{ |
|||
Guard.MustBeLessThan(0, 1, "myParamName"); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(2, 1)] |
|||
[InlineData(1, 1)] |
|||
public void MustBeLessThan_IsGreaterOrEqual_ThrowsNoException(int value, int max) |
|||
{ |
|||
ArgumentOutOfRangeException exception = Assert.Throws<ArgumentOutOfRangeException>(() => |
|||
{ |
|||
Guard.MustBeLessThan(value, max, "myParamName"); |
|||
}); |
|||
|
|||
Assert.Equal("myParamName", exception.ParamName); |
|||
Assert.Contains($"Value {value} must be less than {max}.", exception.Message); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(0, 1)] |
|||
[InlineData(1, 1)] |
|||
public void MustBeLessThanOrEqualTo_IsLessOrEqual_ThrowsNoException(int value, int max) |
|||
{ |
|||
Guard.MustBeLessThanOrEqualTo(value, max, "myParamName"); |
|||
} |
|||
|
|||
[Fact] |
|||
public void MustBeLessThanOrEqualTo_IsGreater_ThrowsNoException() |
|||
{ |
|||
ArgumentOutOfRangeException exception = Assert.Throws<ArgumentOutOfRangeException>(() => |
|||
{ |
|||
Guard.MustBeLessThanOrEqualTo(2, 1, "myParamName"); |
|||
}); |
|||
|
|||
Assert.Equal("myParamName", exception.ParamName); |
|||
Assert.Contains($"Value 2 must be less than or equal to 1.", exception.Message); |
|||
} |
|||
|
|||
[Fact] |
|||
public void MustBeGreaterThan_IsGreater_ThrowsNoException() |
|||
{ |
|||
Guard.MustBeGreaterThan(2, 1, "myParamName"); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(1, 2)] |
|||
[InlineData(1, 1)] |
|||
public void MustBeGreaterThan_IsLessOrEqual_ThrowsNoException(int value, int min) |
|||
{ |
|||
ArgumentOutOfRangeException exception = Assert.Throws<ArgumentOutOfRangeException>(() => |
|||
{ |
|||
Guard.MustBeGreaterThan(value, min, "myParamName"); |
|||
}); |
|||
|
|||
Assert.Equal("myParamName", exception.ParamName); |
|||
Assert.Contains($"Value {value} must be greater than {min}.", exception.Message); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(2, 1)] |
|||
[InlineData(1, 1)] |
|||
public void MustBeGreaterThanOrEqualTo_IsGreaterOrEqual_ThrowsNoException(int value, int min) |
|||
{ |
|||
Guard.MustBeGreaterThanOrEqualTo(value, min, "myParamName"); |
|||
} |
|||
|
|||
[Fact] |
|||
public void MustBeGreaterThanOrEqualTo_IsLess_ThrowsNoException() |
|||
{ |
|||
ArgumentOutOfRangeException exception = Assert.Throws<ArgumentOutOfRangeException>(() => |
|||
{ |
|||
Guard.MustBeGreaterThanOrEqualTo(1, 2, "myParamName"); |
|||
}); |
|||
|
|||
Assert.Equal("myParamName", exception.ParamName); |
|||
Assert.Contains($"Value 1 must be greater than or equal to 2.", exception.Message); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(1, 1, 3)] |
|||
[InlineData(2, 1, 3)] |
|||
[InlineData(3, 1, 3)] |
|||
public void MustBeBetweenOrEqualTo_IsBetweenOrEqual_ThrowsNoException(int value, int min, int max) |
|||
{ |
|||
Guard.MustBeBetweenOrEqualTo(value, min, max, "myParamName"); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(0, 1, 3)] |
|||
[InlineData(4, 1, 3)] |
|||
public void MustBeBetweenOrEqualTo_IsLessOrGreater_ThrowsNoException(int value, int min, int max) |
|||
{ |
|||
ArgumentOutOfRangeException exception = Assert.Throws<ArgumentOutOfRangeException>(() => |
|||
{ |
|||
Guard.MustBeBetweenOrEqualTo(value, min, max, "myParamName"); |
|||
}); |
|||
|
|||
Assert.Equal("myParamName", exception.ParamName); |
|||
Assert.Contains($"Value {value} must be greater than or equal to {min} and less than or equal to {max}.", exception.Message); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(2, 1)] |
|||
[InlineData(2, 2)] |
|||
public void MustBeSizedAtLeast_Array_LengthIsGreaterOrEqual_ThrowsNoException(int valueLength, int minLength) |
|||
{ |
|||
Guard.MustBeSizedAtLeast<int>(new int[valueLength], minLength, "myParamName"); |
|||
} |
|||
|
|||
[Fact] |
|||
public void MustBeSizedAtLeast_Array_LengthIsLess_ThrowsException() |
|||
{ |
|||
ArgumentException exception = Assert.Throws<ArgumentException>(() => |
|||
{ |
|||
Guard.MustBeSizedAtLeast<int>(new int[] { 1, 2 }, 3, "myParamName"); |
|||
}); |
|||
|
|||
Assert.Equal("myParamName", exception.ParamName); |
|||
Assert.Contains("The size must be at least 3", exception.Message); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,95 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.Tests.Helpers |
|||
{ |
|||
public class MathFTests |
|||
{ |
|||
[Fact] |
|||
public void MathF_PI_Is_Equal() |
|||
{ |
|||
Assert.Equal(MathF.PI, (float)Math.PI); |
|||
} |
|||
|
|||
[Fact] |
|||
public void MathF_Ceililng_Is_Equal() |
|||
{ |
|||
Assert.Equal(MathF.Ceiling(0.3333F), (float)Math.Ceiling(0.3333F)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void MathF_Cos_Is_Equal() |
|||
{ |
|||
Assert.Equal(MathF.Cos(0.3333F), (float)Math.Cos(0.3333F)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void MathF_Abs_Is_Equal() |
|||
{ |
|||
Assert.Equal(MathF.Abs(-0.3333F), (float)Math.Abs(-0.3333F)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void MathF_Atan2_Is_Equal() |
|||
{ |
|||
Assert.Equal(MathF.Atan2(1.2345F, 1.2345F), (float)Math.Atan2(1.2345F, 1.2345F)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void MathF_Exp_Is_Equal() |
|||
{ |
|||
Assert.Equal(MathF.Exp(1.2345F), (float)Math.Exp(1.2345F)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void MathF_Floor_Is_Equal() |
|||
{ |
|||
Assert.Equal(MathF.Floor(1.2345F), (float)Math.Floor(1.2345F)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void MathF_Min_Is_Equal() |
|||
{ |
|||
Assert.Equal(MathF.Min(1.2345F, 5.4321F), (float)Math.Min(1.2345F, 5.4321F)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void MathF_Max_Is_Equal() |
|||
{ |
|||
Assert.Equal(MathF.Max(1.2345F, 5.4321F), (float)Math.Max(1.2345F, 5.4321F)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void MathF_Pow_Is_Equal() |
|||
{ |
|||
Assert.Equal(MathF.Pow(1.2345F, 5.4321F), (float)Math.Pow(1.2345F, 5.4321F)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void MathF_Round_Is_Equal() |
|||
{ |
|||
Assert.Equal(MathF.Round(1.2345F), (float)Math.Round(1.2345F)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void MathF_Round_With_Midpoint_Is_Equal() |
|||
{ |
|||
Assert.Equal(MathF.Round(1.2345F, MidpointRounding.AwayFromZero), (float)Math.Round(1.2345F, MidpointRounding.AwayFromZero)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void MathF_Sin_Is_Equal() |
|||
{ |
|||
Assert.Equal(MathF.Sin(1.2345F), (float)Math.Sin(1.2345F)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void MathF_Sqrt_Is_Equal() |
|||
{ |
|||
Assert.Equal(MathF.Sqrt(2F), (float)Math.Sqrt(2F)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,257 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
// ReSharper disable InconsistentNaming
|
|||
using System; |
|||
using System.Buffers; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
using SixLabors.Tests; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.Memory.Tests |
|||
{ |
|||
public class ArrayPoolMemoryAllocatorTests |
|||
{ |
|||
private const int MaxPooledBufferSizeInBytes = 2048; |
|||
|
|||
private const int PoolSelectorThresholdInBytes = MaxPooledBufferSizeInBytes / 2; |
|||
|
|||
private MemoryAllocator MemoryAllocator { get; set; } = |
|||
new ArrayPoolMemoryAllocator(MaxPooledBufferSizeInBytes, PoolSelectorThresholdInBytes); |
|||
|
|||
/// <summary>
|
|||
/// Rent a buffer -> return it -> re-rent -> verify if it's span points to the previous location.
|
|||
/// </summary>
|
|||
private bool CheckIsRentingPooledBuffer<T>(int length) |
|||
where T : struct |
|||
{ |
|||
IMemoryOwner<T> buffer = this.MemoryAllocator.Allocate<T>(length); |
|||
ref T ptrToPrevPosition0 = ref buffer.GetReference(); |
|||
buffer.Dispose(); |
|||
|
|||
buffer = this.MemoryAllocator.Allocate<T>(length); |
|||
bool sameBuffers = Unsafe.AreSame(ref ptrToPrevPosition0, ref buffer.GetReference()); |
|||
buffer.Dispose(); |
|||
|
|||
return sameBuffers; |
|||
} |
|||
|
|||
public class BufferTests : BufferTestSuite |
|||
{ |
|||
public BufferTests() |
|||
: base(new ArrayPoolMemoryAllocator(MaxPooledBufferSizeInBytes, PoolSelectorThresholdInBytes)) |
|||
{ |
|||
} |
|||
} |
|||
|
|||
public class Constructor |
|||
{ |
|||
[Fact] |
|||
public void WhenBothParametersPassedByUser() |
|||
{ |
|||
var mgr = new ArrayPoolMemoryAllocator(1111, 666); |
|||
Assert.Equal(1111, mgr.MaxPoolSizeInBytes); |
|||
Assert.Equal(666, mgr.PoolSelectorThresholdInBytes); |
|||
} |
|||
|
|||
[Fact] |
|||
public void WhenPassedOnly_MaxPooledBufferSizeInBytes_SmallerThresholdValueIsAutoCalculated() |
|||
{ |
|||
var mgr = new ArrayPoolMemoryAllocator(5000); |
|||
Assert.Equal(5000, mgr.MaxPoolSizeInBytes); |
|||
Assert.True(mgr.PoolSelectorThresholdInBytes < mgr.MaxPoolSizeInBytes); |
|||
} |
|||
|
|||
[Fact] |
|||
public void When_PoolSelectorThresholdInBytes_IsGreaterThan_MaxPooledBufferSizeInBytes_ExceptionIsThrown() |
|||
{ |
|||
Assert.ThrowsAny<Exception>(() => new ArrayPoolMemoryAllocator(100, 200)); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(32)] |
|||
[InlineData(512)] |
|||
[InlineData(MaxPooledBufferSizeInBytes - 1)] |
|||
public void SmallBuffersArePooled_OfByte(int size) |
|||
{ |
|||
Assert.True(this.CheckIsRentingPooledBuffer<byte>(size)); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(128 * 1024 * 1024)] |
|||
[InlineData(MaxPooledBufferSizeInBytes + 1)] |
|||
public void LargeBuffersAreNotPooled_OfByte(int size) |
|||
{ |
|||
if (!TestEnvironment.Is64BitProcess) |
|||
{ |
|||
// can lead to OutOfMemoryException
|
|||
return; |
|||
} |
|||
|
|||
Assert.False(this.CheckIsRentingPooledBuffer<byte>(size)); |
|||
} |
|||
|
|||
[Fact] |
|||
public unsafe void SmallBuffersArePooled_OfBigValueType() |
|||
{ |
|||
int count = (MaxPooledBufferSizeInBytes / sizeof(LargeStruct)) - 1; |
|||
|
|||
Assert.True(this.CheckIsRentingPooledBuffer<LargeStruct>(count)); |
|||
} |
|||
|
|||
[Fact] |
|||
public unsafe void LaregeBuffersAreNotPooled_OfBigValueType() |
|||
{ |
|||
if (!TestEnvironment.Is64BitProcess) |
|||
{ |
|||
// can lead to OutOfMemoryException
|
|||
return; |
|||
} |
|||
|
|||
int count = (MaxPooledBufferSizeInBytes / sizeof(LargeStruct)) + 1; |
|||
|
|||
Assert.False(this.CheckIsRentingPooledBuffer<LargeStruct>(count)); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(AllocationOptions.None)] |
|||
[InlineData(AllocationOptions.Clean)] |
|||
public void CleaningRequests_AreControlledByAllocationParameter_Clean(AllocationOptions options) |
|||
{ |
|||
using (IMemoryOwner<int> firstAlloc = this.MemoryAllocator.Allocate<int>(42)) |
|||
{ |
|||
firstAlloc.GetSpan().Fill(666); |
|||
} |
|||
|
|||
using (IMemoryOwner<int> secondAlloc = this.MemoryAllocator.Allocate<int>(42, options)) |
|||
{ |
|||
int expected = options == AllocationOptions.Clean ? 0 : 666; |
|||
Assert.Equal(expected, secondAlloc.GetSpan()[0]); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(false)] |
|||
[InlineData(true)] |
|||
public void ReleaseRetainedResources_ReplacesInnerArrayPool(bool keepBufferAlive) |
|||
{ |
|||
IMemoryOwner<int> buffer = this.MemoryAllocator.Allocate<int>(32); |
|||
ref int ptrToPrev0 = ref MemoryMarshal.GetReference(buffer.GetSpan()); |
|||
|
|||
if (!keepBufferAlive) |
|||
{ |
|||
buffer.Dispose(); |
|||
} |
|||
|
|||
this.MemoryAllocator.ReleaseRetainedResources(); |
|||
|
|||
buffer = this.MemoryAllocator.Allocate<int>(32); |
|||
|
|||
Assert.False(Unsafe.AreSame(ref ptrToPrev0, ref buffer.GetReference())); |
|||
} |
|||
|
|||
[Fact] |
|||
public void ReleaseRetainedResources_DisposingPreviouslyAllocatedBuffer_IsAllowed() |
|||
{ |
|||
IMemoryOwner<int> buffer = this.MemoryAllocator.Allocate<int>(32); |
|||
this.MemoryAllocator.ReleaseRetainedResources(); |
|||
buffer.Dispose(); |
|||
} |
|||
|
|||
[Fact] |
|||
public void AllocationOverLargeArrayThreshold_UsesDifferentPool() |
|||
{ |
|||
if (!TestEnvironment.Is64BitProcess) |
|||
{ |
|||
// can lead to OutOfMemoryException
|
|||
return; |
|||
} |
|||
|
|||
const int ArrayLengthThreshold = PoolSelectorThresholdInBytes / sizeof(int); |
|||
|
|||
IMemoryOwner<int> small = this.MemoryAllocator.Allocate<int>(ArrayLengthThreshold - 1); |
|||
ref int ptr2Small = ref small.GetReference(); |
|||
small.Dispose(); |
|||
|
|||
IMemoryOwner<int> large = this.MemoryAllocator.Allocate<int>(ArrayLengthThreshold + 1); |
|||
|
|||
Assert.False(Unsafe.AreSame(ref ptr2Small, ref large.GetReference())); |
|||
} |
|||
|
|||
[Fact] |
|||
public void CreateWithAggressivePooling() |
|||
{ |
|||
if (!TestEnvironment.Is64BitProcess) |
|||
{ |
|||
// can lead to OutOfMemoryException
|
|||
return; |
|||
} |
|||
|
|||
this.MemoryAllocator = ArrayPoolMemoryAllocator.CreateWithAggressivePooling(); |
|||
|
|||
Assert.True(this.CheckIsRentingPooledBuffer<Rgba32>(4096 * 4096)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void CreateDefault() |
|||
{ |
|||
if (!TestEnvironment.Is64BitProcess) |
|||
{ |
|||
// can lead to OutOfMemoryException
|
|||
return; |
|||
} |
|||
|
|||
this.MemoryAllocator = ArrayPoolMemoryAllocator.CreateDefault(); |
|||
|
|||
Assert.False(this.CheckIsRentingPooledBuffer<Rgba32>(2 * 4096 * 4096)); |
|||
Assert.True(this.CheckIsRentingPooledBuffer<Rgba32>(2048 * 2048)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void CreateWithModeratePooling() |
|||
{ |
|||
if (!TestEnvironment.Is64BitProcess) |
|||
{ |
|||
// can lead to OutOfMemoryException
|
|||
return; |
|||
} |
|||
|
|||
this.MemoryAllocator = ArrayPoolMemoryAllocator.CreateWithModeratePooling(); |
|||
|
|||
Assert.False(this.CheckIsRentingPooledBuffer<Rgba32>(2048 * 2048)); |
|||
Assert.True(this.CheckIsRentingPooledBuffer<Rgba32>(1024 * 16)); |
|||
} |
|||
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
private struct Rgba32 |
|||
{ |
|||
private readonly uint dummy; |
|||
} |
|||
|
|||
private const int SizeOfLargeStruct = MaxPooledBufferSizeInBytes / 5; |
|||
|
|||
[StructLayout(LayoutKind.Explicit, Size = SizeOfLargeStruct)] |
|||
private struct LargeStruct |
|||
{ |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(-1)] |
|||
[InlineData((int.MaxValue / SizeOfLargeStruct) + 1)] |
|||
public void AllocateIncorrectAmount_ThrowsCorrect_ArgumentOutOfRangeException(int length) |
|||
{ |
|||
ArgumentOutOfRangeException ex = Assert.Throws<ArgumentOutOfRangeException>(() => this.MemoryAllocator.Allocate<LargeStruct>(length)); |
|||
Assert.Equal("length", ex.ParamName); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(-1)] |
|||
public void AllocateManagedByteBuffer_IncorrectAmount_ThrowsCorrect_ArgumentOutOfRangeException(int length) |
|||
{ |
|||
ArgumentOutOfRangeException ex = Assert.Throws<ArgumentOutOfRangeException>(() => this.MemoryAllocator.AllocateManagedByteBuffer(length)); |
|||
Assert.Equal("length", ex.ParamName); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace SixLabors.Memory.Tests |
|||
{ |
|||
internal static class BufferExtensions |
|||
{ |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Span<T> GetSpan<T>(this IMemoryOwner<T> buffer) |
|||
=> buffer.Memory.Span; |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static int Length<T>(this IMemoryOwner<T> buffer) |
|||
=> buffer.GetSpan().Length; |
|||
|
|||
public static ref T GetReference<T>(this IMemoryOwner<T> buffer) |
|||
where T : struct => |
|||
ref MemoryMarshal.GetReference(buffer.GetSpan()); |
|||
} |
|||
} |
|||
@ -0,0 +1,318 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
using Xunit; |
|||
|
|||
// ReSharper disable InconsistentNaming
|
|||
namespace SixLabors.Memory.Tests |
|||
{ |
|||
/// <summary>
|
|||
/// Inherit this class to test an <see cref="IMemoryOwner{T}"/> implementation (provided by <see cref="MemoryAllocator"/>).
|
|||
/// </summary>
|
|||
public abstract class BufferTestSuite |
|||
{ |
|||
protected BufferTestSuite(MemoryAllocator memoryAllocator) |
|||
{ |
|||
this.MemoryAllocator = memoryAllocator; |
|||
} |
|||
|
|||
protected MemoryAllocator MemoryAllocator { get; } |
|||
|
|||
public struct CustomStruct : IEquatable<CustomStruct> |
|||
{ |
|||
public long A; |
|||
|
|||
public byte B; |
|||
|
|||
public float C; |
|||
|
|||
public CustomStruct(long a, byte b, float c) |
|||
{ |
|||
this.A = a; |
|||
this.B = b; |
|||
this.C = c; |
|||
} |
|||
|
|||
public bool Equals(CustomStruct other) |
|||
{ |
|||
return this.A == other.A && this.B == other.B && this.C.Equals(other.C); |
|||
} |
|||
|
|||
public override bool Equals(object obj) |
|||
{ |
|||
return obj is CustomStruct other && this.Equals(other); |
|||
} |
|||
|
|||
public override int GetHashCode() |
|||
{ |
|||
unchecked |
|||
{ |
|||
int hashCode = this.A.GetHashCode(); |
|||
hashCode = (hashCode * 397) ^ this.B.GetHashCode(); |
|||
hashCode = (hashCode * 397) ^ this.C.GetHashCode(); |
|||
return hashCode; |
|||
} |
|||
} |
|||
} |
|||
|
|||
public static readonly TheoryData<int> LenthValues = new TheoryData<int> { 0, 1, 7, 1023, 1024 }; |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(LenthValues))] |
|||
public void HasCorrectLength_byte(int desiredLength) |
|||
{ |
|||
this.TestHasCorrectLength<byte>(desiredLength); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(LenthValues))] |
|||
public void HasCorrectLength_float(int desiredLength) |
|||
{ |
|||
this.TestHasCorrectLength<float>(desiredLength); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(LenthValues))] |
|||
public void HasCorrectLength_CustomStruct(int desiredLength) |
|||
{ |
|||
this.TestHasCorrectLength<CustomStruct>(desiredLength); |
|||
} |
|||
|
|||
private void TestHasCorrectLength<T>(int desiredLength) |
|||
where T : struct |
|||
{ |
|||
using (IMemoryOwner<T> buffer = this.MemoryAllocator.Allocate<T>(desiredLength)) |
|||
{ |
|||
Assert.Equal(desiredLength, buffer.GetSpan().Length); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(LenthValues))] |
|||
public void CanAllocateCleanBuffer_byte(int desiredLength) |
|||
{ |
|||
this.TestCanAllocateCleanBuffer<byte>(desiredLength, false); |
|||
this.TestCanAllocateCleanBuffer<byte>(desiredLength, true); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(LenthValues))] |
|||
public void CanAllocateCleanBuffer_double(int desiredLength) |
|||
{ |
|||
this.TestCanAllocateCleanBuffer<double>(desiredLength); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(LenthValues))] |
|||
public void CanAllocateCleanBuffer_CustomStruct(int desiredLength) |
|||
{ |
|||
this.TestCanAllocateCleanBuffer<CustomStruct>(desiredLength); |
|||
} |
|||
|
|||
private IMemoryOwner<T> Allocate<T>(int desiredLength, AllocationOptions options, bool managedByteBuffer) |
|||
where T : struct |
|||
{ |
|||
if (managedByteBuffer) |
|||
{ |
|||
if (!(this.MemoryAllocator.AllocateManagedByteBuffer(desiredLength, options) is IMemoryOwner<T> buffer)) |
|||
{ |
|||
throw new InvalidOperationException("typeof(T) != typeof(byte)"); |
|||
} |
|||
|
|||
return buffer; |
|||
} |
|||
|
|||
return this.MemoryAllocator.Allocate<T>(desiredLength, options); |
|||
} |
|||
|
|||
private void TestCanAllocateCleanBuffer<T>(int desiredLength, bool testManagedByteBuffer = false) |
|||
where T : struct, IEquatable<T> |
|||
{ |
|||
ReadOnlySpan<T> expected = new T[desiredLength]; |
|||
|
|||
for (int i = 0; i < 10; i++) |
|||
{ |
|||
using (IMemoryOwner<T> buffer = this.Allocate<T>(desiredLength, AllocationOptions.Clean, testManagedByteBuffer)) |
|||
{ |
|||
Assert.True(buffer.GetSpan().SequenceEqual(expected)); |
|||
} |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(LenthValues))] |
|||
public void SpanPropertyIsAlwaysTheSame_int(int desiredLength) |
|||
{ |
|||
this.TestSpanPropertyIsAlwaysTheSame<int>(desiredLength); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(LenthValues))] |
|||
public void SpanPropertyIsAlwaysTheSame_byte(int desiredLength) |
|||
{ |
|||
this.TestSpanPropertyIsAlwaysTheSame<byte>(desiredLength, false); |
|||
this.TestSpanPropertyIsAlwaysTheSame<byte>(desiredLength, true); |
|||
} |
|||
|
|||
private void TestSpanPropertyIsAlwaysTheSame<T>(int desiredLength, bool testManagedByteBuffer = false) |
|||
where T : struct |
|||
{ |
|||
using (IMemoryOwner<T> buffer = this.Allocate<T>(desiredLength, AllocationOptions.None, testManagedByteBuffer)) |
|||
{ |
|||
ref T a = ref MemoryMarshal.GetReference(buffer.GetSpan()); |
|||
ref T b = ref MemoryMarshal.GetReference(buffer.GetSpan()); |
|||
ref T c = ref MemoryMarshal.GetReference(buffer.GetSpan()); |
|||
|
|||
Assert.True(Unsafe.AreSame(ref a, ref b)); |
|||
Assert.True(Unsafe.AreSame(ref b, ref c)); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(LenthValues))] |
|||
public void WriteAndReadElements_float(int desiredLength) |
|||
{ |
|||
this.TestWriteAndReadElements<float>(desiredLength, x => x * 1.2f); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(LenthValues))] |
|||
public void WriteAndReadElements_byte(int desiredLength) |
|||
{ |
|||
this.TestWriteAndReadElements<byte>(desiredLength, x => (byte)(x + 1), false); |
|||
this.TestWriteAndReadElements<byte>(desiredLength, x => (byte)(x + 1), true); |
|||
} |
|||
|
|||
private void TestWriteAndReadElements<T>(int desiredLength, Func<int, T> getExpectedValue, bool testManagedByteBuffer = false) |
|||
where T : struct |
|||
{ |
|||
using (IMemoryOwner<T> buffer = this.Allocate<T>(desiredLength, AllocationOptions.None, testManagedByteBuffer)) |
|||
{ |
|||
T[] expectedVals = new T[buffer.Length()]; |
|||
|
|||
for (int i = 0; i < buffer.Length(); i++) |
|||
{ |
|||
Span<T> span = buffer.GetSpan(); |
|||
expectedVals[i] = getExpectedValue(i); |
|||
span[i] = expectedVals[i]; |
|||
} |
|||
|
|||
for (int i = 0; i < buffer.Length(); i++) |
|||
{ |
|||
Span<T> span = buffer.GetSpan(); |
|||
Assert.Equal(expectedVals[i], span[i]); |
|||
} |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(LenthValues))] |
|||
public void IndexingSpan_WhenOutOfRange_Throws_byte(int desiredLength) |
|||
{ |
|||
this.TestIndexOutOfRangeShouldThrow<byte>(desiredLength, false); |
|||
this.TestIndexOutOfRangeShouldThrow<byte>(desiredLength, true); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(LenthValues))] |
|||
public void IndexingSpan_WhenOutOfRange_Throws_long(int desiredLength) |
|||
{ |
|||
this.TestIndexOutOfRangeShouldThrow<long>(desiredLength); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(LenthValues))] |
|||
public void IndexingSpan_WhenOutOfRange_Throws_CustomStruct(int desiredLength) |
|||
{ |
|||
this.TestIndexOutOfRangeShouldThrow<CustomStruct>(desiredLength); |
|||
} |
|||
|
|||
private T TestIndexOutOfRangeShouldThrow<T>(int desiredLength, bool testManagedByteBuffer = false) |
|||
where T : struct, IEquatable<T> |
|||
{ |
|||
var dummy = default(T); |
|||
|
|||
using (IMemoryOwner<T> buffer = this.Allocate<T>(desiredLength, AllocationOptions.None, testManagedByteBuffer)) |
|||
{ |
|||
Assert.ThrowsAny<Exception>( |
|||
() => |
|||
{ |
|||
Span<T> span = buffer.GetSpan(); |
|||
dummy = span[desiredLength]; |
|||
}); |
|||
|
|||
Assert.ThrowsAny<Exception>( |
|||
() => |
|||
{ |
|||
Span<T> span = buffer.GetSpan(); |
|||
dummy = span[desiredLength + 1]; |
|||
}); |
|||
|
|||
Assert.ThrowsAny<Exception>( |
|||
() => |
|||
{ |
|||
Span<T> span = buffer.GetSpan(); |
|||
dummy = span[desiredLength + 42]; |
|||
}); |
|||
} |
|||
|
|||
return dummy; |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(1)] |
|||
[InlineData(7)] |
|||
[InlineData(1024)] |
|||
[InlineData(6666)] |
|||
public void ManagedByteBuffer_ArrayIsCorrect(int desiredLength) |
|||
{ |
|||
using (IManagedByteBuffer buffer = this.MemoryAllocator.AllocateManagedByteBuffer(desiredLength)) |
|||
{ |
|||
ref byte array0 = ref buffer.Array[0]; |
|||
ref byte span0 = ref buffer.GetReference(); |
|||
|
|||
Assert.True(Unsafe.AreSame(ref span0, ref array0)); |
|||
Assert.True(buffer.Array.Length >= buffer.GetSpan().Length); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void GetMemory_ReturnsValidMemory() |
|||
{ |
|||
using (IMemoryOwner<CustomStruct> buffer = this.MemoryAllocator.Allocate<CustomStruct>(42)) |
|||
{ |
|||
Span<CustomStruct> span0 = buffer.GetSpan(); |
|||
span0[10].A = 30; |
|||
Memory<CustomStruct> memory = buffer.Memory; |
|||
|
|||
Assert.Equal(42, memory.Length); |
|||
Span<CustomStruct> span1 = memory.Span; |
|||
|
|||
Assert.Equal(42, span1.Length); |
|||
Assert.Equal(30, span1[10].A); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public unsafe void GetMemory_ResultIsPinnable() |
|||
{ |
|||
using (IMemoryOwner<int> buffer = this.MemoryAllocator.Allocate<int>(42)) |
|||
{ |
|||
Span<int> span0 = buffer.GetSpan(); |
|||
span0[10] = 30; |
|||
|
|||
Memory<int> memory = buffer.Memory; |
|||
|
|||
using (MemoryHandle h = memory.Pin()) |
|||
{ |
|||
int* ptr = (int*)h.Pointer; |
|||
Assert.Equal(30, ptr[10]); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,43 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Runtime.InteropServices; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.Memory.Tests |
|||
{ |
|||
public class SimpleGcMemoryAllocatorTests |
|||
{ |
|||
public class BufferTests : BufferTestSuite |
|||
{ |
|||
public BufferTests() |
|||
: base(new SimpleGcMemoryAllocator()) |
|||
{ |
|||
} |
|||
} |
|||
|
|||
protected SimpleGcMemoryAllocator MemoryAllocator { get; } = new SimpleGcMemoryAllocator(); |
|||
|
|||
[Theory] |
|||
[InlineData(-1)] |
|||
public void Allocate_IncorrectAmount_ThrowsCorrect_ArgumentOutOfRangeException(int length) |
|||
{ |
|||
ArgumentOutOfRangeException ex = Assert.Throws<ArgumentOutOfRangeException>(() => this.MemoryAllocator.Allocate<BigStruct>(length)); |
|||
Assert.Equal("length", ex.ParamName); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(-1)] |
|||
public void AllocateManagedByteBuffer_IncorrectAmount_ThrowsCorrect_ArgumentOutOfRangeException(int length) |
|||
{ |
|||
ArgumentOutOfRangeException ex = Assert.Throws<ArgumentOutOfRangeException>(() => this.MemoryAllocator.AllocateManagedByteBuffer(length)); |
|||
Assert.Equal("length", ex.ParamName); |
|||
} |
|||
|
|||
[StructLayout(LayoutKind.Explicit, Size = 512)] |
|||
private struct BigStruct |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,210 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Globalization; |
|||
using System.Numerics; |
|||
using System.Reflection; |
|||
using System.Runtime.CompilerServices; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.Primitives.Tests |
|||
{ |
|||
public class PointFTests |
|||
{ |
|||
[Fact] |
|||
public void CanReinterpretCastFromVector2() |
|||
{ |
|||
var vector = new Vector2(1, 2); |
|||
|
|||
PointF point = Unsafe.As<Vector2, PointF>(ref vector); |
|||
|
|||
Assert.Equal(vector.X, point.X); |
|||
Assert.Equal(vector.Y, point.Y); |
|||
} |
|||
|
|||
[Fact] |
|||
public void DefaultConstructorTest() |
|||
{ |
|||
Assert.Equal(default, PointF.Empty); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(float.MaxValue, float.MinValue)] |
|||
[InlineData(float.MinValue, float.MinValue)] |
|||
[InlineData(float.MaxValue, float.MaxValue)] |
|||
[InlineData(float.MinValue, float.MaxValue)] |
|||
[InlineData(0.0, 0.0)] |
|||
public void NonDefaultConstructorTest(float x, float y) |
|||
{ |
|||
var p1 = new PointF(x, y); |
|||
|
|||
Assert.Equal(x, p1.X); |
|||
Assert.Equal(y, p1.Y); |
|||
} |
|||
|
|||
[Fact] |
|||
public void IsEmptyDefaultsTest() |
|||
{ |
|||
Assert.True(PointF.Empty.IsEmpty); |
|||
Assert.True(default(PointF).IsEmpty); |
|||
Assert.True(new PointF(0, 0).IsEmpty); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(float.MaxValue, float.MinValue)] |
|||
[InlineData(float.MinValue, float.MinValue)] |
|||
[InlineData(float.MaxValue, float.MaxValue)] |
|||
public void IsEmptyRandomTest(float x, float y) |
|||
{ |
|||
Assert.False(new PointF(x, y).IsEmpty); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(float.MaxValue, float.MinValue)] |
|||
[InlineData(float.MinValue, float.MinValue)] |
|||
[InlineData(float.MaxValue, float.MaxValue)] |
|||
[InlineData(0, 0)] |
|||
public void CoordinatesTest(float x, float y) |
|||
{ |
|||
var p = new PointF(x, y); |
|||
Assert.Equal(x, p.X); |
|||
Assert.Equal(y, p.Y); |
|||
|
|||
p.X = 10; |
|||
Assert.Equal(10, p.X); |
|||
|
|||
p.Y = -10.123f; |
|||
Assert.Equal(-10.123, p.Y, 3); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(float.MaxValue, float.MinValue, int.MaxValue, int.MinValue)] |
|||
[InlineData(float.MinValue, float.MaxValue, int.MinValue, int.MaxValue)] |
|||
[InlineData(0, 0, 0, 0)] |
|||
public void ArithmeticTestWithSize(float x, float y, int x1, int y1) |
|||
{ |
|||
var p = new PointF(x, y); |
|||
var s = new Size(x1, y1); |
|||
|
|||
var addExpected = new PointF(x + x1, y + y1); |
|||
var subExpected = new PointF(x - x1, y - y1); |
|||
Assert.Equal(addExpected, p + s); |
|||
Assert.Equal(subExpected, p - s); |
|||
Assert.Equal(addExpected, PointF.Add(p, s)); |
|||
Assert.Equal(subExpected, PointF.Subtract(p, s)); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(float.MaxValue, float.MinValue)] |
|||
[InlineData(float.MinValue, float.MaxValue)] |
|||
[InlineData(0, 0)] |
|||
public void ArithmeticTestWithSizeF(float x, float y) |
|||
{ |
|||
var p = new PointF(x, y); |
|||
var s = new SizeF(y, x); |
|||
|
|||
var addExpected = new PointF(x + y, y + x); |
|||
var subExpected = new PointF(x - y, y - x); |
|||
Assert.Equal(addExpected, p + s); |
|||
Assert.Equal(subExpected, p - s); |
|||
Assert.Equal(addExpected, PointF.Add(p, s)); |
|||
Assert.Equal(subExpected, PointF.Subtract(p, s)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void RotateTest() |
|||
{ |
|||
var p = new PointF(13, 17); |
|||
Matrix3x2 matrix = Matrix3x2Extensions.CreateRotationDegrees(45, PointF.Empty); |
|||
|
|||
var pout = PointF.Transform(p, matrix); |
|||
|
|||
Assert.Equal(new PointF(-2.82842732F, 21.2132034F), pout); |
|||
} |
|||
|
|||
[Fact] |
|||
public void SkewTest() |
|||
{ |
|||
var p = new PointF(13, 17); |
|||
Matrix3x2 matrix = Matrix3x2Extensions.CreateSkewDegrees(45, 45, PointF.Empty); |
|||
|
|||
var pout = PointF.Transform(p, matrix); |
|||
Assert.Equal(new PointF(30, 30), pout); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(float.MaxValue, float.MinValue)] |
|||
[InlineData(float.MinValue, float.MaxValue)] |
|||
[InlineData(float.MinValue, float.MinValue)] |
|||
[InlineData(float.MaxValue, float.MaxValue)] |
|||
[InlineData(0, 0)] |
|||
public void EqualityTest(float x, float y) |
|||
{ |
|||
var pLeft = new PointF(x, y); |
|||
var pRight = new PointF(y, x); |
|||
|
|||
if (x == y) |
|||
{ |
|||
Assert.True(pLeft == pRight); |
|||
Assert.False(pLeft != pRight); |
|||
Assert.True(pLeft.Equals(pRight)); |
|||
Assert.True(pLeft.Equals((object)pRight)); |
|||
Assert.Equal(pLeft.GetHashCode(), pRight.GetHashCode()); |
|||
return; |
|||
} |
|||
|
|||
Assert.True(pLeft != pRight); |
|||
Assert.False(pLeft == pRight); |
|||
Assert.False(pLeft.Equals(pRight)); |
|||
Assert.False(pLeft.Equals((object)pRight)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void EqualityTest_NotPointF() |
|||
{ |
|||
var point = new PointF(0, 0); |
|||
Assert.False(point.Equals(null)); |
|||
Assert.False(point.Equals(0)); |
|||
|
|||
// If PointF implements IEquatable<PointF> (e.g. in .NET Core), then structs that are implicitly
|
|||
// convertible to var can potentially be equal.
|
|||
// See https://github.com/dotnet/corefx/issues/5255.
|
|||
bool expectsImplicitCastToPointF = typeof(IEquatable<PointF>).IsAssignableFrom(point.GetType()); |
|||
Assert.Equal(expectsImplicitCastToPointF, point.Equals(new Point(0, 0))); |
|||
|
|||
Assert.False(point.Equals((object)new Point(0, 0))); // No implicit cast
|
|||
} |
|||
|
|||
[Fact] |
|||
public void GetHashCodeTest() |
|||
{ |
|||
var point = new PointF(10, 10); |
|||
Assert.Equal(point.GetHashCode(), new PointF(10, 10).GetHashCode()); |
|||
Assert.NotEqual(point.GetHashCode(), new PointF(20, 10).GetHashCode()); |
|||
Assert.NotEqual(point.GetHashCode(), new PointF(10, 20).GetHashCode()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void ToStringTest() |
|||
{ |
|||
var p = new PointF(5.1F, -5.123F); |
|||
Assert.Equal(string.Format(CultureInfo.CurrentCulture, "PointF [ X={0}, Y={1} ]", p.X, p.Y), p.ToString()); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(float.MaxValue, float.MinValue)] |
|||
[InlineData(float.MinValue, float.MinValue)] |
|||
[InlineData(float.MaxValue, float.MaxValue)] |
|||
[InlineData(0, 0)] |
|||
public void DeconstructTest(float x, float y) |
|||
{ |
|||
PointF p = new PointF(x, y); |
|||
|
|||
(float deconstructedX, float deconstructedY) = p; |
|||
|
|||
Assert.Equal(x, deconstructedX); |
|||
Assert.Equal(y, deconstructedY); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,258 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Globalization; |
|||
using System.Numerics; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.Primitives.Tests |
|||
{ |
|||
public class PointTests |
|||
{ |
|||
[Fact] |
|||
public void DefaultConstructorTest() |
|||
{ |
|||
Assert.Equal(default, Point.Empty); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(int.MaxValue, int.MinValue)] |
|||
[InlineData(int.MinValue, int.MinValue)] |
|||
[InlineData(int.MaxValue, int.MaxValue)] |
|||
[InlineData(0, 0)] |
|||
public void NonDefaultConstructorTest(int x, int y) |
|||
{ |
|||
var p1 = new Point(x, y); |
|||
var p2 = new Point(new Size(x, y)); |
|||
|
|||
Assert.Equal(p1, p2); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(int.MaxValue)] |
|||
[InlineData(int.MinValue)] |
|||
[InlineData(0)] |
|||
public void SingleIntConstructorTest(int x) |
|||
{ |
|||
var p1 = new Point(x); |
|||
var p2 = new Point(unchecked((short)(x & 0xFFFF)), unchecked((short)((x >> 16) & 0xFFFF))); |
|||
|
|||
Assert.Equal(p1, p2); |
|||
} |
|||
|
|||
[Fact] |
|||
public void IsEmptyDefaultsTest() |
|||
{ |
|||
Assert.True(Point.Empty.IsEmpty); |
|||
Assert.True(default(Point).IsEmpty); |
|||
Assert.True(new Point(0, 0).IsEmpty); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(int.MaxValue, int.MinValue)] |
|||
[InlineData(int.MinValue, int.MinValue)] |
|||
[InlineData(int.MaxValue, int.MaxValue)] |
|||
public void IsEmptyRandomTest(int x, int y) |
|||
{ |
|||
Assert.False(new Point(x, y).IsEmpty); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(int.MaxValue, int.MinValue)] |
|||
[InlineData(int.MinValue, int.MinValue)] |
|||
[InlineData(int.MaxValue, int.MaxValue)] |
|||
[InlineData(0, 0)] |
|||
public void CoordinatesTest(int x, int y) |
|||
{ |
|||
var p = new Point(x, y); |
|||
Assert.Equal(x, p.X); |
|||
Assert.Equal(y, p.Y); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(int.MaxValue, int.MinValue)] |
|||
[InlineData(int.MinValue, int.MinValue)] |
|||
[InlineData(int.MaxValue, int.MaxValue)] |
|||
[InlineData(0, 0)] |
|||
public void PointFConversionTest(int x, int y) |
|||
{ |
|||
PointF p = new Point(x, y); |
|||
Assert.Equal(new PointF(x, y), p); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(int.MaxValue, int.MinValue)] |
|||
[InlineData(int.MinValue, int.MinValue)] |
|||
[InlineData(int.MaxValue, int.MaxValue)] |
|||
[InlineData(0, 0)] |
|||
public void SizeConversionTest(int x, int y) |
|||
{ |
|||
var sz = (Size)new Point(x, y); |
|||
Assert.Equal(new Size(x, y), sz); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(int.MaxValue, int.MinValue)] |
|||
[InlineData(int.MinValue, int.MinValue)] |
|||
[InlineData(int.MaxValue, int.MaxValue)] |
|||
[InlineData(0, 0)] |
|||
public void ArithmeticTest(int x, int y) |
|||
{ |
|||
Point addExpected, subExpected, p = new Point(x, y); |
|||
var s = new Size(y, x); |
|||
|
|||
unchecked |
|||
{ |
|||
addExpected = new Point(x + y, y + x); |
|||
subExpected = new Point(x - y, y - x); |
|||
} |
|||
|
|||
Assert.Equal(addExpected, p + s); |
|||
Assert.Equal(subExpected, p - s); |
|||
Assert.Equal(addExpected, Point.Add(p, s)); |
|||
Assert.Equal(subExpected, Point.Subtract(p, s)); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(float.MaxValue, float.MinValue)] |
|||
[InlineData(float.MinValue, float.MinValue)] |
|||
[InlineData(float.MaxValue, float.MaxValue)] |
|||
[InlineData(0, 0)] |
|||
public void PointFMathematicalTest(float x, float y) |
|||
{ |
|||
var pf = new PointF(x, y); |
|||
Point pCeiling, pTruncate, pRound; |
|||
|
|||
unchecked |
|||
{ |
|||
pCeiling = new Point((int)MathF.Ceiling(x), (int)MathF.Ceiling(y)); |
|||
pTruncate = new Point((int)x, (int)y); |
|||
pRound = new Point((int)MathF.Round(x), (int)MathF.Round(y)); |
|||
} |
|||
|
|||
Assert.Equal(pCeiling, Point.Ceiling(pf)); |
|||
Assert.Equal(pRound, Point.Round(pf)); |
|||
Assert.Equal(pTruncate, (Point)pf); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(int.MaxValue, int.MinValue)] |
|||
[InlineData(int.MinValue, int.MinValue)] |
|||
[InlineData(int.MaxValue, int.MaxValue)] |
|||
[InlineData(0, 0)] |
|||
public void OffsetTest(int x, int y) |
|||
{ |
|||
var p1 = new Point(x, y); |
|||
var p2 = new Point(y, x); |
|||
|
|||
p1.Offset(p2); |
|||
|
|||
Assert.Equal(unchecked(p2.X + p2.Y), p1.X); |
|||
Assert.Equal(p1.X, p1.Y); |
|||
|
|||
p2.Offset(x, y); |
|||
Assert.Equal(p1, p2); |
|||
} |
|||
|
|||
[Fact] |
|||
public void RotateTest() |
|||
{ |
|||
var p = new Point(13, 17); |
|||
Matrix3x2 matrix = Matrix3x2Extensions.CreateRotationDegrees(45, Point.Empty); |
|||
|
|||
var pout = Point.Transform(p, matrix); |
|||
|
|||
Assert.Equal(new Point(-3, 21), pout); |
|||
} |
|||
|
|||
[Fact] |
|||
public void SkewTest() |
|||
{ |
|||
var p = new Point(13, 17); |
|||
Matrix3x2 matrix = Matrix3x2Extensions.CreateSkewDegrees(45, 45, Point.Empty); |
|||
|
|||
var pout = Point.Transform(p, matrix); |
|||
Assert.Equal(new Point(30, 30), pout); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(int.MaxValue, int.MinValue)] |
|||
[InlineData(int.MinValue, int.MinValue)] |
|||
[InlineData(int.MaxValue, int.MaxValue)] |
|||
[InlineData(0, 0)] |
|||
public void EqualityTest(int x, int y) |
|||
{ |
|||
var p1 = new Point(x, y); |
|||
var p2 = new Point((x / 2) - 1, (y / 2) - 1); |
|||
var p3 = new Point(x, y); |
|||
|
|||
Assert.True(p1 == p3); |
|||
Assert.True(p1 != p2); |
|||
Assert.True(p2 != p3); |
|||
|
|||
Assert.True(p1.Equals(p3)); |
|||
Assert.False(p1.Equals(p2)); |
|||
Assert.False(p2.Equals(p3)); |
|||
|
|||
Assert.True(p1.Equals((object)p3)); |
|||
Assert.False(p1.Equals((object)p2)); |
|||
Assert.False(p2.Equals((object)p3)); |
|||
|
|||
Assert.Equal(p1.GetHashCode(), p3.GetHashCode()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void EqualityTest_NotPoint() |
|||
{ |
|||
var point = new Point(0, 0); |
|||
Assert.False(point.Equals(null)); |
|||
Assert.False(point.Equals(0)); |
|||
Assert.False(point.Equals(new PointF(0, 0))); |
|||
} |
|||
|
|||
[Fact] |
|||
public void GetHashCodeTest() |
|||
{ |
|||
var point = new Point(10, 10); |
|||
Assert.Equal(point.GetHashCode(), new Point(10, 10).GetHashCode()); |
|||
Assert.NotEqual(point.GetHashCode(), new Point(20, 10).GetHashCode()); |
|||
Assert.NotEqual(point.GetHashCode(), new Point(10, 20).GetHashCode()); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(0, 0, 0, 0)] |
|||
[InlineData(1, -2, 3, -4)] |
|||
public void ConversionTest(int x, int y, int width, int height) |
|||
{ |
|||
var rect = new Rectangle(x, y, width, height); |
|||
RectangleF rectF = rect; |
|||
Assert.Equal(x, rectF.X); |
|||
Assert.Equal(y, rectF.Y); |
|||
Assert.Equal(width, rectF.Width); |
|||
Assert.Equal(height, rectF.Height); |
|||
} |
|||
|
|||
[Fact] |
|||
public void ToStringTest() |
|||
{ |
|||
var p = new Point(5, -5); |
|||
Assert.Equal(string.Format(CultureInfo.CurrentCulture, "Point [ X={0}, Y={1} ]", p.X, p.Y), p.ToString()); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(int.MaxValue, int.MinValue)] |
|||
[InlineData(int.MinValue, int.MinValue)] |
|||
[InlineData(int.MaxValue, int.MaxValue)] |
|||
[InlineData(0, 0)] |
|||
public void DeconstructTest(int x, int y) |
|||
{ |
|||
Point p = new Point(x, y); |
|||
|
|||
(int deconstructedX, int deconstructedY) = p; |
|||
|
|||
Assert.Equal(x, deconstructedX); |
|||
Assert.Equal(y, deconstructedY); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,286 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Globalization; |
|||
using System.Reflection; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.Primitives.Tests |
|||
{ |
|||
/// <summary>
|
|||
/// Tests the <see cref="RectangleF"/> struct.
|
|||
/// </summary>
|
|||
public class RectangleFTests |
|||
{ |
|||
[Fact] |
|||
public void DefaultConstructorTest() |
|||
{ |
|||
Assert.Equal(default, RectangleF.Empty); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(0, 0, 0, 0)] |
|||
[InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)] |
|||
[InlineData(float.MaxValue, 0, 0, float.MaxValue)] |
|||
[InlineData(0, float.MinValue, float.MaxValue, 0)] |
|||
public void NonDefaultConstructorTest(float x, float y, float width, float height) |
|||
{ |
|||
var rect1 = new RectangleF(x, y, width, height); |
|||
var p = new PointF(x, y); |
|||
var s = new SizeF(width, height); |
|||
var rect2 = new RectangleF(p, s); |
|||
|
|||
Assert.Equal(rect1, rect2); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(0, 0, 0, 0)] |
|||
[InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)] |
|||
[InlineData(float.MaxValue, 0, 0, float.MaxValue)] |
|||
[InlineData(0, float.MinValue, float.MaxValue, 0)] |
|||
public void FromLTRBTest(float left, float top, float right, float bottom) |
|||
{ |
|||
var expected = new RectangleF(left, top, right - left, bottom - top); |
|||
var actual = RectangleF.FromLTRB(left, top, right, bottom); |
|||
|
|||
Assert.Equal(expected, actual); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(0, 0, 0, 0)] |
|||
[InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)] |
|||
[InlineData(float.MaxValue, 0, 0, float.MaxValue)] |
|||
[InlineData(0, float.MinValue, float.MaxValue, 0)] |
|||
public void DimensionsTest(float x, float y, float width, float height) |
|||
{ |
|||
var rect = new RectangleF(x, y, width, height); |
|||
var p = new PointF(x, y); |
|||
var s = new SizeF(width, height); |
|||
|
|||
Assert.Equal(p, rect.Location); |
|||
Assert.Equal(s, rect.Size); |
|||
Assert.Equal(x, rect.X); |
|||
Assert.Equal(y, rect.Y); |
|||
Assert.Equal(width, rect.Width); |
|||
Assert.Equal(height, rect.Height); |
|||
Assert.Equal(x, rect.Left); |
|||
Assert.Equal(y, rect.Top); |
|||
Assert.Equal(x + width, rect.Right); |
|||
Assert.Equal(y + height, rect.Bottom); |
|||
} |
|||
|
|||
[Fact] |
|||
public void IsEmptyTest() |
|||
{ |
|||
Assert.True(RectangleF.Empty.IsEmpty); |
|||
Assert.True(default(RectangleF).IsEmpty); |
|||
Assert.True(new RectangleF(1, -2, -10, 10).IsEmpty); |
|||
Assert.True(new RectangleF(1, -2, 10, -10).IsEmpty); |
|||
Assert.True(new RectangleF(1, -2, 0, 0).IsEmpty); |
|||
|
|||
Assert.False(new RectangleF(0, 0, 10, 10).IsEmpty); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(0, 0)] |
|||
[InlineData(float.MaxValue, float.MinValue)] |
|||
public void LocationSetTest(float x, float y) |
|||
{ |
|||
var point = new PointF(x, y); |
|||
var rect = new RectangleF(10, 10, 10, 10) { Location = point }; |
|||
Assert.Equal(point, rect.Location); |
|||
Assert.Equal(point.X, rect.X); |
|||
Assert.Equal(point.Y, rect.Y); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(0, 0)] |
|||
[InlineData(float.MaxValue, float.MinValue)] |
|||
public void SizeSetTest(float x, float y) |
|||
{ |
|||
var size = new SizeF(x, y); |
|||
var rect = new RectangleF(10, 10, 10, 10) { Size = size }; |
|||
Assert.Equal(size, rect.Size); |
|||
Assert.Equal(size.Width, rect.Width); |
|||
Assert.Equal(size.Height, rect.Height); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)] |
|||
[InlineData(float.MaxValue, 0, 0, float.MaxValue)] |
|||
[InlineData(0, float.MinValue, float.MaxValue, 0)] |
|||
public void EqualityTest(float x, float y, float width, float height) |
|||
{ |
|||
var rect1 = new RectangleF(x, y, width, height); |
|||
var rect2 = new RectangleF(width, height, x, y); |
|||
|
|||
Assert.True(rect1 != rect2); |
|||
Assert.False(rect1 == rect2); |
|||
Assert.False(rect1.Equals(rect2)); |
|||
Assert.False(rect1.Equals((object)rect2)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void EqualityTestNotRectangleF() |
|||
{ |
|||
var rectangle = new RectangleF(0, 0, 0, 0); |
|||
Assert.False(rectangle.Equals(null)); |
|||
Assert.False(rectangle.Equals(0)); |
|||
|
|||
// If RectangleF implements IEquatable<RectangleF> (e.g. in .NET Core), then classes that are implicitly
|
|||
// convertible to RectangleF can potentially be equal.
|
|||
// See https://github.com/dotnet/corefx/issues/5255.
|
|||
bool expectsImplicitCastToRectangleF = typeof(IEquatable<RectangleF>).IsAssignableFrom(rectangle.GetType()); |
|||
Assert.Equal(expectsImplicitCastToRectangleF, rectangle.Equals(new Rectangle(0, 0, 0, 0))); |
|||
|
|||
Assert.False(rectangle.Equals((object)new Rectangle(0, 0, 0, 0))); // No implicit cast
|
|||
} |
|||
|
|||
[Fact] |
|||
public void GetHashCodeTest() |
|||
{ |
|||
var rect1 = new RectangleF(10, 10, 10, 10); |
|||
var rect2 = new RectangleF(10, 10, 10, 10); |
|||
Assert.Equal(rect1.GetHashCode(), rect2.GetHashCode()); |
|||
Assert.NotEqual(rect1.GetHashCode(), new RectangleF(20, 10, 10, 10).GetHashCode()); |
|||
Assert.NotEqual(rect1.GetHashCode(), new RectangleF(10, 20, 10, 10).GetHashCode()); |
|||
Assert.NotEqual(rect1.GetHashCode(), new RectangleF(10, 10, 20, 10).GetHashCode()); |
|||
Assert.NotEqual(rect1.GetHashCode(), new RectangleF(10, 10, 10, 20).GetHashCode()); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)] |
|||
[InlineData(0, float.MinValue, float.MaxValue, 0)] |
|||
public void ContainsTest(float x, float y, float width, float height) |
|||
{ |
|||
var rect = new RectangleF(x, y, width, height); |
|||
float x1 = (x + width) / 2; |
|||
float y1 = (y + height) / 2; |
|||
var p = new PointF(x1, y1); |
|||
var r = new RectangleF(x1, y1, width / 2, height / 2); |
|||
|
|||
Assert.False(rect.Contains(x1, y1)); |
|||
Assert.False(rect.Contains(p)); |
|||
Assert.False(rect.Contains(r)); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(0, 0, 0, 0)] |
|||
[InlineData(float.MaxValue / 2, float.MinValue / 2, float.MinValue / 2, float.MaxValue / 2)] |
|||
[InlineData(0, float.MinValue, float.MaxValue, 0)] |
|||
public void InflateTest(float x, float y, float width, float height) |
|||
{ |
|||
var rect = new RectangleF(x, y, width, height); |
|||
var inflatedRect = new RectangleF(x - width, y - height, width + (2 * width), height + (2 * height)); |
|||
|
|||
rect.Inflate(width, height); |
|||
Assert.Equal(inflatedRect, rect); |
|||
|
|||
var s = new SizeF(x, y); |
|||
inflatedRect = RectangleF.Inflate(rect, x, y); |
|||
|
|||
rect.Inflate(s); |
|||
Assert.Equal(inflatedRect, rect); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(float.MaxValue, float.MinValue, float.MaxValue / 2, float.MinValue / 2)] |
|||
[InlineData(0, float.MinValue, float.MaxValue, 0)] |
|||
public void IntersectTest(float x, float y, float width, float height) |
|||
{ |
|||
var rect1 = new RectangleF(x, y, width, height); |
|||
var rect2 = new RectangleF(y, x, width, height); |
|||
var expectedRect = RectangleF.Intersect(rect1, rect2); |
|||
rect1.Intersect(rect2); |
|||
Assert.Equal(expectedRect, rect1); |
|||
Assert.False(rect1.IntersectsWith(expectedRect)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void IntersectIntersectingRectsTest() |
|||
{ |
|||
var rect1 = new RectangleF(0, 0, 5, 5); |
|||
var rect2 = new RectangleF(1, 1, 3, 3); |
|||
var expected = new RectangleF(1, 1, 3, 3); |
|||
|
|||
Assert.Equal(expected, RectangleF.Intersect(rect1, rect2)); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(0, 0, 0, 0)] |
|||
[InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)] |
|||
[InlineData(float.MaxValue, 0, 0, float.MaxValue)] |
|||
[InlineData(0, float.MinValue, float.MaxValue, 0)] |
|||
public void UnionTest(float x, float y, float width, float height) |
|||
{ |
|||
var a = new RectangleF(x, y, width, height); |
|||
var b = new RectangleF(width, height, x, y); |
|||
|
|||
float x1 = Math.Min(a.X, b.X); |
|||
float x2 = Math.Max(a.X + a.Width, b.X + b.Width); |
|||
float y1 = Math.Min(a.Y, b.Y); |
|||
float y2 = Math.Max(a.Y + a.Height, b.Y + b.Height); |
|||
|
|||
var expectedRectangle = new RectangleF(x1, y1, x2 - x1, y2 - y1); |
|||
|
|||
Assert.Equal(expectedRectangle, RectangleF.Union(a, b)); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(0, 0, 0, 0)] |
|||
[InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)] |
|||
[InlineData(float.MaxValue, 0, 0, float.MaxValue)] |
|||
[InlineData(0, float.MinValue, float.MaxValue, 0)] |
|||
public void OffsetTest(float x, float y, float width, float height) |
|||
{ |
|||
var r1 = new RectangleF(x, y, width, height); |
|||
var expectedRect = new RectangleF(x + width, y + height, width, height); |
|||
var p = new PointF(width, height); |
|||
|
|||
r1.Offset(p); |
|||
Assert.Equal(expectedRect, r1); |
|||
|
|||
expectedRect.Offset(p); |
|||
r1.Offset(width, height); |
|||
Assert.Equal(expectedRect, r1); |
|||
} |
|||
|
|||
[Fact] |
|||
public void ToStringTest() |
|||
{ |
|||
var r = new RectangleF(5, 5.1F, 1.3F, 1); |
|||
Assert.Equal(string.Format(CultureInfo.CurrentCulture, "RectangleF [ X={0}, Y={1}, Width={2}, Height={3} ]", r.X, r.Y, r.Width, r.Height), r.ToString()); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(float.MinValue, float.MaxValue, float.MaxValue, float.MaxValue)] |
|||
[InlineData(float.MinValue, float.MaxValue, float.MaxValue, float.MinValue)] |
|||
[InlineData(float.MinValue, float.MaxValue, float.MinValue, float.MaxValue)] |
|||
[InlineData(float.MinValue, float.MaxValue, float.MinValue, float.MinValue)] |
|||
[InlineData(float.MinValue, float.MinValue, float.MaxValue, float.MaxValue)] |
|||
[InlineData(float.MinValue, float.MinValue, float.MaxValue, float.MinValue)] |
|||
[InlineData(float.MinValue, float.MinValue, float.MinValue, float.MaxValue)] |
|||
[InlineData(float.MinValue, float.MinValue, float.MinValue, float.MinValue)] |
|||
[InlineData(float.MaxValue, float.MaxValue, float.MaxValue, float.MaxValue)] |
|||
[InlineData(float.MaxValue, float.MaxValue, float.MaxValue, float.MinValue)] |
|||
[InlineData(float.MaxValue, float.MaxValue, float.MinValue, float.MaxValue)] |
|||
[InlineData(float.MaxValue, float.MaxValue, float.MinValue, float.MinValue)] |
|||
[InlineData(float.MaxValue, float.MinValue, float.MaxValue, float.MaxValue)] |
|||
[InlineData(float.MaxValue, float.MinValue, float.MaxValue, float.MinValue)] |
|||
[InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)] |
|||
[InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MinValue)] |
|||
[InlineData(0, 0, 0, 0)] |
|||
public void DeconstructTest(float x, float y, float width, float height) |
|||
{ |
|||
RectangleF r = new RectangleF(x, y, width, height); |
|||
|
|||
(float dx, float dy, float dw, float dh) = r; |
|||
|
|||
Assert.Equal(x, dx); |
|||
Assert.Equal(y, dy); |
|||
Assert.Equal(width, dw); |
|||
Assert.Equal(height, dh); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,336 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Globalization; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.Primitives.Tests |
|||
{ |
|||
/// <summary>
|
|||
/// Tests the <see cref="Rectangle"/> struct.
|
|||
/// </summary>
|
|||
public class RectangleTests |
|||
{ |
|||
[Fact] |
|||
public void DefaultConstructorTest() |
|||
{ |
|||
Assert.Equal(default, Rectangle.Empty); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(int.MaxValue, int.MinValue, int.MaxValue, int.MinValue)] |
|||
[InlineData(int.MaxValue, 0, int.MinValue, 0)] |
|||
[InlineData(0, 0, 0, 0)] |
|||
[InlineData(0, int.MinValue, 0, int.MaxValue)] |
|||
public void NonDefaultConstructorTest(int x, int y, int width, int height) |
|||
{ |
|||
var rect1 = new Rectangle(x, y, width, height); |
|||
var rect2 = new Rectangle(new Point(x, y), new Size(width, height)); |
|||
|
|||
Assert.Equal(rect1, rect2); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(int.MaxValue, int.MinValue, int.MaxValue, int.MinValue)] |
|||
[InlineData(int.MaxValue, 0, int.MinValue, 0)] |
|||
[InlineData(0, 0, 0, 0)] |
|||
[InlineData(0, int.MinValue, 0, int.MaxValue)] |
|||
public void FromLTRBTest(int left, int top, int right, int bottom) |
|||
{ |
|||
var rect1 = new Rectangle(left, top, unchecked(right - left), unchecked(bottom - top)); |
|||
var rect2 = Rectangle.FromLTRB(left, top, right, bottom); |
|||
|
|||
Assert.Equal(rect1, rect2); |
|||
} |
|||
|
|||
[Fact] |
|||
public void EmptyTest() |
|||
{ |
|||
Assert.True(Rectangle.Empty.IsEmpty); |
|||
Assert.True(default(Rectangle).IsEmpty); |
|||
Assert.True(new Rectangle(0, 0, 0, 0).IsEmpty); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(int.MaxValue, int.MinValue, int.MaxValue, int.MinValue)] |
|||
[InlineData(int.MaxValue, 0, int.MinValue, 0)] |
|||
[InlineData(int.MinValue, int.MaxValue, int.MinValue, int.MaxValue)] |
|||
[InlineData(0, int.MinValue, 0, int.MaxValue)] |
|||
public void NonEmptyTest(int x, int y, int width, int height) |
|||
{ |
|||
Assert.False(new Rectangle(x, y, width, height).IsEmpty); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(int.MaxValue, int.MinValue, int.MaxValue, int.MinValue)] |
|||
[InlineData(int.MaxValue, 0, int.MinValue, 0)] |
|||
[InlineData(0, 0, 0, 0)] |
|||
[InlineData(0, int.MinValue, 0, int.MaxValue)] |
|||
[InlineData(int.MinValue, int.MaxValue, int.MinValue, int.MaxValue)] |
|||
public void DimensionsTest(int x, int y, int width, int height) |
|||
{ |
|||
var rect = new Rectangle(x, y, width, height); |
|||
Assert.Equal(new Point(x, y), rect.Location); |
|||
Assert.Equal(new Size(width, height), rect.Size); |
|||
|
|||
Assert.Equal(x, rect.X); |
|||
Assert.Equal(y, rect.Y); |
|||
Assert.Equal(width, rect.Width); |
|||
Assert.Equal(height, rect.Height); |
|||
Assert.Equal(x, rect.Left); |
|||
Assert.Equal(y, rect.Top); |
|||
Assert.Equal(unchecked(x + width), rect.Right); |
|||
Assert.Equal(unchecked(y + height), rect.Bottom); |
|||
|
|||
var p = new Point(width, height); |
|||
var s = new Size(x, y); |
|||
rect.Location = p; |
|||
rect.Size = s; |
|||
|
|||
Assert.Equal(p, rect.Location); |
|||
Assert.Equal(s, rect.Size); |
|||
|
|||
Assert.Equal(width, rect.X); |
|||
Assert.Equal(height, rect.Y); |
|||
Assert.Equal(x, rect.Width); |
|||
Assert.Equal(y, rect.Height); |
|||
Assert.Equal(width, rect.Left); |
|||
Assert.Equal(height, rect.Top); |
|||
Assert.Equal(unchecked(x + width), rect.Right); |
|||
Assert.Equal(unchecked(y + height), rect.Bottom); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(0, 0)] |
|||
[InlineData(int.MaxValue, int.MinValue)] |
|||
public void LocationSetTest(int x, int y) |
|||
{ |
|||
var point = new Point(x, y); |
|||
var rect = new Rectangle(10, 10, 10, 10) { Location = point }; |
|||
Assert.Equal(point, rect.Location); |
|||
Assert.Equal(point.X, rect.X); |
|||
Assert.Equal(point.Y, rect.Y); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(0, 0)] |
|||
[InlineData(int.MaxValue, int.MinValue)] |
|||
public void SizeSetTest(int x, int y) |
|||
{ |
|||
var size = new Size(x, y); |
|||
var rect = new Rectangle(10, 10, 10, 10) { Size = size }; |
|||
Assert.Equal(size, rect.Size); |
|||
Assert.Equal(size.Width, rect.Width); |
|||
Assert.Equal(size.Height, rect.Height); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(int.MaxValue, int.MinValue, int.MaxValue, int.MinValue)] |
|||
[InlineData(int.MaxValue, 0, int.MinValue, 0)] |
|||
[InlineData(0, int.MinValue, 0, int.MaxValue)] |
|||
[InlineData(int.MinValue, int.MaxValue, int.MinValue, int.MaxValue)] |
|||
public void EqualityTest(int x, int y, int width, int height) |
|||
{ |
|||
var rect1 = new Rectangle(x, y, width, height); |
|||
var rect2 = new Rectangle(width / 2, height / 2, x, y); |
|||
|
|||
Assert.True(rect1 != rect2); |
|||
Assert.False(rect1 == rect2); |
|||
Assert.False(rect1.Equals(rect2)); |
|||
Assert.False(rect1.Equals((object)rect2)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void EqualityTestNotRectangle() |
|||
{ |
|||
var rectangle = new Rectangle(0, 0, 0, 0); |
|||
Assert.False(rectangle.Equals(null)); |
|||
Assert.False(rectangle.Equals(0)); |
|||
Assert.False(rectangle.Equals(new RectangleF(0, 0, 0, 0))); |
|||
} |
|||
|
|||
[Fact] |
|||
public void GetHashCodeTest() |
|||
{ |
|||
var rect1 = new Rectangle(10, 10, 10, 10); |
|||
var rect2 = new Rectangle(10, 10, 10, 10); |
|||
Assert.Equal(rect1.GetHashCode(), rect2.GetHashCode()); |
|||
Assert.NotEqual(rect1.GetHashCode(), new Rectangle(20, 10, 10, 10).GetHashCode()); |
|||
Assert.NotEqual(rect1.GetHashCode(), new Rectangle(10, 20, 10, 10).GetHashCode()); |
|||
Assert.NotEqual(rect1.GetHashCode(), new Rectangle(10, 10, 20, 10).GetHashCode()); |
|||
Assert.NotEqual(rect1.GetHashCode(), new Rectangle(10, 10, 10, 20).GetHashCode()); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(float.MaxValue, float.MinValue, float.MaxValue, float.MinValue)] |
|||
[InlineData(float.MinValue, float.MaxValue, float.MinValue, float.MaxValue)] |
|||
[InlineData(0, 0, 0, 0)] |
|||
public void RectangleFConversionTest(float x, float y, float width, float height) |
|||
{ |
|||
var rect = new RectangleF(x, y, width, height); |
|||
Rectangle rCeiling, rTruncate, rRound; |
|||
|
|||
unchecked |
|||
{ |
|||
rCeiling = new Rectangle( |
|||
(int)Math.Ceiling(x), |
|||
(int)Math.Ceiling(y), |
|||
(int)Math.Ceiling(width), |
|||
(int)Math.Ceiling(height)); |
|||
|
|||
rTruncate = new Rectangle((int)x, (int)y, (int)width, (int)height); |
|||
|
|||
rRound = new Rectangle( |
|||
(int)Math.Round(x), |
|||
(int)Math.Round(y), |
|||
(int)Math.Round(width), |
|||
(int)Math.Round(height)); |
|||
} |
|||
|
|||
Assert.Equal(rCeiling, Rectangle.Ceiling(rect)); |
|||
Assert.Equal(rTruncate, Rectangle.Truncate(rect)); |
|||
Assert.Equal(rRound, Rectangle.Round(rect)); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(int.MaxValue, int.MinValue, int.MinValue, int.MaxValue)] |
|||
[InlineData(0, int.MinValue, int.MaxValue, 0)] |
|||
public void ContainsTest(int x, int y, int width, int height) |
|||
{ |
|||
var rect = new Rectangle(unchecked((2 * x) - width), unchecked((2 * y) - height), width, height); |
|||
var p = new Point(x, y); |
|||
var r = new Rectangle(x, y, width / 2, height / 2); |
|||
|
|||
Assert.False(rect.Contains(x, y)); |
|||
Assert.False(rect.Contains(p)); |
|||
Assert.False(rect.Contains(r)); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(0, 0, 0, 0)] |
|||
[InlineData(int.MaxValue, int.MinValue, int.MinValue, int.MaxValue)] |
|||
[InlineData(0, int.MinValue, int.MaxValue, 0)] |
|||
public void InflateTest(int x, int y, int width, int height) |
|||
{ |
|||
Rectangle inflatedRect, rect = new Rectangle(x, y, width, height); |
|||
unchecked |
|||
{ |
|||
inflatedRect = new Rectangle(x - width, y - height, width + (2 * width), height + (2 * height)); |
|||
} |
|||
|
|||
Assert.Equal(inflatedRect, Rectangle.Inflate(rect, width, height)); |
|||
|
|||
rect.Inflate(width, height); |
|||
Assert.Equal(inflatedRect, rect); |
|||
|
|||
var s = new Size(x, y); |
|||
unchecked |
|||
{ |
|||
inflatedRect = new Rectangle(rect.X - x, rect.Y - y, rect.Width + (2 * x), rect.Height + (2 * y)); |
|||
} |
|||
|
|||
rect.Inflate(s); |
|||
Assert.Equal(inflatedRect, rect); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(0, 0, 0, 0)] |
|||
[InlineData(int.MaxValue, int.MinValue, int.MinValue, int.MaxValue)] |
|||
[InlineData(0, int.MinValue, int.MaxValue, 0)] |
|||
public void IntersectTest(int x, int y, int width, int height) |
|||
{ |
|||
var rect = new Rectangle(x, y, width, height); |
|||
var expectedRect = Rectangle.Intersect(rect, rect); |
|||
rect.Intersect(rect); |
|||
Assert.Equal(expectedRect, rect); |
|||
Assert.False(rect.IntersectsWith(expectedRect)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void IntersectIntersectingRectsTest() |
|||
{ |
|||
var rect1 = new Rectangle(0, 0, 5, 5); |
|||
var rect2 = new Rectangle(1, 1, 3, 3); |
|||
var expected = new Rectangle(1, 1, 3, 3); |
|||
|
|||
Assert.Equal(expected, Rectangle.Intersect(rect1, rect2)); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(0, 0, 0, 0)] |
|||
[InlineData(int.MaxValue, int.MinValue, int.MinValue, int.MaxValue)] |
|||
[InlineData(int.MaxValue, 0, 0, int.MaxValue)] |
|||
[InlineData(0, int.MinValue, int.MaxValue, 0)] |
|||
public void UnionTest(int x, int y, int width, int height) |
|||
{ |
|||
var a = new Rectangle(x, y, width, height); |
|||
var b = new Rectangle(width, height, x, y); |
|||
|
|||
int x1 = Math.Min(a.X, b.X); |
|||
int x2 = Math.Max(a.X + a.Width, b.X + b.Width); |
|||
int y1 = Math.Min(a.Y, b.Y); |
|||
int y2 = Math.Max(a.Y + a.Height, b.Y + b.Height); |
|||
|
|||
var expectedRectangle = new Rectangle(x1, y1, x2 - x1, y2 - y1); |
|||
|
|||
Assert.Equal(expectedRectangle, Rectangle.Union(a, b)); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(0, 0, 0, 0)] |
|||
[InlineData(int.MaxValue, int.MinValue, int.MinValue, int.MaxValue)] |
|||
[InlineData(int.MaxValue, 0, 0, int.MaxValue)] |
|||
[InlineData(0, int.MinValue, int.MaxValue, 0)] |
|||
public void OffsetTest(int x, int y, int width, int height) |
|||
{ |
|||
var r1 = new Rectangle(x, y, width, height); |
|||
var expectedRect = new Rectangle(x + width, y + height, width, height); |
|||
var p = new Point(width, height); |
|||
|
|||
r1.Offset(p); |
|||
Assert.Equal(expectedRect, r1); |
|||
|
|||
expectedRect.Offset(p); |
|||
r1.Offset(width, height); |
|||
Assert.Equal(expectedRect, r1); |
|||
} |
|||
|
|||
[Fact] |
|||
public void ToStringTest() |
|||
{ |
|||
var r = new Rectangle(5, -5, 0, 1); |
|||
Assert.Equal(string.Format(CultureInfo.CurrentCulture, "Rectangle [ X={0}, Y={1}, Width={2}, Height={3} ]", r.X, r.Y, r.Width, r.Height), r.ToString()); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(int.MinValue, int.MaxValue, int.MaxValue, int.MaxValue)] |
|||
[InlineData(int.MinValue, int.MaxValue, int.MaxValue, int.MinValue)] |
|||
[InlineData(int.MinValue, int.MaxValue, int.MinValue, int.MaxValue)] |
|||
[InlineData(int.MinValue, int.MaxValue, int.MinValue, int.MinValue)] |
|||
[InlineData(int.MinValue, int.MinValue, int.MaxValue, int.MaxValue)] |
|||
[InlineData(int.MinValue, int.MinValue, int.MaxValue, int.MinValue)] |
|||
[InlineData(int.MinValue, int.MinValue, int.MinValue, int.MaxValue)] |
|||
[InlineData(int.MinValue, int.MinValue, int.MinValue, int.MinValue)] |
|||
[InlineData(int.MaxValue, int.MaxValue, int.MaxValue, int.MaxValue)] |
|||
[InlineData(int.MaxValue, int.MaxValue, int.MaxValue, int.MinValue)] |
|||
[InlineData(int.MaxValue, int.MaxValue, int.MinValue, int.MaxValue)] |
|||
[InlineData(int.MaxValue, int.MaxValue, int.MinValue, int.MinValue)] |
|||
[InlineData(int.MaxValue, int.MinValue, int.MaxValue, int.MaxValue)] |
|||
[InlineData(int.MaxValue, int.MinValue, int.MaxValue, int.MinValue)] |
|||
[InlineData(int.MaxValue, int.MinValue, int.MinValue, int.MaxValue)] |
|||
[InlineData(int.MaxValue, int.MinValue, int.MinValue, int.MinValue)] |
|||
[InlineData(0, 0, 0, 0)] |
|||
public void DeconstructTest(int x, int y, int width, int height) |
|||
{ |
|||
var r = new Rectangle(x, y, width, height); |
|||
|
|||
(int dx, int dy, int dw, int dh) = r; |
|||
|
|||
Assert.Equal(x, dx); |
|||
Assert.Equal(y, dy); |
|||
Assert.Equal(width, dw); |
|||
Assert.Equal(height, dh); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,250 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Globalization; |
|||
using System.Reflection; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.Primitives.Tests |
|||
{ |
|||
public class SizeFTests |
|||
{ |
|||
[Fact] |
|||
public void DefaultConstructorTest() |
|||
{ |
|||
Assert.Equal(default, SizeF.Empty); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(float.MaxValue, float.MinValue)] |
|||
[InlineData(float.MinValue, float.MinValue)] |
|||
[InlineData(float.MaxValue, float.MaxValue)] |
|||
[InlineData(0, 0)] |
|||
public void NonDefaultConstructorAndDimensionsTest(float width, float height) |
|||
{ |
|||
var s1 = new SizeF(width, height); |
|||
var p1 = new PointF(width, height); |
|||
var s2 = new SizeF(s1); |
|||
|
|||
Assert.Equal(s1, s2); |
|||
Assert.Equal(s1, new SizeF(p1)); |
|||
Assert.Equal(s2, new SizeF(p1)); |
|||
|
|||
Assert.Equal(width, s1.Width); |
|||
Assert.Equal(height, s1.Height); |
|||
|
|||
s1.Width = 10; |
|||
Assert.Equal(10, s1.Width); |
|||
|
|||
s1.Height = -10.123f; |
|||
Assert.Equal(-10.123, s1.Height, 3); |
|||
} |
|||
|
|||
[Fact] |
|||
public void IsEmptyDefaultsTest() |
|||
{ |
|||
Assert.True(SizeF.Empty.IsEmpty); |
|||
Assert.True(default(SizeF).IsEmpty); |
|||
Assert.True(new SizeF(0, 0).IsEmpty); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(float.MaxValue, float.MinValue)] |
|||
[InlineData(float.MinValue, float.MinValue)] |
|||
[InlineData(float.MaxValue, float.MaxValue)] |
|||
public void IsEmptyRandomTest(float width, float height) |
|||
{ |
|||
Assert.False(new SizeF(width, height).IsEmpty); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(float.MaxValue, float.MinValue)] |
|||
[InlineData(float.MinValue, float.MinValue)] |
|||
[InlineData(float.MaxValue, float.MaxValue)] |
|||
[InlineData(0, 0)] |
|||
public void ArithmeticTest(float width, float height) |
|||
{ |
|||
var s1 = new SizeF(width, height); |
|||
var s2 = new SizeF(height, width); |
|||
var addExpected = new SizeF(width + height, width + height); |
|||
var subExpected = new SizeF(width - height, height - width); |
|||
|
|||
Assert.Equal(addExpected, s1 + s2); |
|||
Assert.Equal(addExpected, SizeF.Add(s1, s2)); |
|||
|
|||
Assert.Equal(subExpected, s1 - s2); |
|||
Assert.Equal(subExpected, SizeF.Subtract(s1, s2)); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(float.MaxValue, float.MinValue)] |
|||
[InlineData(float.MinValue, float.MinValue)] |
|||
[InlineData(float.MaxValue, float.MaxValue)] |
|||
[InlineData(0, 0)] |
|||
public void EqualityTest(float width, float height) |
|||
{ |
|||
var sLeft = new SizeF(width, height); |
|||
var sRight = new SizeF(height, width); |
|||
|
|||
if (width == height) |
|||
{ |
|||
Assert.True(sLeft == sRight); |
|||
Assert.False(sLeft != sRight); |
|||
Assert.True(sLeft.Equals(sRight)); |
|||
Assert.True(sLeft.Equals((object)sRight)); |
|||
Assert.Equal(sLeft.GetHashCode(), sRight.GetHashCode()); |
|||
return; |
|||
} |
|||
|
|||
Assert.True(sLeft != sRight); |
|||
Assert.False(sLeft == sRight); |
|||
Assert.False(sLeft.Equals(sRight)); |
|||
Assert.False(sLeft.Equals((object)sRight)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void EqualityTest_NotSizeF() |
|||
{ |
|||
var size = new SizeF(0, 0); |
|||
Assert.False(size.Equals(null)); |
|||
Assert.False(size.Equals(0)); |
|||
|
|||
// If SizeF implements IEquatable<SizeF> (e.g in .NET Core), then classes that are implicitly
|
|||
// convertible to SizeF can potentially be equal.
|
|||
// See https://github.com/dotnet/corefx/issues/5255.
|
|||
bool expectsImplicitCastToSizeF = typeof(IEquatable<SizeF>).IsAssignableFrom(size.GetType()); |
|||
Assert.Equal(expectsImplicitCastToSizeF, size.Equals(new Size(0, 0))); |
|||
|
|||
Assert.False(size.Equals((object)new Size(0, 0))); // No implicit cast
|
|||
} |
|||
|
|||
[Fact] |
|||
public void GetHashCodeTest() |
|||
{ |
|||
var size = new SizeF(10, 10); |
|||
Assert.Equal(size.GetHashCode(), new SizeF(10, 10).GetHashCode()); |
|||
Assert.NotEqual(size.GetHashCode(), new SizeF(20, 10).GetHashCode()); |
|||
Assert.NotEqual(size.GetHashCode(), new SizeF(10, 20).GetHashCode()); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(float.MaxValue, float.MinValue)] |
|||
[InlineData(float.MinValue, float.MinValue)] |
|||
[InlineData(float.MaxValue, float.MaxValue)] |
|||
[InlineData(0, 0)] |
|||
public void ConversionTest(float width, float height) |
|||
{ |
|||
var s1 = new SizeF(width, height); |
|||
var p1 = (PointF)s1; |
|||
var s2 = new Size(unchecked((int)width), unchecked((int)height)); |
|||
|
|||
Assert.Equal(new PointF(width, height), p1); |
|||
Assert.Equal(p1, (PointF)s1); |
|||
Assert.Equal(s2, (Size)s1); |
|||
} |
|||
|
|||
[Fact] |
|||
public void ToStringTest() |
|||
{ |
|||
var sz = new SizeF(10, 5); |
|||
Assert.Equal(string.Format(CultureInfo.CurrentCulture, "SizeF [ Width={0}, Height={1} ]", sz.Width, sz.Height), sz.ToString()); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(1000.234f, 0.0f)] |
|||
[InlineData(1000.234f, 1.0f)] |
|||
[InlineData(1000.234f, 2400.933f)] |
|||
[InlineData(1000.234f, float.MaxValue)] |
|||
[InlineData(1000.234f, -1.0f)] |
|||
[InlineData(1000.234f, -2400.933f)] |
|||
[InlineData(1000.234f, float.MinValue)] |
|||
[InlineData(float.MaxValue, 0.0f)] |
|||
[InlineData(float.MaxValue, 1.0f)] |
|||
[InlineData(float.MaxValue, 2400.933f)] |
|||
[InlineData(float.MaxValue, float.MaxValue)] |
|||
[InlineData(float.MaxValue, -1.0f)] |
|||
[InlineData(float.MaxValue, -2400.933f)] |
|||
[InlineData(float.MaxValue, float.MinValue)] |
|||
[InlineData(float.MinValue, 0.0f)] |
|||
[InlineData(float.MinValue, 1.0f)] |
|||
[InlineData(float.MinValue, 2400.933f)] |
|||
[InlineData(float.MinValue, float.MaxValue)] |
|||
[InlineData(float.MinValue, -1.0f)] |
|||
[InlineData(float.MinValue, -2400.933f)] |
|||
[InlineData(float.MinValue, float.MinValue)] |
|||
public void MultiplicationTest(float dimension, float multiplier) |
|||
{ |
|||
SizeF sz1 = new SizeF(dimension, dimension); |
|||
SizeF mulExpected; |
|||
|
|||
mulExpected = new SizeF(dimension * multiplier, dimension * multiplier); |
|||
|
|||
Assert.Equal(mulExpected, sz1 * multiplier); |
|||
Assert.Equal(mulExpected, multiplier * sz1); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(1111.1111f, 2222.2222f, 3333.3333f)] |
|||
public void MultiplicationTestWidthHeightMultiplier(float width, float height, float multiplier) |
|||
{ |
|||
SizeF sz1 = new SizeF(width, height); |
|||
SizeF mulExpected; |
|||
|
|||
mulExpected = new SizeF(width * multiplier, height * multiplier); |
|||
|
|||
Assert.Equal(mulExpected, sz1 * multiplier); |
|||
Assert.Equal(mulExpected, multiplier * sz1); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(0.0f, 1.0f)] |
|||
[InlineData(1.0f, 1.0f)] |
|||
[InlineData(-1.0f, 1.0f)] |
|||
[InlineData(1.0f, -1.0f)] |
|||
[InlineData(-1.0f, -1.0f)] |
|||
[InlineData(float.MaxValue, float.MaxValue)] |
|||
[InlineData(float.MaxValue, float.MinValue)] |
|||
[InlineData(float.MinValue, float.MaxValue)] |
|||
[InlineData(float.MinValue, float.MinValue)] |
|||
[InlineData(float.MaxValue, 1.0f)] |
|||
[InlineData(float.MinValue, 1.0f)] |
|||
[InlineData(float.MaxValue, -1.0f)] |
|||
[InlineData(float.MinValue, -1.0f)] |
|||
[InlineData(float.MinValue, 0.0f)] |
|||
[InlineData(1.0f, float.MinValue)] |
|||
[InlineData(1.0f, float.MaxValue)] |
|||
[InlineData(-1.0f, float.MinValue)] |
|||
[InlineData(-1.0f, float.MaxValue)] |
|||
public void DivideTestSizeFloat(float dimension, float divisor) |
|||
{ |
|||
SizeF size = new SizeF(dimension, dimension); |
|||
SizeF expected = new SizeF(dimension / divisor, dimension / divisor); |
|||
Assert.Equal(expected, size / divisor); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(-111.111f, 222.222f, 333.333f)] |
|||
public void DivideTestSizeFloatWidthHeightDivisor(float width, float height, float divisor) |
|||
{ |
|||
SizeF size = new SizeF(width, height); |
|||
SizeF expected = new SizeF(width / divisor, height / divisor); |
|||
Assert.Equal(expected, size / divisor); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(float.MaxValue, float.MinValue)] |
|||
[InlineData(float.MinValue, float.MinValue)] |
|||
[InlineData(float.MaxValue, float.MaxValue)] |
|||
[InlineData(0, 0)] |
|||
public void DeconstructTest(float width, float height) |
|||
{ |
|||
SizeF s = new SizeF(width, height); |
|||
|
|||
(float deconstructedWidth, float deconstructedHeight) = s; |
|||
|
|||
Assert.Equal(width, deconstructedWidth); |
|||
Assert.Equal(height, deconstructedHeight); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,379 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Globalization; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.Primitives.Tests |
|||
{ |
|||
/// <summary>
|
|||
/// Tests the <see cref="Size"/> struct.
|
|||
/// </summary>
|
|||
public class SizeTests |
|||
{ |
|||
[Fact] |
|||
public void DefaultConstructorTest() |
|||
{ |
|||
Assert.Equal(default, Size.Empty); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(int.MaxValue, int.MinValue)] |
|||
[InlineData(int.MinValue, int.MinValue)] |
|||
[InlineData(int.MaxValue, int.MaxValue)] |
|||
[InlineData(0, 0)] |
|||
public void NonDefaultConstructorTest(int width, int height) |
|||
{ |
|||
var s1 = new Size(width, height); |
|||
var s2 = new Size(new Point(width, height)); |
|||
|
|||
Assert.Equal(s1, s2); |
|||
|
|||
s1.Width = 10; |
|||
Assert.Equal(10, s1.Width); |
|||
|
|||
s1.Height = -10; |
|||
Assert.Equal(-10, s1.Height); |
|||
} |
|||
|
|||
[Fact] |
|||
public void IsEmptyDefaultsTest() |
|||
{ |
|||
Assert.True(Size.Empty.IsEmpty); |
|||
Assert.True(default(Size).IsEmpty); |
|||
Assert.True(new Size(0, 0).IsEmpty); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(int.MaxValue, int.MinValue)] |
|||
[InlineData(int.MinValue, int.MinValue)] |
|||
[InlineData(int.MaxValue, int.MaxValue)] |
|||
public void IsEmptyRandomTest(int width, int height) |
|||
{ |
|||
Assert.False(new Size(width, height).IsEmpty); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(int.MaxValue, int.MinValue)] |
|||
[InlineData(int.MinValue, int.MinValue)] |
|||
[InlineData(int.MaxValue, int.MaxValue)] |
|||
[InlineData(0, 0)] |
|||
public void DimensionsTest(int width, int height) |
|||
{ |
|||
var p = new Size(width, height); |
|||
Assert.Equal(width, p.Width); |
|||
Assert.Equal(height, p.Height); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(int.MaxValue, int.MinValue)] |
|||
[InlineData(int.MinValue, int.MinValue)] |
|||
[InlineData(int.MaxValue, int.MaxValue)] |
|||
[InlineData(0, 0)] |
|||
public void PointFConversionTest(int width, int height) |
|||
{ |
|||
SizeF sz = new Size(width, height); |
|||
Assert.Equal(new SizeF(width, height), sz); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(int.MaxValue, int.MinValue)] |
|||
[InlineData(int.MinValue, int.MinValue)] |
|||
[InlineData(int.MaxValue, int.MaxValue)] |
|||
[InlineData(0, 0)] |
|||
public void SizeConversionTest(int width, int height) |
|||
{ |
|||
var sz = (Point)new Size(width, height); |
|||
Assert.Equal(new Point(width, height), sz); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(int.MaxValue, int.MinValue)] |
|||
[InlineData(int.MinValue, int.MinValue)] |
|||
[InlineData(int.MaxValue, int.MaxValue)] |
|||
[InlineData(0, 0)] |
|||
public void ArithmeticTest(int width, int height) |
|||
{ |
|||
var sz1 = new Size(width, height); |
|||
var sz2 = new Size(height, width); |
|||
Size addExpected, subExpected; |
|||
|
|||
unchecked |
|||
{ |
|||
addExpected = new Size(width + height, height + width); |
|||
subExpected = new Size(width - height, height - width); |
|||
} |
|||
|
|||
Assert.Equal(addExpected, sz1 + sz2); |
|||
Assert.Equal(subExpected, sz1 - sz2); |
|||
Assert.Equal(addExpected, Size.Add(sz1, sz2)); |
|||
Assert.Equal(subExpected, Size.Subtract(sz1, sz2)); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(float.MaxValue, float.MinValue)] |
|||
[InlineData(float.MinValue, float.MinValue)] |
|||
[InlineData(float.MaxValue, float.MaxValue)] |
|||
[InlineData(0, 0)] |
|||
public void PointFMathematicalTest(float width, float height) |
|||
{ |
|||
var szF = new SizeF(width, height); |
|||
Size pCeiling, pTruncate, pRound; |
|||
|
|||
unchecked |
|||
{ |
|||
pCeiling = new Size((int)MathF.Ceiling(width), (int)MathF.Ceiling(height)); |
|||
pTruncate = new Size((int)width, (int)height); |
|||
pRound = new Size((int)MathF.Round(width), (int)MathF.Round(height)); |
|||
} |
|||
|
|||
Assert.Equal(pCeiling, Size.Ceiling(szF)); |
|||
Assert.Equal(pRound, Size.Round(szF)); |
|||
Assert.Equal(pTruncate, (Size)szF); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(int.MaxValue, int.MinValue)] |
|||
[InlineData(int.MinValue, int.MinValue)] |
|||
[InlineData(int.MaxValue, int.MaxValue)] |
|||
[InlineData(0, 0)] |
|||
public void EqualityTest(int width, int height) |
|||
{ |
|||
var p1 = new Size(width, height); |
|||
var p2 = new Size(unchecked(width - 1), unchecked(height - 1)); |
|||
var p3 = new Size(width, height); |
|||
|
|||
Assert.True(p1 == p3); |
|||
Assert.True(p1 != p2); |
|||
Assert.True(p2 != p3); |
|||
|
|||
Assert.True(p1.Equals(p3)); |
|||
Assert.False(p1.Equals(p2)); |
|||
Assert.False(p2.Equals(p3)); |
|||
|
|||
Assert.True(p1.Equals((object)p3)); |
|||
Assert.False(p1.Equals((object)p2)); |
|||
Assert.False(p2.Equals((object)p3)); |
|||
|
|||
Assert.Equal(p1.GetHashCode(), p3.GetHashCode()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void EqualityTest_NotSize() |
|||
{ |
|||
var size = new Size(0, 0); |
|||
Assert.False(size.Equals(null)); |
|||
Assert.False(size.Equals(0)); |
|||
Assert.False(size.Equals(new SizeF(0, 0))); |
|||
} |
|||
|
|||
[Fact] |
|||
public void GetHashCodeTest() |
|||
{ |
|||
var size = new Size(10, 10); |
|||
Assert.Equal(size.GetHashCode(), new Size(10, 10).GetHashCode()); |
|||
Assert.NotEqual(size.GetHashCode(), new Size(20, 10).GetHashCode()); |
|||
Assert.NotEqual(size.GetHashCode(), new Size(10, 20).GetHashCode()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void ToStringTest() |
|||
{ |
|||
var sz = new Size(10, 5); |
|||
Assert.Equal(string.Format(CultureInfo.CurrentCulture, "Size [ Width={0}, Height={1} ]", sz.Width, sz.Height), sz.ToString()); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(1000, 0)] |
|||
[InlineData(1000, 1)] |
|||
[InlineData(1000, 2400)] |
|||
[InlineData(1000, int.MaxValue)] |
|||
[InlineData(1000, -1)] |
|||
[InlineData(1000, -2400)] |
|||
[InlineData(1000, int.MinValue)] |
|||
[InlineData(int.MaxValue, 0)] |
|||
[InlineData(int.MaxValue, 1)] |
|||
[InlineData(int.MaxValue, 2400)] |
|||
[InlineData(int.MaxValue, int.MaxValue)] |
|||
[InlineData(int.MaxValue, -1)] |
|||
[InlineData(int.MaxValue, -2400)] |
|||
[InlineData(int.MaxValue, int.MinValue)] |
|||
[InlineData(int.MinValue, 0)] |
|||
[InlineData(int.MinValue, 1)] |
|||
[InlineData(int.MinValue, 2400)] |
|||
[InlineData(int.MinValue, int.MaxValue)] |
|||
[InlineData(int.MinValue, -1)] |
|||
[InlineData(int.MinValue, -2400)] |
|||
[InlineData(int.MinValue, int.MinValue)] |
|||
public void MultiplicationTestSizeInt(int dimension, int multiplier) |
|||
{ |
|||
Size sz1 = new Size(dimension, dimension); |
|||
Size mulExpected; |
|||
|
|||
unchecked |
|||
{ |
|||
mulExpected = new Size(dimension * multiplier, dimension * multiplier); |
|||
} |
|||
|
|||
Assert.Equal(mulExpected, sz1 * multiplier); |
|||
Assert.Equal(mulExpected, multiplier * sz1); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(1000, 2000, 3000)] |
|||
public void MultiplicationTestSizeIntWidthHeightMultiplier(int width, int height, int multiplier) |
|||
{ |
|||
Size sz1 = new Size(width, height); |
|||
Size mulExpected; |
|||
|
|||
unchecked |
|||
{ |
|||
mulExpected = new Size(width * multiplier, height * multiplier); |
|||
} |
|||
|
|||
Assert.Equal(mulExpected, sz1 * multiplier); |
|||
Assert.Equal(mulExpected, multiplier * sz1); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(1000, 0.0f)] |
|||
[InlineData(1000, 1.0f)] |
|||
[InlineData(1000, 2400.933f)] |
|||
[InlineData(1000, float.MaxValue)] |
|||
[InlineData(1000, -1.0f)] |
|||
[InlineData(1000, -2400.933f)] |
|||
[InlineData(1000, float.MinValue)] |
|||
[InlineData(int.MaxValue, 0.0f)] |
|||
[InlineData(int.MaxValue, 1.0f)] |
|||
[InlineData(int.MaxValue, 2400.933f)] |
|||
[InlineData(int.MaxValue, float.MaxValue)] |
|||
[InlineData(int.MaxValue, -1.0f)] |
|||
[InlineData(int.MaxValue, -2400.933f)] |
|||
[InlineData(int.MaxValue, float.MinValue)] |
|||
[InlineData(int.MinValue, 0.0f)] |
|||
[InlineData(int.MinValue, 1.0f)] |
|||
[InlineData(int.MinValue, 2400.933f)] |
|||
[InlineData(int.MinValue, float.MaxValue)] |
|||
[InlineData(int.MinValue, -1.0f)] |
|||
[InlineData(int.MinValue, -2400.933f)] |
|||
[InlineData(int.MinValue, float.MinValue)] |
|||
public void MultiplicationTestSizeFloat(int dimension, float multiplier) |
|||
{ |
|||
Size sz1 = new Size(dimension, dimension); |
|||
SizeF mulExpected; |
|||
|
|||
mulExpected = new SizeF(dimension * multiplier, dimension * multiplier); |
|||
|
|||
Assert.Equal(mulExpected, sz1 * multiplier); |
|||
Assert.Equal(mulExpected, multiplier * sz1); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(1000, 2000, 30.33f)] |
|||
public void MultiplicationTestSizeFloatWidthHeightMultiplier(int width, int height, float multiplier) |
|||
{ |
|||
Size sz1 = new Size(width, height); |
|||
SizeF mulExpected; |
|||
|
|||
mulExpected = new SizeF(width * multiplier, height * multiplier); |
|||
|
|||
Assert.Equal(mulExpected, sz1 * multiplier); |
|||
Assert.Equal(mulExpected, multiplier * sz1); |
|||
} |
|||
|
|||
[Fact] |
|||
public void DivideByZeroChecks() |
|||
{ |
|||
Size size = new Size(100, 100); |
|||
Assert.Throws<DivideByZeroException>(() => size / 0); |
|||
|
|||
SizeF expectedSizeF = new SizeF(float.PositiveInfinity, float.PositiveInfinity); |
|||
Assert.Equal(expectedSizeF, size / 0.0f); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(0, 1)] |
|||
[InlineData(1, 1)] |
|||
[InlineData(-1, 1)] |
|||
[InlineData(1, -1)] |
|||
[InlineData(-1, -1)] |
|||
[InlineData(int.MaxValue, int.MaxValue)] |
|||
[InlineData(int.MaxValue, int.MinValue)] |
|||
[InlineData(int.MinValue, int.MaxValue)] |
|||
[InlineData(int.MinValue, int.MinValue)] |
|||
[InlineData(int.MaxValue, 1)] |
|||
[InlineData(int.MinValue, 1)] |
|||
[InlineData(int.MaxValue, -1)] |
|||
public void DivideTestSizeInt(int dimension, int divisor) |
|||
{ |
|||
Size size = new Size(dimension, dimension); |
|||
Size expected; |
|||
|
|||
expected = new Size(dimension / divisor, dimension / divisor); |
|||
|
|||
Assert.Equal(expected, size / divisor); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(1111, 2222, 3333)] |
|||
public void DivideTestSizeIntWidthHeightDivisor(int width, int height, int divisor) |
|||
{ |
|||
Size size = new Size(width, height); |
|||
Size expected; |
|||
|
|||
expected = new Size(width / divisor, height / divisor); |
|||
|
|||
Assert.Equal(expected, size / divisor); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(0, 1.0f)] |
|||
[InlineData(1, 1.0f)] |
|||
[InlineData(-1, 1.0f)] |
|||
[InlineData(1, -1.0f)] |
|||
[InlineData(-1, -1.0f)] |
|||
[InlineData(int.MaxValue, float.MaxValue)] |
|||
[InlineData(int.MaxValue, float.MinValue)] |
|||
[InlineData(int.MinValue, float.MaxValue)] |
|||
[InlineData(int.MinValue, float.MinValue)] |
|||
[InlineData(int.MaxValue, 1.0f)] |
|||
[InlineData(int.MinValue, 1.0f)] |
|||
[InlineData(int.MaxValue, -1.0f)] |
|||
[InlineData(int.MinValue, -1.0f)] |
|||
public void DivideTestSizeFloat(int dimension, float divisor) |
|||
{ |
|||
SizeF size = new SizeF(dimension, dimension); |
|||
SizeF expected; |
|||
|
|||
expected = new SizeF(dimension / divisor, dimension / divisor); |
|||
Assert.Equal(expected, size / divisor); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(1111, 2222, -333.33f)] |
|||
public void DivideTestSizeFloatWidthHeightDivisor(int width, int height, float divisor) |
|||
{ |
|||
SizeF size = new SizeF(width, height); |
|||
SizeF expected; |
|||
|
|||
expected = new SizeF(width / divisor, height / divisor); |
|||
Assert.Equal(expected, size / divisor); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(int.MaxValue, int.MinValue)] |
|||
[InlineData(int.MinValue, int.MinValue)] |
|||
[InlineData(int.MaxValue, int.MaxValue)] |
|||
[InlineData(0, 0)] |
|||
public void DeconstructTest(int width, int height) |
|||
{ |
|||
Size s = new Size(width, height); |
|||
|
|||
(int deconstructedWidth, int deconstructedHeight) = s; |
|||
|
|||
Assert.Equal(width, deconstructedWidth); |
|||
Assert.Equal(height, deconstructedHeight); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue