Any idea how to make the Java Swing file chooser
look better on 2K displays where the windows
font scaling is > 125%?
I am using ordinary code such as:
JFileChooser fc = new JFileChooser();
if (settings.currentdir != null)
fc.setCurrentDirectory(new File(settings.currentdir));
int returnVal = fc.showOpenDialog((Window) holder);
if (returnVal == JFileChooser.APPROVE_OPTION) {
But the file chooser is only displaying tiny
icons for the listed files and directories. I am using
JDK 8. What is going wrong?
P.S.: Scope of the question is only Windows, not
Unixes. On Windows, the two default L&F, they
scale the font. But they don't scale icons. The
application has to do that, since it might use
a different bitmap resources for higher scales.
It seems that JFileChooser is not coded this way.
But it might be that the JFileChooser can be
instructed to do so. I don't see that the
other question addresses icon size and the
JFileChooser on Windows: How to set the DPI of Java Swing apps on Windows/Linux? The
other question deals with font size, which is
not an issue for the JFileChooser on Windows with
one of the two Windows L&F.
Just a quick idea while i came across this thread. You can try to deliver your own iconset:
new JFileChooser().setFileView(new FileView() {
#Override
public Icon getIcon(File f) {
return fancy2kIconForExtension(StringUtils.substringAfterLast("."));
}
});
be careful to load your Icons from a Cache, as this method is called very often from inside JFileChooser, otherwise you end up reloading icon all the time.
I very recently ran into same problem. the only work around is not using java build in ImageIcon class but to write one yourself,
This one took the provided image, scale it to fit current component size and paint it. I tried to make is as simple as possible and as close to original class as able, but its not perfect and need improvement, especially in component-icon alignment
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import javax.swing.AbstractButton;
import javax.swing.ImageIcon;
/**
*
* #author Rastislav
*/
public class ScaleToFitAndAntialiasIcon extends ImageIcon{
private ImageIcon icon;
public ScaleToFitAndAntialiasIcon(ImageIcon icon)
{
this.icon = icon;
}
public int getIconWidth()
{
return icon.getIconWidth();
}
public int getIconHeight()
{
return icon.getIconHeight();
}
#Override
public void paintIcon(Component c, Graphics g, int x, int y)
{
Graphics2D g2d = (Graphics2D)g.create();
AffineTransform at = g2d.getTransform();
double scaleToFit = ((double)c.getHeight() / (double)icon.getIconHeight());
if((int)icon.getIconHeight()*scaleToFit == c.getHeight()){
scaleToFit = ((double)c.getHeight() / (double)icon.getIconHeight()) - 0.1;
}
AffineTransform scaled = AffineTransform.getScaleInstance(scaleToFit, scaleToFit);
at.concatenate( scaled );
g2d.setTransform( at );
//need improvement
/* int lineupMinus = (int)((double)icon.getIconWidth() *((double)c.getHeight() / (double)icon.getIconHeight()));
int lineup = (int)((double)icon.getIconWidth() * scaleToFit);
int ff = (int)(lineupMinus - lineup);
System.out.println(ff);
*/
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
//improved code goes here
icon.paintIcon(c, g2d, x, 4);
if(c instanceof AbstractButton a){
a.setIconTextGap((int)(-icon.getIconWidth()/2));
}
g2d.dispose();
}
}
Related
I'm trying to set the icon of a Java AWT application so it renders in native resolution on the Windows 10 taskbar (including when desktop scaling is set above 100%). It seems that by default, if an executable embeds an icon containing multiple sizes, Windows seems to pick a size larger than the actual size of taskbar icons and downsize it (at 100% scale it resizes the 32 pixel icon to 24, even if a 24 pixel icon is supplied, and similarly for other scales.)
I've solved this problem for C++ MFC applications by loading just the correctly sized icon as a resource and sending a WM_SETICON message to the window, which results in a nice sharp icon on the taskbar and alt-tab dialog.
smallIcon = (HICON)LoadImage( myInstance, MAKEINTRESOURCE(smallIconRes), IMAGE_ICON, smallIconSize, smallIconSize, LR_DEFAULTCOLOR );
SendMessage(hWnd, WM_SETICON, ICON_SMALL, (LPARAM)smallIcon);
bigIcon = (HICON)LoadImage( myInstance, MAKEINTRESOURCE(bigIconRes), IMAGE_ICON, bigIconSize, bigIconSize, LR_DEFAULTCOLOR );
SendMessage(hWnd, WM_SETICON, ICON_BIG, (LPARAM)bigIcon);
That approach doesn't seem to work for Java applications - a WM_SETICON message with wParam set to ICON_SMALL works fine, but the equivalent with ICON_BIG is ignored.
If I try to use Java's API to set the icon, by doing this
List<Image> icons = new ArrayList<Image>();
icons.add(windowIcons.getIcon(20)); // small icons are 20x20 pixels
icons.add(windowIcons.getIcon(30)); // large are 30x30 at 125% scale
setIconImages(icons);
the correct icon is used but it appears blurry, as if something has resized it to the "expected" size and then resized it back. Left here is how it appears, right is the contents of the icon file.
So, my question is: what can I do in this Java application to make Windows render the icon I give it on the taskbar without scaling it and blurring the details?
There is indeed a scaling function called getScaledIconImage() in sun.awt.SunToolkit which is is always used when setting the icons. You must bypass this function in order to get an unaliased icon. So what you need is a replacement for java.awt.Window.setIconImages() method.
Provided several icon images Icon16x16.png, Icon24x24.png, etc. This is an example of a customSetIconImages() which puts a crisp 24x24 pixels icon in the taskbar of Windows 10.
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import javax.swing.ImageIcon;
import java.awt.peer.WindowPeer;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
#SuppressWarnings("serial")
public class MyFrame extends Frame implements WindowListener {
final Image i16, i24, i32, i48;
MyFrame() throws Exception {
i16 = Toolkit.getDefaultToolkit().getImage("Icon16x16.png");
i24 = Toolkit.getDefaultToolkit().getImage("Icon24x24.png");
i32 = Toolkit.getDefaultToolkit().getImage("Icon32x32.png");
i48 = Toolkit.getDefaultToolkit().getImage("Icon48x48.png");
addWindowListener(this);
setSize(500,300);
setTitle("Unaliased icon example");
setLayout(new FlowLayout());
setVisible(true);
}
public synchronized void customSetIconImages(java.util.List<Image> icons) throws Exception {
Field windowIcons = Class.forName("java.awt.Window").getDeclaredField("icons");
windowIcons.setAccessible(true);
windowIcons.set(this, new ArrayList<Image>(icons));
if (getPeer() != null)
updateIconImages(i24, 24, 24, i24, 24, 24);
firePropertyChange("iconImage", null, null);
}
public void updateIconImages(Image big, int bw, int bh, Image small, int sw, int sh) throws Exception {
DataBufferInt iconData = getUnscaledIconData(big, bw, bh);
DataBufferInt iconSmData = getUnscaledIconData(small, sw, sh);
WindowPeer peer = (WindowPeer) getPeer();
Method setIconImagesData = Class.forName("sun.awt.windows.WWindowPeer").getDeclaredMethod("setIconImagesData", int[].class, int.class, int.class, int[].class, int.class, int.class);
setIconImagesData.setAccessible(true);
setIconImagesData.invoke(peer, iconData.getData(), bw, bh, iconSmData.getData(), sw, sh);
}
public static DataBufferInt getUnscaledIconData(Image image, int w, int h) {
Image temporary = new ImageIcon(image).getImage();
BufferedImage buffImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = buffImage.createGraphics();
g2d.drawImage(temporary, 0, 0, null);
g2d.dispose();
Raster raster = buffImage.getRaster();
DataBuffer buffer = raster.getDataBuffer();
return (DataBufferInt) buffer;
}
#Override
public void windowOpened(WindowEvent arg0) {
try {
customSetIconImages(Arrays.asList(i24));
} catch (Exception e) {
System.err.println(e.getClass().getName()+" "+e.getMessage());
}
}
#Override
public void windowActivated(WindowEvent arg0) {
}
#Override
public void windowClosed(WindowEvent arg0) {
}
#Override
public void windowClosing(WindowEvent arg0) {
dispose();
}
#Override
public void windowDeactivated(WindowEvent arg0) {
}
#Override
public void windowDeiconified(WindowEvent arg0) {
}
#Override
public void windowIconified(WindowEvent arg0) {
}
public static void main(String args[]) throws Exception {
MyFrame fr = new MyFrame();
}
}
As #df778899 said, inside sun.awt.windows.WWindowPeer there are four private native methods which you can call t determine system icons size. You can combine the information returned by these methods with your own version getScaledIconImage() that performs unaliasing or not as yoou wish.
Last, note that this is a very dirty hack just for getting an unaliased icon. I've only tested in in Java 8 and Windows 10. And there are high chances that it doesn't work in newer versions of Java.
This won't be the answer you're hoping for, but this looks like a problem at the JDK level.
The window icons are handled by the sun.awt.windows.WWindowPeer class, which in turn makes a few native method calls, but there is enough to see in the source for this to point to the problem. Please read the important bit here.
Essentially, regardless of how many icon image sizes are provided, it will only pick out two sizes - for the WWindowPeer.getSysIconWidth() and getSysSmIconWidth() - to pass into the native setIconImagesData() method.
The getSysIconWidth() and getSysSmIconWidth() methods are also native, but it is possible to directly check their return values:
JFrame frame = new JFrame();
runOnPeer(frame, "getSysIconWidth");
runOnPeer(frame, "getSysIconHeight");
runOnPeer(frame, "getSysSmIconWidth");
runOnPeer(frame, "getSysSmIconHeight");
private void runOnPeer(JFrame frame, String methodName) {
//JDK8 style
//ComponentPeer peer = frame.getPeer();
//JDK11 style
Field peerField = Component.class.getDeclaredField("peer");
peerField.setAccessible(true);
Object peer = peerField.get(frame);
Method method = Class.forName("sun.awt.windows.WWindowPeer")
.getDeclaredMethod(methodName);
method.setAccessible(true);
System.out.println(methodName + "()=" + method.invoke(peer));
}
... which returns this on Windows 10 ...
getSysIconWidth()=32
getSysIconHeight()=32
getSysSmIconWidth()=16
getSysSmIconHeight()=16
As you say, clearly one of these image sizes is then being scaled for the taskbar.
I was trying to answer a question related to moving a ball across the screen while changing its color over time, however I came through a weird bug, (most probably in my code) and while asking this question I came to a related question but that question is using a Client-Server architecture while mine is simply a Swing app running itself.
What is happening is that when the circle / ball, however you want to name it, reaches the half width of the JPanel or JFrame it becomes invisible or stops.
At first I thought it could be my JPanel being badly positioned, but I added a Border to it, so I could see its dimensions, but it's showing the whole border around the whole space of the JFrame.
Next I thought it could be some arithmetical problem, so I decided to make the ball larger and smaller than what I was originally painting it, giving me the same result, and having the same issue when I enlarge or reduce the window's size.
To get the following output I needed to change the increment by 9 instead of 10 that I was adding originally, because if I change it to 10 it becomes invisible:
The below code produces the above output:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Ellipse2D;
import java.util.Random;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class ChangingColorBall {
private JFrame frame;
private Timer timer;
private BallPane ballPane;
public static void main(String[] args) {
SwingUtilities.invokeLater(new ChangingColorBall()::createAndShowGui);
}
private void createAndShowGui() {
frame = new JFrame(getClass().getSimpleName());
ballPane = new BallPane();
timer = new Timer(100, e -> {
ballPane.increaseX();
});
ballPane.setBorder(BorderFactory.createLineBorder(Color.RED));
frame.add(ballPane);
frame.pack();
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
timer.start();
}
#SuppressWarnings("serial")
class BallPane extends JPanel {
private int x;
private static final int Y = 50;
private static final int SIZE = 20;
private Color color;
private Random r;
public void increaseX() {
x += 9;
r = new Random();
color = new Color(r.nextInt(255), r.nextInt(255), r.nextInt(255));
revalidate();
repaint();
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(color);
// g2d.fill(new Ellipse2D.Double(x, Y, SIZE, SIZE));
g2d.fillOval(x, Y, SIZE, SIZE);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 100);
}
}
}
I also thought it could be something related to the Shapes API, and decided to change it to fillOval as well with the same results, I can't post a GIF yet, but will add it later if necessary.
I'm working under macOS Sierra 10.12.6 (16G29) on a MacBook Pro (13'' Retina display, early 2015) compiling and running it under Java 1.8
I'll test this code later as well on my own PC and not my work's Mac, however, could this be a bug related to Swing's API or a bug in my own code? If so, what am I doing wrong? Since it doesn't seem clear to me
The issue is that you are inadvertently overriding the getX() method defined in JComponent in your BallPane class.
As a result the x coordinate of the the JPanel whenever accessed by getX() is also changing as getX() now returns your field x which is defining how the ball moves and thus resulting in this behavior. You should either remove the method getX() from BallPane or rename it.
First of all, I'd like to inform you all that I'm just a beginner and I've tried many thing without any success.
I've managed to make a JFrame then a JPanel and inside that JPanel, a JLabel with icon. I can retrieve the picture with the button JFileChooser, the picture is resized to fit the JLabel.
My project has exactly 5 files :
ImageFilter: used by LoadFiles
ImagePreview: used by LoadFiles
LoadFiles: JFileChooser class
NewJFrame
Utils: used by LoadFiles
I am using NetBeans IDE and the GUI Builder, I made my class LoadFiles(JFileChooser) so that I just have to drop the class on NewJFrame and the button appears on my JFrame, so that my program can be easily modified (Every class is a module, but yet only one which is LoadFiles) and there is nothing except the variable declared in my NewJFrame.
Here is how my program looks like:
And I want to add two buttons, Next and Previous to navigate between the pictures I already opened in my JLabel.
Class LoadFiles
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.io.File;
import java.io.IOException;
public class LoadFiles extends JButton implements ActionListener {
JFileChooser jfc;
public LoadFiles() {
super("Load");
addActionListener(this);
}
public void actionPerformed(ActionEvent e) {
if (jfc == null) {
jfc = new JFileChooser(".");
jfc.addChoosableFileFilter(new ImageFilter());
jfc.setAcceptAllFileFilterUsed(false);
jfc.setAccessory(new ImagePreview(jfc));
}
jfc.setDialogTitle("test");
int result = jfc.showOpenDialog(null);
if(result == JFileChooser.APPROVE_OPTION) {
File file = jfc.getSelectedFile();
System.out.println(file.getParent());
System.out.println(file.getName());
// Read image and place new file icon into preferred locations
BufferedImage newImage = null;
try {
newImage = ImageIO.read(file);
} catch(IOException ex) {
System.out.println("red error: " + ex.getMessage());
}
ImageIcon backgdIcon = new ImageIcon(newImage);
Image zoom = getScaledImage(backgdIcon.getImage(), 471, 189);
Icon iconScaled = new ImageIcon(zoom);
NewJFrame.jLabel1.setIcon(iconScaled);
} else if (result == JFileChooser.CANCEL_OPTION) {
System.out.println(JFileChooser.CANCEL_OPTION);
}
}
private Image getScaledImage(Image srcImg, int w, int h){
BufferedImage resizedImg = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = resizedImg.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.drawImage(srcImg, 0, 0, w, h, null);
g2.dispose();
return resizedImg;
} }
You can find here my program:
http://www.speedyshare.com/k6e5r/javaapplication29.7z
Please, could you help me?
Edit 1 - Thank you for your comments, I couldn't answer sooner. I will try what you are talking about both MadProgrammer and Andrew Thompson. I will get back to you later hoping I'll make it work.
I'll also remember for my next posts to make it better (MCVE).
Joop Eggen - I am not trying to make a preview icon in JFileChooser, I already have that. I want to be able to retrieve the last image i added to the JLabel with the Previous Button (not Preview). Thank you anyway for your reply.
Edit 2 - Thank's for your advices mbw, what I had in mind when I did that was thatI could use that classe in any application by just dropping it on a GUI and it could work everywhere by just chaning variable's name but you are right, it is not easy to communicate with the other conponent.
I wanted to do the less code possible in the JFrame, so it can be the most modular possible.
I will probably do like you're saying.
Edit 3 -
Finaly I succeeded,
I declare this in the class :
List<BufferedImage> images;
int currentImage = 0;
Then after I put the image in the BufferedImage
//bufferedimage dans la arraylist
if(images == null)
images = new ArrayList<BufferedImage>();
images.add(newImage);
currentImage = images.size() - 1;
And I did two methods
public void nextImage() {
if(images != null && images.get(currentImage + 1) != null )
{
ImageIcon backgdIcon = new ImageIcon(images.get(currentImage + 1));
Image zoom = getScaledImage(backgdIcon.getImage(), 471, 189);//taille en pixels
Icon iconScaled = new ImageIcon(zoom);
NewJFrame.jLabel1.setIcon(iconScaled);
}
}
public void prevImage() {
if(images != null && images.get(currentImage - 1) != null )
{
ImageIcon backgdIcon = new ImageIcon(images.get(currentImage - 1));
Image zoom = getScaledImage(backgdIcon.getImage(), 471, 189);//taille en pixels
Icon iconScaled = new ImageIcon(zoom);
NewJFrame.jLabel1.setIcon(iconScaled);
}
}
Thank you everyone for all your advices. Have a good day.
There are some good ideas in the comments, but I think it will be easier if you change the structure of your code.
If I were you, I would not create extend JButton and create a custom class for each button. It really complicates things, and makes it harder to pass information around the gui to different components that need it.
Instead, to make the code easier to read, I would create three JButtons in your JFrame with the netbeans gui builder: Load, next and previous. Then you can easily add an action event listener for each button and do the all the work in the JFrame. This also makes it really easy to keep a reference to all the loaded pictures for the next and previous button to use.
So I am using Eclipse with Windows builder. I was just wondering if there was anyway I can import an image that'll show up on the JFrame that I can easily move around and re-size instead of setting the location and size and drawing it.
Here is a simple example of adding an image to a JFrame:
frame.add(new JLabel(new ImageIcon("Path/To/Your/Image.png")));
There is no specialized image component provided in Swing (which is sad in my opinion). So, there are a few options:
As #Reimeus said: Use a JLabel with an icon.
Create in the window builder a JPanel, that will represent the location of the image. Then add your own custom image component to the JPanel using a few lines of code you will never have to change. They should look like this:
JImageComponent ic = new JImageComponent(myImageGoesHere);
imagePanel.add(ic);
where JImageComponent is a self created class that extends JComponent that overrides the paintComponent() method to draw the image.
If you are using Netbeans to develop, use JLabel and change its icon property.
As martijn-courteaux said, create a custom component it's the better option.
In C# exists a component called PictureBox and I tried to create this component for Java, here is the code:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
public class JPictureBox extends JComponent {
private Icon icon = null;
private final Dimension dimension = new Dimension(100, 100);
private Image image = null;
private ImageIcon ii = null;
private SizeMode sizeMode = SizeMode.STRETCH;
private int newHeight, newWidth, originalHeight, originalWidth;
public JPictureBox() {
JPictureBox.this.setPreferredSize(dimension);
JPictureBox.this.setOpaque(false);
JPictureBox.this.setSizeMode(SizeMode.STRETCH);
}
#Override
public void paintComponent(Graphics g) {
if (ii != null) {
switch (getSizeMode()) {
case NORMAL:
g.drawImage(image, 0, 0, ii.getIconWidth(), ii.getIconHeight(), null);
break;
case ZOOM:
aspectRatio();
g.drawImage(image, 0, 0, newWidth, newHeight, null);
break;
case STRETCH:
g.drawImage(image, 0, 0, this.getWidth(), this.getHeight(), null);
break;
case CENTER:
g.drawImage(image, (int) (this.getWidth() / 2) - (int) (ii.getIconWidth() / 2), (int) (this.getHeight() / 2) - (int) (ii.getIconHeight() / 2), ii.getIconWidth(), ii.getIconHeight(), null);
break;
default:
g.drawImage(image, 0, 0, this.getWidth(), this.getHeight(), null);
}
}
}
public Icon getIcon() {
return icon;
}
public void setIcon(Icon icon) {
this.icon = icon;
ii = (ImageIcon) icon;
image = ii.getImage();
originalHeight = ii.getIconHeight();
originalWidth = ii.getIconWidth();
}
public SizeMode getSizeMode() {
return sizeMode;
}
public void setSizeMode(SizeMode sizeMode) {
this.sizeMode = sizeMode;
}
public enum SizeMode {
NORMAL,
STRETCH,
CENTER,
ZOOM
}
private void aspectRatio() {
if (ii != null) {
newHeight = this.getHeight();
newWidth = (originalWidth * newHeight) / originalHeight;
}
}
}
If you want to add an image, choose the JPictureBox, after that go to Properties and find "icon" property and select an image.
If you want to change the sizeMode property then choose the JPictureBox, after that go to Properties and find "sizeMode" property, you can choose some values:
NORMAL value, the image is positioned in the upper-left corner of the JPictureBox.
STRETCH value causes the image to stretch or shrink to fit the JPictureBox.
ZOOM value causes the image to be stretched or shrunk to fit the JPictureBox; however, the aspect ratio in the original is maintained.
CENTER value causes the image to be centered in the client area.
If you want to learn more about this topic, you can check this video.
Also you can see the code on Gitlab or Github.
I want to crop an image manually using the mouse.
Suppose the image has some text, and I want to select some text from an image, then
for that purpose I want to crop that area by using the mouse.
The solution I found most useful for cropping a buffered image uses the getSubImage(x,y,w,h);
My cropping routine ended up looking like this:
private BufferedImage cropImage(BufferedImage src, Rectangle rect) {
BufferedImage dest = src.getSubimage(0, 0, rect.width, rect.height);
return dest;
}
There are two potentially major problem with the leading answer to this question. First, as per the docs:
public BufferedImage getSubimage(int x,
int y,
int w,
int h)
Returns a subimage defined by a specified rectangular region.
The returned BufferedImage shares the same data array as the original
image.
Essentially, what this means is that result from getSubimage acts as a pointer which points at a subsection of the original image.
Why is this important? Well, if you are planning to edit the subimage for any reason, the edits will also happen to the original image. For example, I ran into this problem when I was using the smaller image in a separate window to zoom in on the original image. (kind of like a magnifying glass). I made it possible to invert the colors to see certain details more easily, but the area that was "zoomed" also got inverted in the original image! So there was a small section of the original image that had inverted colors while the rest of it remained normal. In many cases, this won't matter, but if you want to edit the image, or if you just want a copy of the cropped section, you might want to consider a method.
Which brings us to the second problem. Fortunately, it is not as big a problem as the first. getSubImage shares the same data array as the original image. That means that the entire original image is still stored in memory. Assuming that by "crop" the image you actually want a smaller image, you will need to redraw it as a new image rather than just get the subimage.
Try this:
BufferedImage img = image.getSubimage(startX, startY, endX, endY); //fill in the corners of the desired crop location here
BufferedImage copyOfImage = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_RGB);
Graphics g = copyOfImage.createGraphics();
g.drawImage(img, 0, 0, null);
return copyOfImage; //or use it however you want
This technique will give you the cropped image you are looking for by itself, without the link back to the original image. This will preserve the integrity of the original image as well as save you the memory overhead of storing the larger image. (If you do dump the original image later)
This is a method which will work:
import java.awt.image.BufferedImage;
import java.awt.Rectangle;
import java.awt.Color;
import java.awt.Graphics;
public BufferedImage crop(BufferedImage src, Rectangle rect)
{
BufferedImage dest = new BufferedImage(rect.getWidth(), rect.getHeight(), BufferedImage.TYPE_ARGB_PRE);
Graphics g = dest.getGraphics();
g.drawImage(src, 0, 0, rect.getWidth(), rect.getHeight(), rect.getX(), rect.getY(), rect.getX() + rect.getWidth(), rect.getY() + rect.getHeight(), null);
g.dispose();
return dest;
}
Of course you have to make your own JComponent:
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;
import java.awt.Rectangle;
import java.awt.Graphics;
import javax.swing.JComponent;
public class JImageCropComponent extends JComponent implements MouseListener, MouseMotionListener
{
private BufferedImage img;
private int x1, y1, x2, y2;
public JImageCropComponent(BufferedImage img)
{
this.img = img;
this.addMouseListener(this);
this.addMouseMotionListener(this);
}
public void setImage(BufferedImage img)
{
this.img = img;
}
public BufferedImage getImage()
{
return this;
}
#Override
public void paintComponent(Graphics g)
{
g.drawImage(img, 0, 0, this);
if (cropping)
{
// Paint the area we are going to crop.
g.setColor(Color.RED);
g.drawRect(Math.min(x1, x2), Math.min(y1, y2), Math.max(x1, x2), Math.max(y1, y2));
}
}
#Override
public void mousePressed(MouseEvent evt)
{
this.x1 = evt.getX();
this.y1 = evt.getY();
}
#Override
public void mouseReleased(MouseEvent evt)
{
this.cropping = false;
// Now we crop the image;
// This is the method a wrote in the other snipped
BufferedImage cropped = crop(new Rectangle(Math.min(x1, x2), Math.min(y1, y2), Math.max(x1, x2), Math.max(y1, y2));
// Now you have the cropped image;
// You have to choose what you want to do with it
this.img = cropped;
}
#Override
public void mouseDragged(MouseEvent evt)
{
cropping = true;
this.x2 = evt.getX();
this.y2 = evt.getY();
}
//TODO: Implement the other unused methods from Mouse(Motion)Listener
}
I didn't test it. Maybe there are some mistakes (I'm not sure about all the imports).
You can put the crop(img, rect) method in this class.
Hope this helps.
File fileToWrite = new File(filePath, "url");
BufferedImage bufferedImage = cropImage(fileToWrite, x, y, w, h);
private BufferedImage cropImage(File filePath, int x, int y, int w, int h){
try {
BufferedImage originalImgage = ImageIO.read(filePath);
BufferedImage subImgage = originalImgage.getSubimage(x, y, w, h);
return subImgage;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
This question has not enough information to answer. A general solution (depending on your GUI framework): add a mouse event handler that will catch clicks and mouse movements. This will give you your (x, y) coordinates. Next use these coordinates to crop your image.
You need to read about Java Image API and mouse-related API, maybe somewhere under the java.awt.event package.
For a start, you need to be able to load and display the image to the screen, maybe you'll use a JPanel.
Then from there, you will try implement a mouse motion listener interface and other related interfaces. Maybe you'll get tied on the mouseDragged method...
For a mousedragged action, you will get the coordinate of the rectangle form by the drag...
Then from these coordinates, you will get the subimage from the image you have and you sort of redraw it anew....
And then display the cropped image... I don't know if this will work, just a product of my imagination... just a thought!
I'm giving this example because this actually work for my use case.
I was trying to use the AWS Rekognition API.
The API returns a BoundingBox object:
BoundingBox boundingBox = faceDetail.getBoundingBox();
The code below uses it to crop the image:
import com.amazonaws.services.rekognition.model.BoundingBox;
private BufferedImage cropImage(BufferedImage image, BoundingBox box) {
Rectangle goal = new Rectangle(Math.round(box.getLeft()* image.getWidth()),Math.round(box.getTop()* image.getHeight()),Math.round(box.getWidth() * image.getWidth()), Math.round(box.getHeight() * image.getHeight()));
Rectangle clip = goal.intersection(new Rectangle(image.getWidth(), image.getHeight()));
BufferedImage clippedImg = image.getSubimage(clip.x, clip.y , clip.width, clip.height);
return clippedImg;
}