Generating square thumbnails in Java by cutting (without destroying aspect ratio)? - java

I'm looking for a way to create a square thumbnail (250px × 250px) in Java without destroying the aspect ratio, that means if the image is rectangular with one side longer than the other it should just cut off whatever doesn't fit in the square. Currently I'm doing this:
public static void createThumbnail(File file, String extension)
throws IOException {
BufferedImage img = new BufferedImage(
250, 250, BufferedImage.TYPE_INT_RGB);
img.createGraphics().drawImage(
ImageIO.read(file).getScaledInstance(
250, 250, Image.SCALE_SMOOTH), 0, 0, null);
ImageIO.write(img, extension, new File(
"./public/images/thumbs/" + file.getName()));
}
However, it is not cutting of parts of the image, instead it is squeezing it to fit inside the 250 × 250 square.

You are using getScaledInstance() which will just expand or shrink your image to fit it in the size you are giving it.
Have a look at getSubimage(). You most probably want to first get a sub image which has the same aspect ratio of your target size (a square), then apply getScaledInstance() on it. This way you just shrink with the same aspect ratio and don't get any squeezing effect.
So something like this should work. Assuming you want to keep the middle part when cropping.
Image getThumbnail(File file) {
BufferedImage original = ImageIO.read(file);
//assuming we want a square thumbnail here
int side = Math.min(original.getWidth(), original.getHeight());
int x = (original.getWidth() - side) / 2;
int y = (original.getHeight() - side) / 2;
BufferedImage cropped = original.getSubimage(x, y, side, side);
return cropped.getScaledInstance(250, 250, Image.SCALE_SMOOTH);
}
(I haven't tried it myself, let me know if there are any problems with it.)
You can then pass it to your drawImage() creating the new rendered BufferedImage, and save it to a file.
BufferedImage img = new BufferedImage(250, 250, BufferedImage.TYPE_INT_RGB);
img.createGraphics().drawImage(getThumbnail(file), 0, 0, null);
ImageIO.write(img, extension, new File("./public/images/thumbs/" + file.getName()));

Related

Why is my BufferedImage different when drawn to canvas?

Original
https://drive.google.com/file/d/1B3xxfWkGsMs2_MQ_bUQ8_ALYI0DL-LIo/view?usp=sharing
When saved to file
https://drive.google.com/file/d/1z5euXupeHmiFebch4A39fVqGukoUiK0p/view?usp=sharing
When printed to canvas
https://drive.google.com/file/d/1VouD-ygf0pPXFFx9Knr4pv44FHMtoqcV/view?usp=sharing
BufferedImage temp = bImg.getSubimage(100, 100, (int)imgWidth - 100, (int)imgHeight - 100);
try{
ImageIO.write(temp, "png", new File("test.png"));
}catch(Exception e){
e.printStackTrace();
}
gc.drawImage(SwingFXUtils.toFXImage(temp, null), 100, 100);
For some reason if I print an image to the canvas, it is different than if I save the same image to a file. When I save it to a file it correctly calculates the subImage but when I print it to the canvas it disregards the x and y coords I give it and takes a subImage using (0,0) as (x,y) with the given width and height.
From the documentation of the getSubimage method:
Returns a subimage defined by a specified rectangular region. The returned BufferedImage shares the same data array as the original image.
The sub-image is just a “window” into the original image; they are using the same pixel data.
The SwingFXUtils.toFXImage documentation states:
Snapshots the specified BufferedImage and stores a copy of its pixels into a JavaFX Image object, creating a new object if needed.
While it would certainly make sense to only copy the pixels in the source image’s dimensions, the above words don’t make it completely clear that it won’t copy the entire pixel data buffer, thus ignoring the boundaries of a sub-image. I would consider this a bug, but I can see where there might be an argument that it’s not.
In the meantime, you can work around this by extracting a sub-image yourself:
BufferedImage cropped = new BufferedImage(
(int) imgWidth - 100,
(int) imgHeight - 100,
bImg.getType());
Graphics g = cropped.getGraphics();
g.drawImage(bImg, -100, -100, null);
g.dispose();
gc.drawImage(SwingFXUtils.toFXImage(cropped, null), 100, 100);

OutOfMemoryError: Jave heap space when jtable saved as Image

Currently I am saving a jtable as jpeg using the below method, when the dimension of the jtable became 2590, 126181, java.lang.OutOfMemoryError: Java heap space exception occurs at "BufferedImage constructor", when the size of the table is small the image gets saved successfully.
public BufferedImage saveComponentAsJPEG(JTable table, String filename) {
Dimension size = table.getSize();
BufferedImage myImage =
new BufferedImage(size.width, size.height,
BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = myImage.createGraphics();
table.paint(g2);
return myImage;
}
How to save a jtable with bigger size in pdf or jpeg image?
Updated Info:
You asked how to "split the JTable into different small images":
As you go through my code below please read my comments, they help explain what is happening and will help you grasp a better understanding of how a JTable/JComponent can be painted to lots of small images. At the heart my code is similar to yours, but there are two key points:
1) Rather than create a single large BufferedImage, I create a single small image that is then used multiple times, therefore leaving a very small memory footprint.
2) With the single image, I use Graphics.translate() to paint a small part of the JTable each time.
The following code was tested with a large JComponents (2590 x 126181) and a tile size of 200x200, and the whole process did not exceed 60mb of memory:
//width = width of tile in pixels, for minimal memory usage try 200
//height = height of tile in pixels, for minimal memory usage try 200
//saveFileLocation = folder to save image tiles
//component = The JComponent to save as tiles
public static boolean saveComponentTiles(int width, int height, String saveFileLocation, JComponent component)
{
try
{
//Calculate tile sizes
int componentWidth = component.getWidth();
int componentHeight = component.getHeight();
int horizontalTiles = (int) Math.ceil((double)componentWidth / width); //use (double) so Math.ceil works correctly.
int verticalTiles = (int) Math.ceil((double)componentHeight / height); //use (double) so Math.ceil works correctly.
System.out.println("Tiles Required (H, W): "+horizontalTiles+", verticalTiles: "+verticalTiles);
//preset arguments
BufferedImage image;
//Loop through vertical and horizontal tiles
//Draw part of the component to the image
//Save image to file
for (int h = 0; h < verticalTiles; h++)
{
for (int w = 0; w < horizontalTiles; w++)
{
//check tile size, if area to paint is smaller than image then shrink image
int imageHeight = height;
int imageWidth = width;
if (h + 1 == verticalTiles)
{
imageHeight = componentHeight - (h * height);
}
if (w + 1 == horizontalTiles)
{
imageWidth = componentWidth - (w * width);
}
image = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_ARGB);
Graphics g = image.getGraphics();
//translate image graphics so that only the correct part of the component is panted to the image
g.translate(-(w * width), -(h * height));
component.paint(g);
//In my example I am saving the image to file, however you could throw your PDF processing code here
//Files are named as "Image.[h].[w]"
//Example: Image 8 down and 2 accross would save as "Image.8.2.png"
ImageIO.write(image, "png", new File(saveFileLocation + "Image." + h +"."+ w + ".png"));
//tidy up
g.dispose();
}
}
return true;
}
catch (IOException ex)
{
return false;
}
}
Just call it like so:
boolean result = saveComponentTiles(200, 200, saveFileLocation, jTable1);
Also if you haven't done it already, you should only call the method from a different thread because it will hang your application when dealing with large components.
If you have not picked a PDF library yet, then I highly recommend looking at iText.
Original Post:
The process you are looking for is quite simple, however it may take some work.
You were on the right track thinking about parts, but as David
mentioned you shouldn't mess with the jTable, instead you will need a
to make use of the TiledImage class, or do something yourself with
RenderedImage and Rasters.
This sort of method basically uses HDD space instead of memory and
lets you create a large image in lots of smaller parts/tiles, then
when its done you can save it all to a single image file.
This answer may also help: https://stackoverflow.com/a/14069551/1270000

getting the desired aspect ratio for BufferedImage

I am using the following codes to resize an image, I can easily get same aspect ratio as the original image by using the getScaledInstance function by setting one parameter to negative, so that it automatically maintains the other paramter to get the aspect ratio. But the problem that I am facing is with the BufferedImage, I cannot set it to the desired aspect ratio because I don't know beforehand what values of height it will generate(width I have set to 500).I have also included the image that I can getting as a result of running the following code. BufferedImage I have set to 800x600 because I have no other option.My previous question was resizing an image without compromising the aspect ratio
public class ResizeImage {
public static void main(String[] args) throws IOException {
BufferedImage img1 = new BufferedImage(800, 600,
BufferedImage.TYPE_INT_RGB);
img1.createGraphics()
.drawImage(
ImageIO.read(
new File(
"C:/Users/Public/Pictures/Sample Pictures/Desert.jpg"))
.getScaledInstance(500, -1, Image.SCALE_SMOOTH),
0, 0, null);
ImageIO.write(img1, "jpg", new File(
"C:/Users/Public/Pictures/Sample Pictures/test/Desert.jpg"));
}
}
You can do the math to get the original image aspect ratio
double ratio = img1.getWidth() / img1.getHeigth();
and invoke the getScaledInstance with this ratio
blabla.getScaledInstance(500, (int)500 / ratio, Image.SCALE_SMOOTH);

How to get small images from big Bufferedimage really fast

I had a BufferedImage an image of size (100mb) pixels 6720x9239 and needed many small images with pixels 60x60
firstly i used this code i found on the net
BufferedImage bi= ImageIO.read(new File(filename));
......
//in paint()
Image b=createImage(new FilteredImageSource(bi.getSource(),
new CropImageFilter(x, y , 60, 60)));
needed to wait around 1 to 5 secs for each small image very slow because i my app needed like 50 images which would mean ppl would have to w8 from 50 to 5*50 sec for panel to reload, so i chan that to
BufferedImage bi= ImageIO.read(new File(filename));
......
//in paint()
BufferedImage a = imageMap.getSubimage(x, y , 60, 60);
Image b = createImage(a.getSource());
feel really happy now had to let the world know this
Oh my god you solved my problem which had stumped me for like 5 days. I had just finished typing out the question and was about to submit it. APPARENTLY (now that I know using Images works) when you use a bufferedImage in g2d.drawImage(Image, at, this) the drawing is MUCH slower than if you use an Image. Something like 50x slower. By converting the BufferedImages to Images (I didn't know you could do that, less that would solve the problem) like this:
Inside the loadBullets function:
BufferedImage a;
Image b;
a = spriteSheetPositive.getSubimage(
//going to use the same image 252 times until I get the motherload of data string converted to the format:
//sprites[shotId]=spriteSheetPositive.getSubimage(x, y, width, height);
520, //x 520,1,536,16 (small lime star) id=100
1, //y
16, //width
15 //height
);
b= createImage(a.getSource());
sprites[shotID]=b;
I can now use images from a spritesheet as projectile sprites with as many as 1,000 on screen at time with no lag! Hooray!
My original question:
This is code within the paint function:
for (int i = 0; i < Projectiles.size(); i++) {
Shot02 m = (Shot02) Projectiles.get(i);
//m.getImage();
// g2d.drawImage(m.getImage(), m.getIntX(), m.getIntY(), this);
AffineTransform at = new AffineTransform();
// 4. translate it to the center of the component
at.translate(m.getDrawX(), m.getDrawY());
// 3. do the actual rotation
at.rotate(m.getAngle()); //rotation is Clockwise
g2d.drawImage(m.getImage(), at, this);
}
I am working on a platformer perspective shooting game. I switched from using a simple imageicon image to a bufferedImage created as a subImage from a sprite sheet. However, as a result of that the program lags with as little as 20 projectiles on screen, whereas previously I could have up to around 1000.
private void loadBullets() {//is done only once, when the window starts
// Get Image
ImageIcon icon = new ImageIcon(this.getClass().getResource("AbsoluteWin\\CustomShotsPositive.png"));
Image image = icon.getImage();
// Create empty BufferedImage, sized to Image
BufferedImage spriteSheetPositive =
new BufferedImage(
image.getWidth(null),
image.getHeight(null),
Transparency.BITMASK);
// Draw Image into BufferedImage
Graphics g = spriteSheetPositive.createGraphics();
g.drawImage(image, 0, 0, null);
int shotID = 1;
System.out.println(shotID);
while (shotID <= length) {//fills the array with the bullets from the sprite sheet spriteSheetPositive
sprites[shotID] = spriteSheetPositive.getSubimage(
//going to use the same image 252 times until I get the coordinates for all the other sub-images
//sprites[shotId]=spriteSheetPositive.getSubimage(x, y, width, height);
520, //x 520,1,536,16 (small lime star) id=100
1, //y
16, //width
15 //height
);
shotID += 1;
}
System.out.println(shotID);
}
Shot02 Class:
ImageIcon ii =
new ImageIcon(this.getClass().getResource("missile.png"));
image = ii.getImage();
//image=Destination.sprites[100];//is the source
This is the code in the Shot02 Class that controls the what image the bullets use. Again, if I uncomment the second option and use the BufferedImages, the program slows down like crazy.

java image crop

I am aware of BufferedImage.getSubimage However, it cant deal with cropping images that are smaller than the cropping size throwing the exception:
java.awt.image.RasterFormatException: (y + height) is outside raster
I want to be able to crop either a PNG/JPG/GIF to a certain size however if the image is smaller than the cropping area centre itself on a white background. Is there a call to do this? Or do I need to create an image manually to centre the image on if so, how would I go about this?
Thanks
You cannot crop an image larger, only smaller. So, you start with the goal dimension,let's say 100x100. And your BufferedImage (bi), let's say 150x50.
Create a rectangle of your goal:
Rectangle goal = new Rectangle(100, 100);
Then intersect it with the dimensions of your image:
Rectangle clip = goal.intersection(new Rectangle(bi.getWidth(), bi.getHeight());
Now, clip corresponds to the portion of bi that will fit within your goal. In this case 100 x50.
Now get the subImage using the value of clip.
BufferedImage clippedImg = bi.subImage(clip,1, clip.y, clip.width, clip.height);
Create a new BufferedImage (bi2), the size of goal:
BufferedImage bi2 = new BufferedImage(goal.width, goal.height);
Fill it with white (or whatever bg color you choose):
Graphics2D big2 = bi2.getGraphics();
big2.setColor(Color.white);
big2.fillRect(0, 0, goal.width, goal.height);
and draw the clipped image onto it.
int x = goal.width - (clip.width / 2);
int y = goal.height - (clip.height / 2);
big2.drawImage(x, y, clippedImg, null);

Categories