coobird / thumbnailator

Thumbnailator - a thumbnail generation library for Java
MIT License
5.17k stars 789 forks source link

Crop image using Thumbnailator starting at x,y and size of height,width #86

Open checklist opened 8 years ago

checklist commented 8 years ago

I need to crop an image starting at position x,y and it should be height,width in size (starting at x,y). I have the following code:

Thumbnails .of(url) .size(width, height) .crop(new Coordinate(x,y)) .toOutputStream(os); ; But it does not seem to be working. Any ideas?

coobird commented 7 years ago

Sorry that this is a very late reply, I'd like to get more information.

When you mention that "it does not seem to be working", could you describe what exactly you encountered, and what your expectations are?

I suspect this may be a misunderstanding of the crop functionality, which is basically a way to enforce an certain output dimensions (which in turn has a certain aspect ratio) from input images which have differing aspect ratios.

For example, say we want to create thumbnails which are 100 x 100 (hence, we use size(100, 100)) from images which can be a variety of sizes and aspect ratios, like 200 x 100, 200 x 400, etc. In this situation, we could use something like Positions.CENTER, and crop out parts that overhang after resizing to 100 x 100 while keeping the aspect ratio (which is what the size method does.)

In that case:

In general, using a Coordinate as a parameter for crop would probably lead to some unexpected results. This behavior may be a little bit difficult to understand from the explanation of the crop method, so it might need some work to clarify that.

xak2000 commented 4 years ago

@coobird Thanks for the explanation!

Yes, this is how .crop(Position) works and this is definitely useful behavior. But what if I want just a simple crop, like the author of the issue?

By simple crop I mean just a crop from X1, Y1 to X2, Y2 coordinates of original image. Without any resize at all.

Example:

This is useful when you let user to select cropping area on his image to focus on something important, like a face. :-) When user does this, we need to first crop the original image exactly at the user-provided coordinates, and only then resize it to thumbnail size.

Any advice to achieve that with this library? Custom filter maybe?

xak2000 commented 4 years ago

Nevermind, I found a solution!

.sourceRegion(int x, int y, int width, int height) FTW!

It does exactly what I described. And I can even do source cropping and .crop() cropping in one step, like:

    BufferedImage dstBufferedImage = Thumbnails.fromImages(Collections.singleton(srcImage.getBufferedImage()))
            .sourceRegion(180, 0, 413 - 180, 348)
            .crop(Positions.CENTER)
            .size(dstWidth, dstHeight)
            .asBufferedImage();

And if I need to just simple cropping (described earlier) without resize, I can do this:

    BufferedImage dstBufferedImage = Thumbnails.fromImages(Collections.singleton(srcImage.getBufferedImage()))
            .sourceRegion(180, 0, 413 - 180,348)
            .resizer(Resizers.NULL)
            .scale(1.0)
            .asBufferedImage();

The tricky part here is that you need to set resizer to Resizers.NULL and scale to some value, because without scale the builder complains on size is not set, even with Resizers.NULL resizer.

I understand the confusion of issue creator as I was confused too, because .crop() method does not exactly what is usually called crop in graphic editors like Gimp. It does more: resize and then crop. I glad I found .sourceRegion() method. :) Thank you anyway for the good library!

coobird commented 4 years ago

The tricky part here is that you need to set resizer to Resizers.NULL and scale to some value, because without scale the builder complains on size is not set, even with Resizers.NULL resizer.

I would have thought that using scale(1.0) would be enough, and that Resizers.NULL wouldn't be necessary... (It's been a while since I've actually played around that part of the code base.)

Did you find that the final image's dimensions (cropped image) were the same as the original image? (That's the only reason I can guess that would require Resizers.NULL as a workaround.)

I understand the confusion of issue creator as I was confused too, because .crop() method does not exactly what is usually called crop in graphic editors like Gimp. It does more: resize and then crop. I glad I found .sourceRegion() method.

Yeah, I probably should add more examples since cropping (and some other operations) are not very straightforward... (Many things were bolted on, so there's some rough edges..)

Right now, cropping and source region are only explained in detail in the Changes page, which it not very intuitive..

Thanks for comments!

xak2000 commented 4 years ago

Honestly, I'm not sure why Resizers.NULL is required. I stole this "hack" from my old project. Maybe setting scale is really enough, and I did set Resizers.NULL just to be 100% sure that no processing other than I explicitly set will consume any CPU resources.

In my old project it looks like this:

    BufferedImage dstBufferedImage = Thumbnails.fromImages(Collections.singleton(srcImage.getBufferedImage()))
            .resizer(Resizers.NULL)
            .scale(1.0)
            .watermark(watermarkPosition, scaledWatermark, watermarkOpacity)
            .asBufferedImage();

The idea here is to just watermark the image without any resizing. But yes, maybe scale(1.0) is enough actually.

Thanks to pointing me to Changes page. Didn't know it even has images to illustrate the behavior of some methods! Behavior of some methods is really hard to understand for me even after reading their javadocs. It's funny that Changelog is more informative than documentation. :)