Skip to content

Commit 76eff36

Browse files
committed
Added CloneAndMutate to IMagickImage that can be used to efficiently clone and mutate an image (#1577).
1 parent c298ab4 commit 76eff36

File tree

9 files changed

+226
-45
lines changed

9 files changed

+226
-45
lines changed

src/Magick.NET.Core/IMagickImage.cs

Lines changed: 1 addition & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ namespace ImageMagick;
1313
/// <summary>
1414
/// Interface that represents an ImageMagick image.
1515
/// </summary>
16-
public partial interface IMagickImage : IDisposable
16+
public partial interface IMagickImage : IMagickImageCreateOperations, IDisposable
1717
{
1818
/// <summary>
1919
/// Event that will be raised when progress is reported by this image.
@@ -2628,39 +2628,6 @@ public partial interface IMagickImage : IDisposable
26282628
/// <exception cref="MagickException">Thrown when an error is raised by ImageMagick.</exception>
26292629
void ResetPage();
26302630

2631-
/// <summary>
2632-
/// Resize image to specified size.
2633-
/// <para />
2634-
/// Resize will fit the image into the requested size. It does NOT fill, the requested box size.
2635-
/// Use the <see cref="IMagickGeometry"/> overload for more control over the resulting size.
2636-
/// </summary>
2637-
/// <param name="width">The new width.</param>
2638-
/// <param name="height">The new height.</param>
2639-
/// <exception cref="MagickException">Thrown when an error is raised by ImageMagick.</exception>
2640-
void Resize(uint width, uint height);
2641-
2642-
/// <summary>
2643-
/// Resize image to specified geometry.
2644-
/// </summary>
2645-
/// <param name="geometry">The geometry to use.</param>
2646-
/// <exception cref="MagickException">Thrown when an error is raised by ImageMagick.</exception>
2647-
void Resize(IMagickGeometry geometry);
2648-
2649-
/// <summary>
2650-
/// Resize image to specified percentage.
2651-
/// </summary>
2652-
/// <param name="percentage">The percentage.</param>
2653-
/// <exception cref="MagickException">Thrown when an error is raised by ImageMagick.</exception>
2654-
void Resize(Percentage percentage);
2655-
2656-
/// <summary>
2657-
/// Resize image to specified percentage.
2658-
/// </summary>
2659-
/// <param name="percentageWidth">The percentage of the width.</param>
2660-
/// <param name="percentageHeight">The percentage of the height.</param>
2661-
/// <exception cref="MagickException">Thrown when an error is raised by ImageMagick.</exception>
2662-
void Resize(Percentage percentageWidth, Percentage percentageHeight);
2663-
26642631
/// <summary>
26652632
/// Roll image (rolls image vertically and horizontally).
26662633
/// </summary>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Copyright Dirk Lemstra https://github.com/dlemstra/Magick.NET.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
namespace ImageMagick;
5+
6+
/// <summary>
7+
/// Interface that can be used to efficiently clone and mutate an image.
8+
/// </summary>
9+
public interface IMagickImageCloneMutator : IMagickImageCreateOperations
10+
{
11+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright Dirk Lemstra https://github.com/dlemstra/Magick.NET.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
namespace ImageMagick;
5+
6+
/// <summary>
7+
/// Interface that represents ImageMagick operations that create a new image.
8+
/// </summary>
9+
public interface IMagickImageCreateOperations
10+
{
11+
/// <summary>
12+
/// Resize image to specified size.
13+
/// <para />
14+
/// Resize will fit the image into the requested size. It does NOT fill, the requested box size.
15+
/// Use the <see cref="IMagickGeometry"/> overload for more control over the resulting size.
16+
/// </summary>
17+
/// <param name="width">The new width.</param>
18+
/// <param name="height">The new height.</param>
19+
/// <exception cref="MagickException">Thrown when an error is raised by ImageMagick.</exception>
20+
void Resize(uint width, uint height);
21+
22+
/// <summary>
23+
/// Resize image to specified geometry.
24+
/// </summary>
25+
/// <param name="geometry">The geometry to use.</param>
26+
/// <exception cref="MagickException">Thrown when an error is raised by ImageMagick.</exception>
27+
void Resize(IMagickGeometry geometry);
28+
29+
/// <summary>
30+
/// Resize image to specified percentage.
31+
/// </summary>
32+
/// <param name="percentage">The percentage.</param>
33+
/// <exception cref="MagickException">Thrown when an error is raised by ImageMagick.</exception>
34+
void Resize(Percentage percentage);
35+
36+
/// <summary>
37+
/// Resize image to specified percentage.
38+
/// </summary>
39+
/// <param name="percentageWidth">The percentage of the width.</param>
40+
/// <param name="percentageHeight">The percentage of the height.</param>
41+
/// <exception cref="MagickException">Thrown when an error is raised by ImageMagick.</exception>
42+
void Resize(Percentage percentageWidth, Percentage percentageHeight);
43+
}

src/Magick.NET.Core/IMagickImage{TQuantumType}.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,15 @@ public partial interface IMagickImage<TQuantumType> : IMagickImage, IComparable<
4343
/// <returns>A clone of the current image.</returns>
4444
IMagickImage<TQuantumType> Clone();
4545

46+
/// <summary>
47+
/// Creates a clone of the current image and executes the action that can be used
48+
/// to mutate the clone. This is more efficient because it prevents an extra copy
49+
/// of the image.
50+
/// </summary>
51+
/// <param name="action">The mutate action to execute on the clone.</param>
52+
/// <returns>A clone of the current image.</returns>
53+
IMagickImage<TQuantumType> CloneAndMutate(Action<IMagickImageCloneMutator> action);
54+
4655
/// <summary>
4756
/// Creates a clone of the current image with the specified geometry.
4857
/// </summary>
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright Dirk Lemstra https://github.com/dlemstra/Magick.NET.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using System;
5+
6+
namespace ImageMagick;
7+
8+
/// <content />
9+
public partial class MagickImage
10+
{
11+
private class CloneMutator : IMagickImageCloneMutator, IDisposable
12+
{
13+
private IntPtr _result = IntPtr.Zero;
14+
15+
public CloneMutator(NativeMagickImage nativeMagickImage)
16+
=> NativeMagickImage = nativeMagickImage;
17+
18+
protected NativeMagickImage NativeMagickImage { get; }
19+
20+
public void Dispose()
21+
{
22+
if (_result != IntPtr.Zero)
23+
NativeMagickImage.DisposeInstance(_result);
24+
}
25+
26+
public IntPtr GetResult()
27+
{
28+
var result = _result;
29+
_result = IntPtr.Zero;
30+
return result;
31+
}
32+
33+
public void Resize(uint width, uint height)
34+
=> Resize(new MagickGeometry(width, height));
35+
36+
public void Resize(IMagickGeometry geometry)
37+
{
38+
Throw.IfNull(nameof(geometry), geometry);
39+
40+
SetResult(NativeMagickImage.Resize(geometry.ToString()));
41+
}
42+
43+
public void Resize(Percentage percentage)
44+
=> Resize(new MagickGeometry(percentage, percentage));
45+
46+
public void Resize(Percentage percentageWidth, Percentage percentageHeight)
47+
=> Resize(new MagickGeometry(percentageWidth, percentageHeight));
48+
49+
protected virtual void SetResult(IntPtr result)
50+
{
51+
if (_result != IntPtr.Zero)
52+
throw new InvalidOperationException("Only a single operation can be executed.");
53+
54+
_result = result;
55+
}
56+
}
57+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright Dirk Lemstra https://github.com/dlemstra/Magick.NET.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using System;
5+
6+
namespace ImageMagick;
7+
8+
/// <content />
9+
public partial class MagickImage
10+
{
11+
private sealed class Mutater : CloneMutator
12+
{
13+
public Mutater(NativeMagickImage nativeMagickImage)
14+
: base(nativeMagickImage)
15+
{
16+
}
17+
18+
protected override void SetResult(IntPtr result)
19+
=> NativeMagickImage.Instance = result;
20+
}
21+
}

src/Magick.NET/MagickImage.cs

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1513,6 +1513,21 @@ public void ClipOutside(string pathName)
15131513
public IMagickImage<QuantumType> Clone()
15141514
=> new MagickImage(this);
15151515

1516+
/// <summary>
1517+
/// Creates a clone of the current image and executes the action that can be used
1518+
/// to mutate the clone. This is more efficient because it prevents an extra copy
1519+
/// of the image.
1520+
/// </summary>
1521+
/// <param name="action">The mutate action to execute on the clone.</param>
1522+
/// <returns>A clone of the current image.</returns>
1523+
public IMagickImage<QuantumType> CloneAndMutate(Action<IMagickImageCloneMutator> action)
1524+
{
1525+
using var imageCreator = new CloneMutator(_nativeInstance);
1526+
action(imageCreator);
1527+
1528+
return Create(imageCreator.GetResult(), _settings);
1529+
}
1530+
15161531
/// <summary>
15171532
/// Creates a clone of the current image with the specified geometry.
15181533
/// </summary>
@@ -5206,7 +5221,10 @@ public void ResetPage()
52065221
/// <param name="height">The new height.</param>
52075222
/// <exception cref="MagickException">Thrown when an error is raised by ImageMagick.</exception>
52085223
public void Resize(uint width, uint height)
5209-
=> Resize(new MagickGeometry(width, height));
5224+
{
5225+
using var mutator = new Mutater(_nativeInstance);
5226+
mutator.Resize(width, height);
5227+
}
52105228

52115229
/// <summary>
52125230
/// Resize image to specified geometry.
@@ -5215,9 +5233,8 @@ public void Resize(uint width, uint height)
52155233
/// <exception cref="MagickException">Thrown when an error is raised by ImageMagick.</exception>
52165234
public void Resize(IMagickGeometry geometry)
52175235
{
5218-
Throw.IfNull(nameof(geometry), geometry);
5219-
5220-
_nativeInstance.Resize(geometry.ToString());
5236+
using var mutator = new Mutater(_nativeInstance);
5237+
mutator.Resize(geometry);
52215238
}
52225239

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

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

52405263
/// <summary>
52415264
/// Roll image (rolls image vertically and horizontally).

src/Magick.NET/Native/MagickImage.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -670,8 +670,7 @@ private unsafe sealed partial class NativeMagickImage : NativeInstance, INativeM
670670
public partial void Resample(double resolutionX, double resolutionY);
671671

672672
[Throws]
673-
[SetInstance]
674-
public partial void Resize(string geometry);
673+
public partial IntPtr Resize(string geometry);
675674

676675
[Throws]
677676
[SetInstance]
@@ -875,8 +874,5 @@ private unsafe sealed partial class NativeMagickImage : NativeInstance, INativeM
875874

876875
[Throws]
877876
public partial void WriteStream(IMagickSettings<QuantumType>? settings, ReadWriteStreamDelegate? writer, SeekStreamDelegate? seeker, TellStreamDelegate? teller, ReadWriteStreamDelegate? reader, void* data);
878-
879-
void INativeMagickImage.Dispose(IntPtr instance)
880-
=> Dispose(instance);
881877
}
882878
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright Dirk Lemstra https://github.com/dlemstra/Magick.NET.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using System;
5+
using ImageMagick;
6+
using Xunit;
7+
8+
namespace Magick.NET.Tests;
9+
10+
public partial class MagickImageTests
11+
{
12+
public class TheCloneAndMutateMethod
13+
{
14+
[Fact]
15+
public void ShouldThrowExceptionWhenNoImageIsRead()
16+
{
17+
using var image = new MagickImage();
18+
19+
Assert.Throws<MagickCorruptImageErrorException>(() => image.CloneAndMutate(static mutator => mutator.Resize(50, 50)));
20+
}
21+
22+
[Fact]
23+
public void ShouldThrowExceptionWhenNoActionIsExecuted()
24+
{
25+
using var image = new MagickImage();
26+
27+
Assert.Throws<InvalidOperationException>(() => image.CloneAndMutate(_ => { }));
28+
}
29+
30+
[Fact]
31+
public void ShouldThrowExceptionWhenMultipleActionsAreExecuted()
32+
{
33+
using var image = new MagickImage(Files.Builtin.Logo);
34+
35+
using var clone = image.CloneAndMutate(mutator =>
36+
{
37+
mutator.Resize(100, 100);
38+
Assert.Throws<InvalidOperationException>(() => mutator.Resize(50, 50));
39+
});
40+
}
41+
42+
[Fact]
43+
public void ShouldCloneAndMutateTheImage()
44+
{
45+
using var image = new MagickImage(Files.Builtin.Logo);
46+
using var clone = image.CloneAndMutate(static mutator => mutator.Resize(100, 100));
47+
48+
Assert.NotEqual(image, clone);
49+
Assert.False(ReferenceEquals(image, clone));
50+
Assert.Equal(100U, clone.Width);
51+
Assert.Equal(75U, clone.Height);
52+
}
53+
}
54+
}

0 commit comments

Comments
 (0)