Laravel has Intervention Image, with which you can resize images
// create instance
$img = Image::make('public/foo.jpg');
// resize image to fixed size
$img->resize(300, 200);
Everything is fine if the uploaded image has a size of 3000 x 2000 and we will make 300 x 200 from it.
But if we load images with different heights and widths, and do a 300 x 200 resize, then the images will stretch and look awful.
I need them to be cut a little at the edges.
I have tried using $img->fit(300, 200); but it cuts too much.
Is it possible to do two steps in Intervention Image for resizing, for example, defining the size and removing a few unnecessary parts to make the picture look more or less normal?
To remove a few unnecessary parts you can use the crop() function first and then resize the image maintaining its aspect ration.
$img->crop(width, height, x, y);
This function cuts out a rectangular part of the current image with given width and height. Define optional x,y coordinates to move the top-left corner of the cutout to a certain position. By default the rectangular part will be centered on the current image if you do not provide the x,y co-ordinates.
After cropping the image, you can resize the image maintaining its aspect ration, so the image will not look stretchy or abnormal.
// resize the image to a width of 300 and constrain aspect ratio (auto height)
$img->resize(300, null, function ($constraint) {
$constraint->aspectRatio();
});
// resize the image to a height of 200 and constrain aspect ratio (auto width)
$img->resize(null, 200, function ($constraint) {
$constraint->aspectRatio();
});
However, if you always want the image to be a specific dimension, which is in your case 300 X 200 , You can try the fit function with the callback functionality to retain maximal original image size.
// add callback functionality to retain maximal original image size
$img->fit(300, 200, function ($constraint) {
$constraint->upsize();
});
Related
Note:
The question is specific to PHP GD library only
This question is NOT about how to crop image to a target aspect ratio, rather it is about how to draw overlay extending outside the image
I want to create custom graphic by putting together a background image with a polygon and some texts.
Input background images are of varied dimensions and aspect-ratios, but the final graphic has to be of a fixed (2:1) aspect ratio (it also has to be of pre-defined dimensions, but resizing of image is trivial, so correct aspect ratio is my only target).
Presently I'm cropping-to-fit my input image to target aspect ratio (2:1) by performing max-center area cropping using imagecrop function. Thereafter I draw red polygon on it as shown below (ignore the texts drawn on red band) using imagefilledpolygon method [cropping screenshot below is for demonstration purpose only, it is actually being done programmatically via imagecrop function]
Here's my function that draws the overlay (this function is called after cropping of image to 2:1 aspect ratio is done)
/**
* adds overlay (colored band) on the image
* for better output, overlay must be added before resizing
*/
public function withOverlay(): NotifAdsCreativeGenerator {
// Prepare custom red-color Hex to RGB https://stackoverflow.com/a/15202130/3679900
list($r, $g, $b) = sscanf(self::OVERLAY_COLOR, "#%02x%02x%02x");
$custom_red_color = imagecolorallocate($this->getCrrImage(), $r, $g, $b);
// prepare coordinates for polygon
$coords = [
[0, 0],
[(int) ($this->getCrrWidth() * self::OVERLAY_BEGIN_X_RATIO), 0],
[(int) ($this->getCrrWidth() * self::OVERLAY_END_X_RATIO), $this->getCrrHeight()],
[0, $this->getCrrHeight()]
];
$flattened_coords = array_merge(...$coords);
// draw polygon on image
imagefilledpolygon($this->getCrrImage(), $flattened_coords, count($flattened_coords) / 2, $custom_red_color);
return $this;
}
But what I want is to crop the image to ~ 1.28:1 aspect ratio (the approx ratio of right part of graphic without the red band) and then draw the polygon (extending) outside the image so as to obtain the final graphic in the same same 2:1 aspect ratio as shown below
I'm able to crop image to my desired aspect ratio (1.28:1) but I can't figure out a way to draw the polygon outside the image bounds (effectively expanding the image in the process). Is there a way to do this using PHP-GD library?
It was just a lack of understanding (about working of PHP-GD, available methods) on my part, but the solution is pretty simple
create an empty 'canvas' image of desired dimensions (and the 2:1 target aspect ratio) using imagecreatetruecolor function
(after cropping), copy the image on right side of canvas using imagecopy method (some basic maths has to be done to determine the offset where the image has to be placed on canvas)
now as before, the red polygon can be drawn on the left side on canvas to obtain the final graphic
/**
* adds overlay (colored band) on the image
* for better output, overlay must be added before resizing
*
* This method tries to preserve maximum center region of input image by performing minCenterCrop(.) on it
* before drawing an overlay that extends beyond left border of the cropped image
*
* (since this method incorporates call to 'withMinCenterCrop', calling that method before this is not required
* (and is redundant). For benefits of this method over 'withOverlay', read docstring comment of
* 'withMinCenterCrop' method
* #return NotifAdsCreativeGenerator
*/
public function withExtendedOverlay(): NotifAdsCreativeGenerator {
// perform min center crop to the 1.28:1 aspect ratio (preserve max central portion of image)
$this->withMinCenterCrop();
// this $required_canvas_aspect_ratio calculates to 2.0 (2:1 aspect ratio)
// calculate aspect ratio & dimensions of empty 'canvas' image
// since canvas is wider than min center-cropped image (as space on the left will be occupied by red overlay)
// therefore it's height is matched with cropped image and width is calculated
$required_canvas_aspect_ratio = self::IMAGE_WIDTH / self::IMAGE_HEIGHT;
// height of cropped image
$canvas_height = $this->getCrrHeight();
$canvas_width = $required_canvas_aspect_ratio * $canvas_height;
// create a new 'canvas' (empty image) on which we will
// 1. draw the existing input 'min-cropped' image on the right
// 2. draw the red overlay on the left
$canvas_image = imagecreatetruecolor($canvas_width, $canvas_height);
// copy contents of image on right side of canvas
imagecopy(
$canvas_image,
// cropped image
$this->getCrrImage(),
self::OVERLAY_BEGIN_X_RATIO * $canvas_width,
0,
0,
0,
// dimensions of cropped image
$this->getCrrWidth(),
$this->getCrrHeight()
);
// draw red band overlay on left side of canvas
$this->crr_image = $canvas_image;
return $this->withOverlay();
}
I have a problem with iTextSharp. I have an image of 20000x1000 and I have to put it in a PDF with page size A1 horizontal. The tricky thing is that I need to adjust the height of the image to the PDF and print multiple pages wide in relation to the image.
I tried with this but it generates a PDF with a single page and the image adjusted both width and height.
Rectangle pageSize = PageSize.A1;
Document doc = new Document(pageSize.Rotate(), 10f, 10f, 10f, 10f);
PdfWriter writer = PdfWriter.GetInstance(doc, new FileStream(#"C:\TestFiles\Default.pdf", FileMode.Create));
doc.Open();
Image image = Image.GetInstance(#"C:\TestFiles\image.png");
PdfPTable table = new PdfPTable(1);
table.WidthPercentage = 100;
PdfPCell c = new PdfPCell(image, true);
c.Border = PdfPCell.NO_BORDER;
c.Padding = 5;
c.Image.ScaleAbsoluteHeight(pageSize.Height);
table.AddCell(c);
doc.Add(table);
doc.Close();
Thank you in advance for your help.
Using a table is not the way to go for your requirement.
First let's take a look at how you can scale the image so that the height is adapted to the height of a rotated A1 document:
Image image = Image.GetInstance(#"C:\TestFiles\image.png");
image.ScaleToFit(image.ScaledWidth, PageSize.A1.Width);
The ScaleToFit() method scales an image so that it fits into a rectangle. In this case, we don't want the width to be reduced, so we define the width of that rectangle as equal to the width of the original image. We do want to scale the height so that it fits the height of a rotated A1 page. As we rotate the A1 page, we have to use PageSize.A1.Width instead of PageSize.A1.Height.
Suppose that you have an image that measures 500 x 1500, then the scaled image will have size 500 by 2000 because that image fits a rectangle of 500 by 1684.
Suppose that you have an image that measures 500 x 2000, then the scaled image will be 421 x 1684. The height will be reduced to fit the rotated A1 page, and the width will be reduced accordingly.
Suppose that you have an image of 5000 by 2000, then the scaled image will be 4210 x 1684.
Now we have to add the same image as many times as needed until the complete image is rendered. Note that the image bytes will only be stored once in the PDF: those bytes are reused for every page.
Float offset = 0;
while (offset <= img.ScaledWidth) {
document.NewPage();
img.SetAbsolutePosition(-offset, 0);
document.Add(img);
offset += PageSize.A1.HEIGHT;
}
What happens in the above code snippet? On the first page, we add the image at position (0, 0) which is means that the lower-left corner of the image will coincide with the lower left corner of the page.
If the image fits the page, e.g. in case the width was scaled smaller than the new offset (2384), no new page will be triggered. If the image doesn't fit the page (e.g. because the scaled width is 4210 which is greater than 2384) a new page will be created, and the same image will be added with a new offset (e.g. (-2384, 0)).
Suppose that the width of the scaled image is indeed 4210 and the width of the page is 2384, then the offset after a second page is added will be 4768. That is greater than 4210, so there will be no third page.
Let's assume we have a container with the size of 500 x 300 (w x h).
Inside this container we have a canvas with the same size, but with a different reference system inside it, with the size of 700 x 1000.
When I put an image of 700 x 1000 in this canvas it will obviously appear distorted, because the canvas occupies the entire 500 x 300 pixels of its parent container - even if inside it is still 700 x 1000.
Now, I am trying to figure out a formula to scale the image in the interlal reference system so the image doesn't appear distorted then loaded in the canvas.
Can anybody help?
First of all, if you have w = 700, h = 1000, i.e. w_container / w_canvas = 5/7 != h_container / h_canvas = 3/10, you will not be able to load you image not being distorted and taking entire space of the container. I just can tell you how to resize your image properly.
To save the width-height ratio of your image (equals 7/10, I think this is what you mean under "distorted image") you should calculate how your image's ratio rescales after putting the image on the canvas.
canvas_ratio(7/10) * x = container_ratio(5/3), hence x = 50/21. So if your image has the ratio y, then it will become y*x = y*50/21.
So you should just resize your image before putting it on the canvas such a way that after multiplying this ratio by x it would be 7/10 (the ratio when your image looks perfect). We have an equation y*50/21 = 7/10, hence y = 147/500. That's the ratio your image should have!
For example, you can make image size 294x1000, and after putting it to the canvas it will have height of 300 pixels in your container's coordinate system and 294*(500/700) = 210 pixel width (unfortunately, not 500). Hope this will be useful information for you.
Good luck!
How do you scale down an image until one side reaches it's goal dimension with Carrierwave and rmagick?
Example:
Goal dimensions: 600x400
Picture being uploaded: 700x450
I want this image to be scaled down until the height reaches 400 pixels keeping the original aspect ratio.
That would result in a image with the following dimensions: 622x400
You might take a look at resize_to_limit. From the carrierwave docs:
Resize the image to fit within the specified dimensions while retaining the original aspect ratio. Will only resize the image if it is larger than the specified dimensions. The resulting image may be shorter or narrower than specified in the smaller dimension but will not be larger than the specified values.
So you could do something like this in your uploader:
process :resize_to_fill => [600, 400]
If you don't mind to crop the image, you could go for resize_to_fit instead, and use the gravity value that you desire:
From the RMagick documentation: “Resize the image to fit within the specified dimensions while retaining the original aspect ratio. The image may be shorter or narrower than specified in the smaller dimension but will not be larger than the specified values.“
Edit:
You can read the documentation for these processors for more options on resizing
For a resize_to_min implementation that would only enforce minimum dimensions for your width and height, you can take resize_to_limit as base and just modify the geometry setting to MinimumGeometry to create a custom processor:
process :resize_to_min => [600, 400]
def resize_to_min(width, height)
manipulate! do |img|
geometry = Magick::Geometry.new(width, height, 0, 0, Magick::MinimumGeometry)
new_img = img.change_geometry(geometry) do |new_width, new_height|
img.resize(new_width, new_height)
end
destroy_image(img)
new_img = yield(new_img) if block_given?
new_img
end
end
Use algebra: http://www.algebrahelp.com/lessons/proportionbasics/pg2.htm
Since 622px > 600px, you need to set the width to 600px and calculate the correct height which maintains aspect ratio:
700/450 = 600/x
(700/450)*x = 600
x = 600/(700/450)
x ~= 386
Your desired size is: 600px x 386px
This will fit within the goal dimensions, maximizing size, while maintaining aspect ratio.
I want to resize/scale an image. The originals have not the same dimensions like 300x200 or 512x600. I want to resize the image to 100x100 but DONT crop anything from the image or change ratio. Ideally the image will be first scale the long edge to 100 (aspect ratio) and then fill up the smaller edge with white.
.---------.
|- - - - -|
| IMAGE |
|- - - - -|
'---------'
I dont use Paperclip or Rails, just RMagick.
I've done it with merging the resized image with a new 100x100 image. That's for sure not the best way but it works:
img = Magick::Image.read("file.png").first
target = Magick::Image.new(100, 100) do
self.background_color = 'white'
end
img.resize_to_fit!(100, 100)
target.composite(img, Magick::CenterGravity, Magick::CopyCompositeOp).write("file-small.png)
After playing with it for a while I got Fu86's composite trick to work like so:
img = Image.read("some_file").first().resize_to_fit!(width, height)
target = Image.new(width, height) do
self.background_color = 'white'
end
target.composite(img, CenterGravity, AtopCompositeOp).write("some_new_file")
AtopCompositeOp seems to work better than CopyCompositeOp, which turned part of my background black for some reason.
image = Magick::Image.read("filename").first
resized = image.resize_to_fit(width, height) # will maintain aspect ratio, so one of the resized dimensions may be less than the specified dimensions
resized.background_color = "#FFFFFF" # without a default, background color will vary based on the border of your original image
x = (resized.columns - width) / 2 # calculate necessary translation to center image on background
y = (resized.rows - height) / 2
resized = resized.extent(width, height, x, y) # 'extent' fills out the resized image if necessary, with the background color, to match the full requested dimensions. the x and y parameters calculated in the previous step center the image on the background.
resized.write("new_filename")
Note: on heroku, which as of this posting uses imagemagick 6.5.7-8, I needed to multiply the x and y translations by -1 (and send positive numbers). Version 6.8.0-10 expects negative numbers.
It seems you want to use change_geometry...