Skip to content

Commit

Permalink
Added CloneAndMutate to IMagickImage that can be used to efficiently …
Browse files Browse the repository at this point in the history
…clone and mutate an image (#1577).
  • Loading branch information
dlemstra committed Nov 2, 2024
1 parent c298ab4 commit 76eff36
Show file tree
Hide file tree
Showing 9 changed files with 226 additions and 45 deletions.
35 changes: 1 addition & 34 deletions src/Magick.NET.Core/IMagickImage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace ImageMagick;
/// <summary>
/// Interface that represents an ImageMagick image.
/// </summary>
public partial interface IMagickImage : IDisposable
public partial interface IMagickImage : IMagickImageCreateOperations, IDisposable
{
/// <summary>
/// Event that will be raised when progress is reported by this image.
Expand Down Expand Up @@ -2628,39 +2628,6 @@ public partial interface IMagickImage : IDisposable
/// <exception cref="MagickException">Thrown when an error is raised by ImageMagick.</exception>
void ResetPage();

/// <summary>
/// Resize image to specified size.
/// <para />
/// Resize will fit the image into the requested size. It does NOT fill, the requested box size.
/// Use the <see cref="IMagickGeometry"/> overload for more control over the resulting size.
/// </summary>
/// <param name="width">The new width.</param>
/// <param name="height">The new height.</param>
/// <exception cref="MagickException">Thrown when an error is raised by ImageMagick.</exception>
void Resize(uint width, uint height);

/// <summary>
/// Resize image to specified geometry.
/// </summary>
/// <param name="geometry">The geometry to use.</param>
/// <exception cref="MagickException">Thrown when an error is raised by ImageMagick.</exception>
void Resize(IMagickGeometry geometry);

/// <summary>
/// Resize image to specified percentage.
/// </summary>
/// <param name="percentage">The percentage.</param>
/// <exception cref="MagickException">Thrown when an error is raised by ImageMagick.</exception>
void Resize(Percentage percentage);

/// <summary>
/// Resize image to specified percentage.
/// </summary>
/// <param name="percentageWidth">The percentage of the width.</param>
/// <param name="percentageHeight">The percentage of the height.</param>
/// <exception cref="MagickException">Thrown when an error is raised by ImageMagick.</exception>
void Resize(Percentage percentageWidth, Percentage percentageHeight);

/// <summary>
/// Roll image (rolls image vertically and horizontally).
/// </summary>
Expand Down
11 changes: 11 additions & 0 deletions src/Magick.NET.Core/IMagickImageCloneMutator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright Dirk Lemstra https://github.com/dlemstra/Magick.NET.
// Licensed under the Apache License, Version 2.0.

namespace ImageMagick;

/// <summary>
/// Interface that can be used to efficiently clone and mutate an image.
/// </summary>
public interface IMagickImageCloneMutator : IMagickImageCreateOperations
{
}
43 changes: 43 additions & 0 deletions src/Magick.NET.Core/IMagickImageCreateOperations.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright Dirk Lemstra https://github.com/dlemstra/Magick.NET.
// Licensed under the Apache License, Version 2.0.

namespace ImageMagick;

/// <summary>
/// Interface that represents ImageMagick operations that create a new image.
/// </summary>
public interface IMagickImageCreateOperations
{
/// <summary>
/// Resize image to specified size.
/// <para />
/// Resize will fit the image into the requested size. It does NOT fill, the requested box size.
/// Use the <see cref="IMagickGeometry"/> overload for more control over the resulting size.
/// </summary>
/// <param name="width">The new width.</param>
/// <param name="height">The new height.</param>
/// <exception cref="MagickException">Thrown when an error is raised by ImageMagick.</exception>
void Resize(uint width, uint height);

/// <summary>
/// Resize image to specified geometry.
/// </summary>
/// <param name="geometry">The geometry to use.</param>
/// <exception cref="MagickException">Thrown when an error is raised by ImageMagick.</exception>
void Resize(IMagickGeometry geometry);

/// <summary>
/// Resize image to specified percentage.
/// </summary>
/// <param name="percentage">The percentage.</param>
/// <exception cref="MagickException">Thrown when an error is raised by ImageMagick.</exception>
void Resize(Percentage percentage);

/// <summary>
/// Resize image to specified percentage.
/// </summary>
/// <param name="percentageWidth">The percentage of the width.</param>
/// <param name="percentageHeight">The percentage of the height.</param>
/// <exception cref="MagickException">Thrown when an error is raised by ImageMagick.</exception>
void Resize(Percentage percentageWidth, Percentage percentageHeight);
}
9 changes: 9 additions & 0 deletions src/Magick.NET.Core/IMagickImage{TQuantumType}.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,15 @@ public partial interface IMagickImage<TQuantumType> : IMagickImage, IComparable<
/// <returns>A clone of the current image.</returns>
IMagickImage<TQuantumType> Clone();

/// <summary>
/// Creates a clone of the current image and executes the action that can be used
/// to mutate the clone. This is more efficient because it prevents an extra copy
/// of the image.
/// </summary>
/// <param name="action">The mutate action to execute on the clone.</param>
/// <returns>A clone of the current image.</returns>
IMagickImage<TQuantumType> CloneAndMutate(Action<IMagickImageCloneMutator> action);

/// <summary>
/// Creates a clone of the current image with the specified geometry.
/// </summary>
Expand Down
57 changes: 57 additions & 0 deletions src/Magick.NET/MagickImage.CloneMutator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright Dirk Lemstra https://github.com/dlemstra/Magick.NET.
// Licensed under the Apache License, Version 2.0.

using System;

namespace ImageMagick;

/// <content />
public partial class MagickImage
{
private class CloneMutator : IMagickImageCloneMutator, IDisposable
{
private IntPtr _result = IntPtr.Zero;

public CloneMutator(NativeMagickImage nativeMagickImage)
=> NativeMagickImage = nativeMagickImage;

protected NativeMagickImage NativeMagickImage { get; }

public void Dispose()
{
if (_result != IntPtr.Zero)
NativeMagickImage.DisposeInstance(_result);
}

public IntPtr GetResult()
{
var result = _result;
_result = IntPtr.Zero;
return result;
}

public void Resize(uint width, uint height)
=> Resize(new MagickGeometry(width, height));

public void Resize(IMagickGeometry geometry)
{
Throw.IfNull(nameof(geometry), geometry);

SetResult(NativeMagickImage.Resize(geometry.ToString()));
}

public void Resize(Percentage percentage)
=> Resize(new MagickGeometry(percentage, percentage));

public void Resize(Percentage percentageWidth, Percentage percentageHeight)
=> Resize(new MagickGeometry(percentageWidth, percentageHeight));

protected virtual void SetResult(IntPtr result)
{
if (_result != IntPtr.Zero)
throw new InvalidOperationException("Only a single operation can be executed.");

_result = result;
}
}
}
21 changes: 21 additions & 0 deletions src/Magick.NET/MagickImage.Mutator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright Dirk Lemstra https://github.com/dlemstra/Magick.NET.
// Licensed under the Apache License, Version 2.0.

using System;

namespace ImageMagick;

/// <content />
public partial class MagickImage
{
private sealed class Mutater : CloneMutator
{
public Mutater(NativeMagickImage nativeMagickImage)
: base(nativeMagickImage)
{
}

protected override void SetResult(IntPtr result)
=> NativeMagickImage.Instance = result;
}
}
35 changes: 29 additions & 6 deletions src/Magick.NET/MagickImage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1513,6 +1513,21 @@ public void ClipOutside(string pathName)
public IMagickImage<QuantumType> Clone()
=> new MagickImage(this);

/// <summary>
/// Creates a clone of the current image and executes the action that can be used
/// to mutate the clone. This is more efficient because it prevents an extra copy
/// of the image.
/// </summary>
/// <param name="action">The mutate action to execute on the clone.</param>
/// <returns>A clone of the current image.</returns>
public IMagickImage<QuantumType> CloneAndMutate(Action<IMagickImageCloneMutator> action)
{
using var imageCreator = new CloneMutator(_nativeInstance);
action(imageCreator);

return Create(imageCreator.GetResult(), _settings);
}

/// <summary>
/// Creates a clone of the current image with the specified geometry.
/// </summary>
Expand Down Expand Up @@ -5206,7 +5221,10 @@ public void ResetPage()
/// <param name="height">The new height.</param>
/// <exception cref="MagickException">Thrown when an error is raised by ImageMagick.</exception>
public void Resize(uint width, uint height)
=> Resize(new MagickGeometry(width, height));
{
using var mutator = new Mutater(_nativeInstance);
mutator.Resize(width, height);
}

/// <summary>
/// Resize image to specified geometry.
Expand All @@ -5215,9 +5233,8 @@ public void Resize(uint width, uint height)
/// <exception cref="MagickException">Thrown when an error is raised by ImageMagick.</exception>
public void Resize(IMagickGeometry geometry)
{
Throw.IfNull(nameof(geometry), geometry);

_nativeInstance.Resize(geometry.ToString());
using var mutator = new Mutater(_nativeInstance);
mutator.Resize(geometry);
}

/// <summary>
Expand All @@ -5226,7 +5243,10 @@ public void Resize(IMagickGeometry geometry)
/// <param name="percentage">The percentage.</param>
/// <exception cref="MagickException">Thrown when an error is raised by ImageMagick.</exception>
public void Resize(Percentage percentage)
=> Resize(new MagickGeometry(percentage, percentage));
{
using var mutator = new Mutater(_nativeInstance);
mutator.Resize(percentage);
}

/// <summary>
/// Resize image to specified percentage.
Expand All @@ -5235,7 +5255,10 @@ public void Resize(Percentage percentage)
/// <param name="percentageHeight">The percentage of the height.</param>
/// <exception cref="MagickException">Thrown when an error is raised by ImageMagick.</exception>
public void Resize(Percentage percentageWidth, Percentage percentageHeight)
=> Resize(new MagickGeometry(percentageWidth, percentageHeight));
{
using var mutator = new Mutater(_nativeInstance);
mutator.Resize(percentageWidth, percentageHeight);
}

/// <summary>
/// Roll image (rolls image vertically and horizontally).
Expand Down
6 changes: 1 addition & 5 deletions src/Magick.NET/Native/MagickImage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -670,8 +670,7 @@ private unsafe sealed partial class NativeMagickImage : NativeInstance, INativeM
public partial void Resample(double resolutionX, double resolutionY);

[Throws]
[SetInstance]
public partial void Resize(string geometry);
public partial IntPtr Resize(string geometry);

[Throws]
[SetInstance]
Expand Down Expand Up @@ -875,8 +874,5 @@ private unsafe sealed partial class NativeMagickImage : NativeInstance, INativeM

[Throws]
public partial void WriteStream(IMagickSettings<QuantumType>? settings, ReadWriteStreamDelegate? writer, SeekStreamDelegate? seeker, TellStreamDelegate? teller, ReadWriteStreamDelegate? reader, void* data);

void INativeMagickImage.Dispose(IntPtr instance)
=> Dispose(instance);
}
}
54 changes: 54 additions & 0 deletions tests/Magick.NET.Tests/MagickImageTests/TheCloneAndMutateMethod.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright Dirk Lemstra https://github.com/dlemstra/Magick.NET.
// Licensed under the Apache License, Version 2.0.

