Resize image keeping aspect ratio and fit in bounds nodejs - node.js

I need to resize images to fit in a specific dimensions. I want to keep aspect ratio.
For example
original image:
w:634
h:975
resize to max:
w:50
h:100
result:
w:50
h:85
I have not found anything that could do that(calculations for w and h)
and I am too dumb to figure it out by myself
copilot suggested me something that just keeps aspect ratio
If you want to use packages. I prefer jimp for image editing.

Jimp.read('image.jpg')
.then((lenna) => {
const isHorizontal = lenna.getWidth() > lenna.getHeight();
const ratio = isHorizontal
? lenna.getWidth() / lenna.getHeight()
: lenna.getHeight() / lenna.getWidth();
const width = 375; // set the width you want
const height = isHorizontal ? width / ratio : width * ratio;
return lenna.resize(width, height).quality(60).write("image.jpg");
})
.catch((err) => {
console.error(err);
});

Calculate the aspect ratio of your original image and of your maximum dimensions. According to that ratios, either take the maximum width or height as fixed and calculate the other side accordingly.
let
oheight = 634, owidth = 975,
mheight = 50, mwidth = 100,
theight, twidth;
let
oratio = owidth/oheight, // (~1.54)
mratio = mwidth/mheight, // (2)
if (mratio > oratio) {
//original image is "higher" so take the maximum height
//and calculate the width accordingly
theight = mheight; //50
twidth = theight * oratio; //77
} else {
//original image is "wider" so take the maximum width
//and calculate the height accordingly
twidth = mwidth;
theight = twidth / oratio;
}
But any decent image processing library will have the functionality, that you can pass in the maximum dimensions and define to keep the aspect ratio and will do these calculations internally ...

Related

How to stretch geometry so its bounding box fits precisely the screen in Three.js

I am looking for the way of stretching a geometry (with all vertices z = 0) into visible screen (HTML Canvas Element).
For now I have worked out how to fit the geometry to the screen, like this:
with following code that basically adjusts camera.z to fit geometry to the height of canvas.
geometry.computeBoundingBox();
const bbox = geometry.boundingBox;
const geometryCenter = bbox.getCenter(new THREE.Vector3());
const geometrySize = bbox.getSize(new THREE.Vector3())
const cameraZ = getZFromGeometrySize(camera.fov, geometrySize);
const scale = getScaleFromZ(height, camera.fov, cameraZ);
const zoomTransform = d3.zoomIdentity
.translate(width * 0.5, height * 0.5)
.scale(scale);
zoom.transform(canvasSelection, zoomTransform);
camera.position.set(geometryCenter.x, geometryCenter.y, cameraZ)
camera.updateProjectionMatrix();
with below definitions of functions:
function getZFromGeometrySize(fov, geometrySize) {
const maxSize = Math.max( geometrySize.x, geometrySize.y );
const halfFOVRadians = toRadians(fov * 0.5);
return maxSize / ( 2 * Math.tan( halfFOVRadians ) );
}
function getScaleFromZ (height, fov, z) {
const halfFOVRadians = toRadians(fov * 0.5);
return height / (2 * Math.tan(halfFOVRadians) * z);
}
This however is using camera position so geometry will fit the view. However, I am looking for the way to stretch the geometry so its bounding box precisely fits the screen, ideally with some predefined padding.
Since this is not related to camera settings I need to manipulate geometry vertices values to stretch it horizontally. How to achieve this? I want to retain values of vertices as they relate to underlying data.
I assume this would need to be a function of canvas dimensions (width, height), geometry coordinates, and camera settings returning new geometry coordinates? Any hint is appreciated.
A short answer to this question is: to set camera's aspect ratio to 1.0.
This will work if geometry bounds are in clip space already [-1, 1 ]. If not they have to be converted to clip space first.

Is there a property to know if an image is vertically or horizontally in Jimp?

I am making a code to put a watermark on images. The only problem is that some images are in vertical mode and others are in horizontal mode. Due to this, vertically images are cropping in a square and adding a black background arround the square.
I have found nothing. I am using image.composite like this: https://github.com/oliver-moran/jimp/issues/175#issuecomment-255878441 but this does not resolve my problem. I resize the image that goes on the background image.
My actual code:
let jimp = require("Jimp")
let images = require("fs").readdirSync("./images")
images.forEach(image => {
let p1 = jimp.read("./images/"+image.split("/").pop());
Promise.all([p1, p2]).then(imgs => {
imgs[1].resize(imgs[0].getWidth() > imgs[0].getHeight() ? imgs[0].getWidth() / 50 * 4.166666666666667 * 1.75 : imgs[0].getHeight() / 50 * 4.166666666666667* 1.75,
imgs[0].getWidth() > imgs[0].getHeight() ? imgs[0].getWidth() / 50 * 4.166666666666667 * 1.75 : imgs[0].getHeight() / 50 * 4.166666666666667* 1.75,
() => {
imgs[0].composite(imgs[1], imgs[0].HORIZONTAL_ALIGN_RIGHT, imgs[0].VERTICAL_ALIGN_BOTTOM).write("./outputs/"+image.split("/").pop());
}
)
}).catch(console.error)
})
This works well on horizontal images but not on vertical.
Any help is appreciated.
Edit: I found that jimp read verticals images as horizontals. I don't know if canvas can do this better.
You can use the img.bitmap.width & img.bitmap.height property in JIMP to get the actual width and height of the image.
const width = item.bitmap.width
const height = item.bitmap.height
if (width > height)
// horizontal
else if (width == height)
// square
else
// vertical
Based on this you can proceed further on the logic

Scale image width and height according to a number

I have a certain amount of squares with the width and height of 40, Is there any way to fit them "perfectly" into an image generated with node-canvas without the image being too big or too small? Also making the image size balanced so that there is not too much squares on the width or too much on the height.
Here is what I have tried:
let b = ((accent.length + other.length + 1) * 40); // the amount of squares * 40 (width/height)
let canvas = new Canvas(b, b);
and:
let b = ((accent.length + other.length + 1) * 40) / 2; // the amount of squares * 40 (width/height)
let canvas = new Canvas(b, b);
But that either just makes the image too big or too small.

Resize image to one resolution

I have a bunch of images, with different resolution.
Also there is a mix of landscape and portrait pictures. I need to resize the images to one resolution (1024x768). If i have a portrait picture, the max height needs to be 768, and my landscape pictures has to have a max width of 1024.
The space that is over, has to be made black.
Right now i use mogrify -resize 1024x768 -verbose *.jpg
I know i can use 1024x!768 , but like i said i'm using different kind of pictures.
My exif information also doesn't contains information about if a picture is landscape or not.
I use ImageMagick for such tasks. When installed, you have the "convert" command, which is very common, and does your task easyly.
You will have to crop the image to get the same aspect ratio, then you can resize the image to get the desired resolution. Example code using nodejs (imagemagick command line tools):
var width = 166;
var height = 117;
var ratio_new = width/height;
var ratio_old = image_file.width_orig/image_file.height_orig;
var pixels_too_much = 0;
var geometry = '';
if (ratio_old > ratio_new)
{
config.debug && console.log ("remove horizontal pixel!");
pixels_too_much = parseInt(image_file.width_orig - (image_file.height_orig * ratio_new))-1;
geometry = parseInt(image_file.height_orig * ratio_new + 0.5) + 'x' + image_file.height_orig;
geometry += "+" + parseInt(pixels_too_much/2) + "+0\!";
}
else if (ratio_old < ratio_new)
{
config.debug && console.log ("remove vertikal pixel");
pixels_too_much = parseInt(image_file.height_orig - (image_file.width_orig / ratio_new));
geometry = image_file.width_orig + 'x' + (image_file.width_orig / ratio_new);
geometry += "+0+" + parseInt(pixels_too_much/2)+"\!";
}
im.convert([image_file.path, '-crop', geometry, '-resize', width + 'x' + height, thumb_path],function(){});

Rotating an Image in Silverlight without cropping

I am currently working on a simple Silverlight app that will allow people to upload an image, crop, resize and rotate it and then load it via a webservice to a CMS.
Cropping and resizing is done, however rotation is causing some problems. The image gets cropped and is off centre after the rotation.
WriteableBitmap wb = new WriteableBitmap(destWidth, destHeight);
RotateTransform rt = new RotateTransform();
rt.Angle = 90;
rt.CenterX = width/2;
rt.CenterY = height/2;
//Draw to the Writeable Bitmap
Image tempImage2 = new Image();
tempImage2.Width = width;
tempImage2.Height = height;
tempImage2.Source = rawImage;
wb.Render(tempImage2,rt);
wb.Invalidate();
rawImage = wb;
message.Text = "h:" + rawImage.PixelHeight.ToString();
message.Text += ":w:" + rawImage.PixelWidth.ToString();
//Finally set the Image back
MyImage.Source = wb;
MyImage.Width = destWidth;
MyImage.Height = destHeight;
The code above only needs to rotate by 90° at this time so I'm just setting destWidth and destHeight to the height and width of the original image.
It looks like your target image is the same size as your source image. If you want to rotate over 90 degrees, your width and height should be exchanged:
WriteableBitmap wb = new WriteableBitmap(destHeight, destWidth);
Also, if you rotate about the centre of the original image, part of it will end up outside the boundaries. You could either include some translation transforms, or simply rotate the image about a different point:
rt.CenterX = rt.CenterY = Math.Min(width / 2, height / 2);
Try it with a piece of rectangular paper to see why that makes sense.
Many thanks to those above.. they helped a lot. I include here a simple example which includes the additional transform necessary to move the rotated image back to the top left corner of the result.
int width = currentImage.PixelWidth;
int height = currentImage.PixelHeight;
int full = Math.Max(width, height);
Image tempImage2 = new Image();
tempImage2.Width = full;
tempImage2.Height = full;
tempImage2.Source = currentImage;
// New bitmap has swapped width/height
WriteableBitmap wb1 = new WriteableBitmap(height,width);
TransformGroup transformGroup = new TransformGroup();
// Rotate around centre
RotateTransform rotate = new RotateTransform();
rotate.Angle = 90;
rotate.CenterX = full/2;
rotate.CenterY = full/2;
transformGroup.Children.Add(rotate);
// and transform back to top left corner of new image
TranslateTransform translate = new TranslateTransform();
translate.X = -(full - height) / 2;
translate.Y = -(full - width) / 2;
transformGroup.Children.Add(translate);
wb1.Render(tempImage2, transformGroup);
wb1.Invalidate();
If the image isn't square you will get cropping.
I know this won't give you exactly the right result, you'll need to crop it afterwards, but it will create a bitmap big enough in each direction to take the rotated image.
//Draw to the Writeable Bitmap
Image tempImage2 = new Image();
tempImage2.Width = Math.Max(width, height);
tempImage2.Height = Math.Max(width, height);
tempImage2.Source = rawImage;
You need to calculate the scaling based on the rotation of the corners relative to the centre.
If the image is a square only one corner is needed, but for a rectangle you need to check 2 corners in order to see if a vertical or horizontal edge is overlapped. This check is a linear comparison of how much the rectangle's height and width are exceeded.
Click here for the working testbed app created for this answer (image below):
double CalculateConstraintScale(double rotation, int pixelWidth, int pixelHeight)
The pseudo-code is as follows (actual C# code at the end):
Convert rotation angle into Radians
Calculate the "radius" from the rectangle centre to a corner
Convert BR corner position to polar coordinates
Convert BL corner position to polar coordinates
Apply the rotation to both polar coordinates
Convert the new positions back to Cartesian coordinates (ABS value)
Find the largest of the 2 horizontal positions
Find the largest of the 2 vertical positions
Calculate the delta change for horizontal size
Calculate the delta change for vertical size
Return width/2 / x if horizontal change is greater
Return height/2 / y if vertical change is greater
The result is a multiplier that will scale the image down to fit the original rectangle regardless of rotation.
**Note: While it is possible to do much of the maths using matrix operations, there are not enough calculations to warrant that. I also thought it would make a better example from first-principles.*
C# Code:
/// <summary>
/// Calculate the scaling required to fit a rectangle into a rotation of that same rectangle
/// </summary>
/// <param name="rotation">Rotation in degrees</param>
/// <param name="pixelWidth">Width in pixels</param>
/// <param name="pixelHeight">Height in pixels</param>
/// <returns>A scaling value between 1 and 0</returns>
/// <remarks>Released to the public domain 2011 - David Johnston (HiTech Magic Ltd)</remarks>
private double CalculateConstraintScale(double rotation, int pixelWidth, int pixelHeight)
{
// Convert angle to radians for the math lib
double rotationRadians = rotation * PiDiv180;
// Centre is half the width and height
double width = pixelWidth / 2.0;
double height = pixelHeight / 2.0;
double radius = Math.Sqrt(width * width + height * height);
// Convert BR corner into polar coordinates
double angle = Math.Atan(height / width);
// Now create the matching BL corner in polar coordinates
double angle2 = Math.Atan(height / -width);
// Apply the rotation to the points
angle += rotationRadians;
angle2 += rotationRadians;
// Convert back to rectangular coordinate
double x = Math.Abs(radius * Math.Cos(angle));
double y = Math.Abs(radius * Math.Sin(angle));
double x2 = Math.Abs(radius * Math.Cos(angle2));
double y2 = Math.Abs(radius * Math.Sin(angle2));
// Find the largest extents in X & Y
x = Math.Max(x, x2);
y = Math.Max(y, y2);
// Find the largest change (pixel, not ratio)
double deltaX = x - width;
double deltaY = y - height;
// Return the ratio that will bring the largest change into the region
return (deltaX > deltaY) ? width / x : height / y;
}
Example of use:
private WriteableBitmap GenerateConstrainedBitmap(BitmapImage sourceImage, int pixelWidth, int pixelHeight, double rotation)
{
double scale = CalculateConstraintScale(rotation, pixelWidth, pixelHeight);
// Create a transform to render the image rotated and scaled
var transform = new TransformGroup();
var rt = new RotateTransform()
{
Angle = rotation,
CenterX = (pixelWidth / 2.0),
CenterY = (pixelHeight / 2.0)
};
transform.Children.Add(rt);
var st = new ScaleTransform()
{
ScaleX = scale,
ScaleY = scale,
CenterX = (pixelWidth / 2.0),
CenterY = (pixelHeight / 2.0)
};
transform.Children.Add(st);
// Resize to specified target size
var tempImage = new Image()
{
Stretch = Stretch.Fill,
Width = pixelWidth,
Height = pixelHeight,
Source = sourceImage,
};
tempImage.UpdateLayout();
// Render to a writeable bitmap
var writeableBitmap = new WriteableBitmap(pixelWidth, pixelHeight);
writeableBitmap.Render(tempImage, transform);
writeableBitmap.Invalidate();
return writeableBitmap;
}
I released a Test-bed of the code on my website so you can try it for real - click to try it
P.S. Yes this is my answer from another question, duplicated exactly, but the question does require the same answer as that one to be complete.

Resources