Browse Source

Merge branch 'core' into af/merge-core-2

pull/1087/head
Anton Firszov 6 years ago
parent
commit
0a9356065b
  1. 16
      src/SixLabors.Core/Constants.cs
  2. 34
      src/SixLabors.Core/GeometryUtilities.cs
  3. 21
      src/SixLabors.Core/Memory/AllocationOptions.cs
  4. 85
      src/SixLabors.Core/Memory/ArrayPoolMemoryAllocator.Buffer{T}.cs
  5. 72
      src/SixLabors.Core/Memory/ArrayPoolMemoryAllocator.CommonFactoryMethods.cs
  6. 150
      src/SixLabors.Core/Memory/ArrayPoolMemoryAllocator.cs
  7. 18
      src/SixLabors.Core/Memory/IManagedByteBuffer.cs
  8. 61
      src/SixLabors.Core/Memory/Internals/BasicArrayBuffer.cs
  9. 20
      src/SixLabors.Core/Memory/Internals/BasicByteBuffer.cs
  10. 45
      src/SixLabors.Core/Memory/Internals/ManagedBufferBase.cs
  11. 39
      src/SixLabors.Core/Memory/MemoryAllocator.cs
  12. 31
      src/SixLabors.Core/Memory/SimpleGcMemoryAllocator.cs
  13. 101
      src/SixLabors.Core/Primitives/Matrix3x2Extensions.cs
  14. 288
      src/SixLabors.Core/Primitives/Point.cs
  15. 293
      src/SixLabors.Core/Primitives/PointF.cs
  16. 463
      src/SixLabors.Core/Primitives/Rectangle.cs
  17. 396
      src/SixLabors.Core/Primitives/RectangleF.cs
  18. 296
      src/SixLabors.Core/Primitives/Size.cs
  19. 233
      src/SixLabors.Core/Primitives/SizeF.cs
  20. 234
      tests/SixLabors.Core.Tests/Helpers/DebugGuardTests.cs
  21. 59
      tests/SixLabors.Core.Tests/Helpers/FloatRoundingComparer.cs
  22. 19
      tests/SixLabors.Core.Tests/Helpers/GeometryUtilitiesTests.cs
  23. 248
      tests/SixLabors.Core.Tests/Helpers/GuardTests.cs
  24. 95
      tests/SixLabors.Core.Tests/Helpers/MathFTests.cs
  25. 257
      tests/SixLabors.Core.Tests/Memory/ArrayPoolMemoryAllocatorTests.cs
  26. 25
      tests/SixLabors.Core.Tests/Memory/BufferExtensions.cs
  27. 318
      tests/SixLabors.Core.Tests/Memory/BufferTestSuite.cs
  28. 43
      tests/SixLabors.Core.Tests/Memory/SimpleGcMemoryAllocatorTests.cs
  29. 210
      tests/SixLabors.Core.Tests/Primitives/PointFTests.cs
  30. 258
      tests/SixLabors.Core.Tests/Primitives/PointTests.cs
  31. 286
      tests/SixLabors.Core.Tests/Primitives/RectangleFTests.cs
  32. 336
      tests/SixLabors.Core.Tests/Primitives/RectangleTests.cs
  33. 250
      tests/SixLabors.Core.Tests/Primitives/SizeFTests.cs
  34. 379
      tests/SixLabors.Core.Tests/Primitives/SizeTests.cs

16
src/SixLabors.Core/Constants.cs

@ -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;
}
}

34
src/SixLabors.Core/GeometryUtilities.cs

@ -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);
}
}

21
src/SixLabors.Core/Memory/AllocationOptions.cs

@ -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
}
}

85
src/SixLabors.Core/Memory/ArrayPoolMemoryAllocator.Buffer{T}.cs

@ -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;
}
}
}

72
src/SixLabors.Core/Memory/ArrayPoolMemoryAllocator.CommonFactoryMethods.cs

@ -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);
}
}
}

150
src/SixLabors.Core/Memory/ArrayPoolMemoryAllocator.cs

@ -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);
}
}
}

18
src/SixLabors.Core/Memory/IManagedByteBuffer.cs

@ -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; }
}
}

61
src/SixLabors.Core/Memory/Internals/BasicArrayBuffer.cs

@ -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;
}
}
}

20
src/SixLabors.Core/Memory/Internals/BasicByteBuffer.cs

@ -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)
{
}
}
}

45
src/SixLabors.Core/Memory/Internals/ManagedBufferBase.cs

@ -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();
}
}

39
src/SixLabors.Core/Memory/MemoryAllocator.cs

@ -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()
{
}
}
}

31
src/SixLabors.Core/Memory/SimpleGcMemoryAllocator.cs

@ -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]);
}
}
}

101
src/SixLabors.Core/Primitives/Matrix3x2Extensions.cs

@ -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);
}
}

288
src/SixLabors.Core/Primitives/Point.cs

@ -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));
}
}

293
src/SixLabors.Core/Primitives/PointF.cs

@ -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);
}
}

463
src/SixLabors.Core/Primitives/Rectangle.cs

@ -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);
}
}

396
src/SixLabors.Core/Primitives/RectangleF.cs

@ -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);
}
}

296
src/SixLabors.Core/Primitives/Size.cs

@ -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);
}
}

233
src/SixLabors.Core/Primitives/SizeF.cs

@ -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);
}
}

234
tests/SixLabors.Core.Tests/Helpers/DebugGuardTests.cs

@ -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);
}
}
}

59
tests/SixLabors.Core.Tests/Helpers/FloatRoundingComparer.cs

@ -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);
}
}

19
tests/SixLabors.Core.Tests/Helpers/GeometryUtilitiesTests.cs

@ -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));
}
}

248
tests/SixLabors.Core.Tests/Helpers/GuardTests.cs

@ -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);
}
}
}

95
tests/SixLabors.Core.Tests/Helpers/MathFTests.cs

@ -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));
}
}
}

257
tests/SixLabors.Core.Tests/Memory/ArrayPoolMemoryAllocatorTests.cs

@ -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);
}
}
}

25
tests/SixLabors.Core.Tests/Memory/BufferExtensions.cs

@ -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());
}
}

318
tests/SixLabors.Core.Tests/Memory/BufferTestSuite.cs

@ -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]);
}
}
}
}
}

43
tests/SixLabors.Core.Tests/Memory/SimpleGcMemoryAllocatorTests.cs

@ -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
{
}
}
}

210
tests/SixLabors.Core.Tests/Primitives/PointFTests.cs

@ -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);
}
}
}

258
tests/SixLabors.Core.Tests/Primitives/PointTests.cs

@ -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);
}
}
}

286
tests/SixLabors.Core.Tests/Primitives/RectangleFTests.cs

@ -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);
}
}
}

336
tests/SixLabors.Core.Tests/Primitives/RectangleTests.cs

@ -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);
}
}
}

250
tests/SixLabors.Core.Tests/Primitives/SizeFTests.cs

@ -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);
}
}
}

379
tests/SixLabors.Core.Tests/Primitives/SizeTests.cs

@ -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…
Cancel
Save