using System;
using ImageMagick;
using Xunit;

namespace Magick.NET.Tests;

public partial class MagickImageTests
{
public class TheCloneAndMutateMethod
{
[Fact]
public void ShouldThrowExceptionWhenNoImageIsRead()
{
using var image = new MagickImage();

Assert.Throws<MagickCorruptImageErrorException>(() => image.CloneAndMutate(static mutator => mutator.Resize(50, 50)));
}

[Fact]
public void ShouldThrowExceptionWhenNoActionIsExecuted()
{
using var image = new MagickImage();

Assert.Throws<InvalidOperationException>(() => image.CloneAndMutate(_ => { }));
}

[Fact]
public void ShouldThrowExceptionWhenMultipleActionsAreExecuted()
{
using var image = new MagickImage(Files.Builtin.Logo);

using var clone = image.CloneAndMutate(mutator =>
{
mutator.Resize(100, 100);
Assert.Throws<InvalidOperationException>(() => mutator.Resize(50, 50));
});
}

[Fact]
public void ShouldCloneAndMutateTheImage()
{
using var image = new MagickImage(Files.Builtin.Logo);
using var clone = image.CloneAndMutate(static mutator => mutator.Resize(100, 100));

Assert.NotEqual(image, clone);
Assert.False(ReferenceEquals(image, clone));
Assert.Equal(100U, clone.Width);
Assert.Equal(75U, clone.Height);
}
}
}

0 comments on commit 76eff36

Please sign in to comment.