Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create a resized copy of an image #1577

Closed
toptensoftware opened this issue Mar 7, 2024 · 16 comments
Closed

Create a resized copy of an image #1577

toptensoftware opened this issue Mar 7, 2024 · 16 comments
Milestone

Comments

@toptensoftware
Copy link

toptensoftware commented Mar 7, 2024

Is your feature request related to a problem? Please describe

I have a project that works with very large tif images. I need to keep the large image in memory for later processing, but I also need smaller versions for on-screen rendering. Currently to do this I need to duplicate the large image (doubling the memory usage) and then resize it to the smaller size.

Describe the solution you'd like

Would like the ability to directly create a resized copy of a source image (or part thereof)

Describe alternatives you've considered

As mentioned above, the current solution is to create copy of full image and then downsize it - but this requires more memory than directly resizing to a smaller image.

(Perhaps there's already a way to do this but if so, I couldn't figure out how)

Additional context

No response

@dlemstra
Copy link
Owner

dlemstra commented Mar 8, 2024

In theory this could be possible for methods of ImageMagick that return a new image. It's an interesting idea but I will need to think about an API for this. And that will probably happen after I have added a new source code generator to call the native api.

@toptensoftware
Copy link
Author

Sounds good, I look forward to it.

@dlemstra
Copy link
Owner

Do you have any ideas how I could/should integrate this in the API? I think this behavior would only be required for methods that change the dimensions of an image. Otherwise you could just use .Clone() and change the image. I am now thinking about something like this:

using var image = new MagickImage("input.png");
using var thumbnail = image.NewMethod().Resize(100, 100);
using var thumbnail = image.NewProperty.Resize(100, 100);

The NewMethod() or NewProperty would return an interface that includes a the methods that change the dimensions of the image. But the disadvantage of this would be the discoverability in the api. So I am also thinking about something like this:

using var image = new MagickImage("input.png");
using var thumbnail = image.ResizeWithSuffix(100, 100);

As you can see besides deciding which option to use the naming is also something that I have to think about. Do you have any suggestions?

@toptensoftware
Copy link
Author

Not sure how feasible this is, and I realize this is adding to the original requirement, but the ideal for me would be something like this:

IMagickImage<QuantumType> CloneCropAndResize(IMagickGeometry? crop, IMagickGeometry? resize)

Where:

  • crop specifies which portion of the source image to grab. If null, the entire source image
  • resize specifies how to resize it into a new image instance. If null, no resizing.

ie: it would be the equivalent of the following but internally would combine all this into a single resize operation without the overhead of the full clone at the start.

image.Clone().Crop(crop).Resize(resize);

The nice thing about this is it would let you grab any region at any resolution. ie: you could grab tiled sections of a source image at display resolutions for rendering.

The name CloneCropAndResize is a bit wordy but sums up what it's doing. Alternative suggestion: ExtractImage, or maybe ExtractSubImage?

@dlemstra
Copy link
Owner

Those are not the only two methods this option will become available to. Creating a new method for each possible combination is not feasible.

@toptensoftware
Copy link
Author

toptensoftware commented Mar 26, 2024

Ah ok, I see what you mean.

Perhaps NewMethod() could be CloneThen().

var newImage = image.CloneThen().Resize(100, 100);

I guess the downside to this is it might be tempting to chain these together fluent style, but the result of the Crop call wouldn't be disposed.

var newImage = image
    .CloneThen().Crop(10, 10, 1000, 1000)
    .CloneThen().Resize(100, 100)

Thinking out loud (again no idea if this is feasible) but what if you could put together a chain of operations which you terminated with a function to actually do the work. ie: each intermediate operation just returned an interface (not an image instance) upon which you could perform additional operations. The Process() call at the end walks the chain of operations and produces a final image.

eg:

var newImage = image
    .CloneThen()
    .Crop(10, 10, 1000, 1000)
    .Resize(100, 100)
    .Whatever()
    .SomethingElse()
    .Process();

You're obviously thinking about this from a broader perspective than my narrow view... I'm sure whatever you come up with will be fine.

@alexeygritsenko
Copy link

Good question. But I don’t think that new methods are needed, because it's already supported quite simply:

 using MagickImage image = new MagickImage(inputStream);
 using MagickImage thumbnail = image.Clone() as MagickImage;
 thumbnail.Resize(100, 100);

@dlemstra
Copy link
Owner

dlemstra commented Mar 26, 2024

The Clone() method creates of copy of the pixels of the input image and then resizes that to the new dimensions. Here is an example what it does with the memory (the numbers are made up but should give you an idea of what happens):

flowchart TD
    A[Create image] --> B[10 mb used]
    B --> C[Clone]
    C --> D[20 mb used]
    D --> E[Resize to 50%]
    B --> F[Resize without clone]
    E --> G[15 mb used]
    F --> G
Loading

This means that with the new method the maximum amount of memory that is used is 15mb.

@dlemstra
Copy link
Owner

dlemstra commented Oct 30, 2024

I now have a working prototype that uses the following syntax:

using var image = new MagickImage(Files.Builtin.Logo);
using var clone = image.CloneAndMutate(static mutator => mutator.Resize(100, 100)); // The static keyword is optional

What are your thoughts on this @toptensoftware?

@toptensoftware
Copy link
Author

Hi @dlemstra,

That's pretty nice... I like it. I assume there will be other mutator operations besides just resize or is that it for now?

Brad

@dlemstra
Copy link
Owner

Other operations will be added but you cannot chain them because there are no memory benefits when doing that so you will need to do this:

using var croppedImage = image.CloneAndMutate(static mutator => mutator.Crop(10, 10, 1000, 1000));
using var resizedImage = croppedImage.CloneAndMutate(static mutator => mutator.Resize(100, 100));

@alexeygritsenko
Copy link

@dlemstra hi, does your mutator support a sequence of multiple operations or just one?
Example:
using var thumbnail = image.CloneAndMutate(static mutator => mutator.Crop(10, 10, 1000, 1000).Resize(100, 100));

@dlemstra
Copy link
Owner

In my current proof of concept the mutator will throw an exception when you execute multiple operations. I have done this to avoid complexity in the mutator. There is no memory benefit if I would allow chaining multiple operations. The Crop method always creates a new image. And when I send that image to the Resize method it will also create a new image. And if I would allow multiple operations I would need to cleanup the intermediate image.

@dlemstra dlemstra added this to the 14.1.0 milestone Nov 1, 2024
dlemstra added a commit that referenced this issue Nov 2, 2024
@dlemstra
Copy link
Owner

dlemstra commented Nov 9, 2024

Hey @toptensoftware, I will be going to publish a new release tomorrow or on Monday but I won't be able to include all the operations in the new IMagickImageCreateOperations interface that will be used by .CloneAndMutate. Moving all these actions will take some time. Are there any operations besides Resize that you would want to use right away? If you read this after the release you will probably be going to need to wait for a while.

The methods that will be moved are the ones that have [SetInstance] in this file: https://github.com/dlemstra/Magick.NET/blob/a5920d6b34548452e341a0e280a498e39974630b/src/Magick.NET/Native/MagickImage.cs

@toptensoftware
Copy link
Author

Hi @dlemstra

No urgency at all on this for me. I've got a working solution for the moment, and probably won't get a chance to improve it with these new changes imminently.

Brad

@dlemstra
Copy link
Owner

Closing this because the resize operation has been added in the latest release. More operations will be added in the future.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants