I wonder if anybody can help me with the math/pseudo code/java code to scale an image to a target dimension. the requirement is to keep the aspect ratio, but not falling below the target dimension on both x and y scales. the final calculated dimension can be greater than the requested target but it needs to be the the closest one to the target.
example:
I have an image that is 200x100. it needs to be scaled down to a target dimension 30x10.
i need to find the minimal dimension that keeps the aspect ratio of the origin where both x and y scales are at least what is specified in the target.
in our example, 20x10 is not good because the x scale fell below the target (which is 30).
the closest one would be 30x15
Thank you.
targetRatio = targetWidth / targetHeight;
sourceRatio = sourceWidth / sourceHeight;
if(sourceRatio >= targetRatio){ // source is wider than target in proportion
requiredWidth = targetWidth;
requiredHeight = requiredWidth / sourceRatio;
}else{ // source is higher than target in proportion
requiredHeight = targetHeight;
requiredWidth = requiredHeight * sourceRatio;
}
This way your final image :
always fits inside the target whereas not being cropped.
keeps its original aspect ratio.
and always has either the width or height (or both) exactly matching the target's.
Well in your example you kind off already used the algorithm you're looking for.
I will use the example you have given.
Original Target
200 x 100 -> 30 x 10
1. You take the bigger value of the target dimensions (in our case 30)
2. Check if its smaller than the corresponding original width or height
2.1 If its smaller define this as the new width (So 30 is the new width)
2.2 If its not smaller check the other part
3. Now we have to calculate the height which is simply the (30/200)*100
So as result you get like you wrote: 30 x 15
Hope this was clear :)
In the coding part you could use the BufferedImage and simply create a new BufferedImage with the correct scale value like that.
BufferedImage before = getBufferedImage(encoded);
int w = before.getWidth();
int h = before.getHeight();
BufferedImage after = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
AffineTransform at = new AffineTransform();
at.scale(2.0, 2.0); // <-- Here you should use the calculated scale factors
AffineTransformOp scaleOp =
new AffineTransformOp(at, AffineTransformOp.TYPE_BILINEAR);
after = scaleOp.filter(before, after);
Related
double degPi = degrees * Math.PI / 180;
double a = Math.cos(degPi)*tImgCover.getScaledHeight();
double b = Math.sin(degPi)*tImgCover.getScaledWidth();
double c = -Math.sin(degPi) * tImgCover.getScaledHeight();
double d = Math.cos(degPi)* tImgCover.getScaledWidth();
double e = absX;
double f = absY;
contentByte.addImage(imgae, a, b, c, d, e, f);/*add image*/
How to rotate around the image center by itext?
If we have an Image image and coordinates x, y, we can draw the image without rotation with its lower left corner at the given coordinates like this
contentByte.addImage(image, image.getWidth(), 0, 0, image.getHeight(), x, y);
A bitmap image from the resources has a size of 1x1 with the coordinate origin at its lower left. Thus, this operation stretches the image to its correct size and moves it so its lower left is at the given coordinates.
If we want to draw the same image as if the one drawn above was rotated around its center by an angle rotate, therefore, we can do this by moving the 1x1 image so that the origin is in its center, stretch it to its correct size, rotate it, and then move the origin (which still is at the center of the rotated image) to the center of the unrotated image. These operations are easier to express using AffineTransform instances (from package com.itextpdf.awt.geom) instead number tupels. Thus:
// Draw image as if the previous image was rotated around its center
// Image starts out being 1x1 with origin in lower left
// Move origin to center of image
AffineTransform A = AffineTransform.getTranslateInstance(-0.5, -0.5);
// Stretch it to its dimensions
AffineTransform B = AffineTransform.getScaleInstance(image.getWidth(), image.getHeight());
// Rotate it
AffineTransform C = AffineTransform.getRotateInstance(rotate);
// Move it to have the same center as above
AffineTransform D = AffineTransform.getTranslateInstance(x + image.getWidth()/2, y + image.getHeight()/2);
// Concatenate
AffineTransform M = (AffineTransform) A.clone();
M.preConcatenate(B);
M.preConcatenate(C);
M.preConcatenate(D);
//Draw
contentByte.addImage(image, M);
(AddRotatedImage.java test method testAddRotatedImage)
For example drawing both images using
int x = 200;
int y = 300;
float rotate = (float) Math.PI / 3;
results in something like this:
With a Flip
The OP asked in a comment
how to add rotate and flip image?
For this you simply insert a mirroring affine transformation into the sequence of transformations above.
Unfortunately the OP did not mention which he meant a horizontal or a vertical flip. But as changing the rotation angle accordingly transforms one in the other, that isn't really necessary, either.
// Draw image as if the previous image was flipped and rotated around its center
// Image starts out being 1x1 with origin in lower left
// Move origin to center of image
AffineTransform A = AffineTransform.getTranslateInstance(-0.5, -0.5);
// Flip it horizontally
AffineTransform B = new AffineTransform(-1, 0, 0, 1, 0, 0);
// Stretch it to its dimensions
AffineTransform C = AffineTransform.getScaleInstance(image.getWidth(), image.getHeight());
// Rotate it
AffineTransform D = AffineTransform.getRotateInstance(rotate);
// Move it to have the same center as above
AffineTransform E = AffineTransform.getTranslateInstance(x + image.getWidth()/2, y + image.getHeight()/2);
// Concatenate
AffineTransform M = (AffineTransform) A.clone();
M.preConcatenate(B);
M.preConcatenate(C);
M.preConcatenate(D);
M.preConcatenate(E);
//Draw
contentByte.addImage(image, M);
(AddRotatedImage.java test method testAddRotatedFlippedImage)
The result with the same image as above:
With Interpolation
The OP asked in a yet another comment
How anti aliasing ?
The iText Image class knows an Interpolation property. By setting it to true (before adding the image to the document, obviously),
image.setInterpolation(true);
low resolution images are subject to interpolation when drawn.
E.g. using a 2x2 image with differently colored pixels instead of the image of Willi, you get the following results, first without interpolation, then with interpolation:
Confer the AddRotatedImage.java test testAddRotatedInterpolatedImage which adds this image:
Beware: iText Image property Interpolation effectively sets the Interpolate entry in the PDF image dictionary. The PDF specification notes in this context:
NOTE A conforming Reader may choose to not implement this feature of PDF, or may use any specific implementation of interpolation that it wishes.
Thus, on some viewers interpolation may occur differently than in your viewer, maybe even not at all. If you need a specific kind of interpolation on every viewer, upscale the image with the desired amount of interpolation / anti-aliasing before loading it into an iText Image.
public static BufferedImage rotateClockwise90( BufferedImage inputImage ){
int width = inputImage.getWidth();
int height = inputImage.getHeight();
BufferedImage returnImage = new BufferedImage( height, width , inputImage.getType() );
for( int x = 0; x < width; x++ ) {
for( int y = 0; y < height; y++ ) {
returnImage.setRGB( height-y-1, x, inputImage.getRGB( x, y ) );
}
}
return returnImage;
}
I'm trying to draw a NinePatch using a transform matrix so it can be scaled, rotated, moved etc. So I created a class that inherits from LibGDX's NinePatch class and which is responsible of the matrix.
This is how I compute my transform matrix (I update it each time one of the following values changes) :
this.transform
.idt()
.translate(originX, originY, 0)
.rotate(0, 0, 1, rotation)
.scale(scale, scale, 1)
.translate(-originX, -originY, 0)
;
and how I render my custom NinePatch class :
drawConfig.begin(Mode.BATCH);
this.oldTransform.set(drawConfig.getTransformMatrix());
drawConfig.setTransformMatrix(this.transform);
this.draw(drawConfig.getBatch(), this.x, this.y, this.width, this.height); // Libgdx's NinePatch#draw()
drawConfig.setTransformMatrix(this.oldTransform);
Case 1
Here's what I get when I render 4 nine patches with :
Position = 0,0 / Origin = 0,0 / Scale = 0.002 / Rotation = different for each 9patch
I get what I expect to.
Case 2
Now the same 4 nine patches with :
Position = 0,0 / Origin = 0.5,0.5 / Scale = same / Rotation = same
You can see that my 9 patches aren't draw at 0,0 (their position) but at 0.5,0.5 (their origin), like if I had no .translate(-originX, -originY, 0) when computing the transform matrix. Just to be sure, I commented this instruction and I indeed get the same result. So why is my 2nd translation apparently not taken into account?
The problem is probably your scaling. Because it also scales down the translation, your seccond translate actually translates (-originX*scale, -originY*scale, 0) since scale=0.002, it looks like there is no translate at all. For instance for the x coordinate, you compute :
x_final = originX + scale * (-originX + x_initial)
I had to change the code computing my transform matrix to take the scale into account when translating back as pointed by Guillaume G. except my code is different from his :
this.transform
.idt()
.translate(originX, originY, 0)
.rotate(0, 0, 1, rotation)
.scale(scale, scale, 1)
.translate(-originX / scale, -originY / scale, 0);
;
I have a problem with resizing an image.
First of all at the start I have an image for example in 1920x1080. The user input is the percentage of resizing the current image, for example when input is 25% the final resolution of the image is 960x540.
Explanation:
1920x1080 = 2 073 600
25% from 2 073 600 = 518 400
960x540 = 518 400
I already have the function for resizing an image , but i dont know how to CALCULATE the values WIDTH and HEIGHT for an resized image.
I the example it is 960x540. (if it can help you, images has ratio 3:2,4:3,16:9 ....)
Any help ?
You have the following information given to you:
width = 1920
height = 1080
area = 2073600
ratio = width / height =~ 1.77778
Now, suppose for example that you want to calculate a new width and height when the area shrinks to 25% of its current size. Well then we know the following things:
area = 0.25 * 2073600 = 518400
height = h (Variable, because it is unknown at the moment)
width = w (Variable, because it is unknown at the moment)
ratio = 1.77778 (Ratio should stay the same or image becomes warped/stretched)
So you have
w / h = 1.77778 (Your unknown new width divided by unknown height equals ratio)
w * h = 518400 (Your unknown width times unknown height equals area)
Now you can solve it relatively easily mathematically. It is simply two equations with two variables.
w = 1.77778 * h (from first equation)
(1.77778 * h) * h) = 518400 (by plugging above into second equation)
h =~ 540
w =~ 960
Does that make sense?
How could I resize an image and still keep it's aspect ratio?
This is the method that I use :
private static BufferedImage resizeImage(BufferedImage originalImage,
int type) {
BufferedImage resizedImage = new BufferedImage(IMG_WIDTH, IMG_HEIGHT,
type);
Graphics2D g = resizedImage.createGraphics();
g.drawImage(originalImage, 0, 0, IMG_WIDTH, IMG_HEIGHT, null);
g.dispose();
return resizedImage;
}
The type variable :
BufferedImage original = ImageIO.read(new File(imagePath));
int type = original.getType() == 0 ? BufferedImage.TYPE_INT_ARGB
: original.getType();
The problem is that some images are correctly resized but others lose their aspect ratio because of the IMG_WIDTH and IMG_HEIGHT.
Is there a way to get the original image dimensions and then apply some kind of proportion resize to maintain the aspect ratio of the resized image?
Why don't you use originalImage.getWidth() and originalImage.getHeight()? Then you can easily calculate aspect ratio. Don't forget that int/int = int, so you need to do
double ratio = 1.0 * originalImage.getWidth() / originalImage.getHeight();
or
double ratio = (double) originalImage.getWidth() / originalImage.getHeight();
Regarding the additional math, you can calculate
int height = (int) IMG_WIDTH/ratio;
int width = (int) IMG_HEIGHT*ratio;
Then see which one fits your needs better and resize to (IMG_WIDTH, height) or (width, IMG_HEIGHT)
To get the image size, see getWidth()/getHeight(). The rest is just some relatively simple math.
Presuming the IMG_WIDTH & IMG_HEIGHT represent the largest size desired:
Find which is going to hit the limit first.
Calculate the ratio between the natural size and that maximum size.
Multiply the other image dimension by the same ratio.
If I was given color A and color B, how can one go aboit generating a gradient on a canvas which can be later converted to a bitmap.
Such that
public Bitmap makeGradient(Color from, Color to){}
Would actually work?
I hope this is not too vague. I thankyou for your time and effort.
Ps. There is a question on stackoverflow that answers this but I amstill confused :(
Here it is: Generating gradients programmatically?
One way to go about creating a radial gradient might be to define the focus point as well as the extent of the gradient and when you generate the image you'd calculate the distance between the current pixel and the focus point, divide it by the gradient extent and clip the result to 1. Then use the formula in the question you linked.
Something like this pseudocode:
double d = distance(currentPixel, focusPoint); //I'll leave the implementation for you
double factor = Math.max(1.0, d/extent);
int red = (int) (firstCol.getRed() * factor + secondCol.getRed() * (1.0 - factor) );
int green= (int) firstCol.getGreen() * factor + secondCol.getGreen()* (1.0 - factor) );
int blue = (int) (firstCol.getBlue() * factor + secondCol.getBlue()* (1.0 - factor) );
This would mean that the farther a pixel is from the focus point the more firstCol will contribute to it (pixels that are outside the extent of the gradient will only use firstCol since factor should be 1.0 for those).