Add image thumbnails to a layout in a grid?
I have a list of images. I need to add as small thumbnails to a frame. I currently have frame with SpringLayout
. How can add thumbnails in some grid like fashion with a scroll pane. The list of photos could be large, so I need a scroll pane. I don;t know how to handle this with SpringLayout
. I know how to add thumbnails; the real question how can I show the grid of thumbnails in SpringLayout
.
import java.awt.Color;
import java.awt.Container;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SpringLayout;
public class grid {
/**
* @param args
*/
public grid() {
JFrame frame = new JFrame("Hello");
Container pane = frame.getContentPane();
pane.setBackground(Color.WHITE);
SpringLayout layout = new SpringLayout();
pane.setLayout(layout);
JPanel photoPanel = new JPanel();
JScrollPane photoScroll = new JScrollPane(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
photoPanel.add(photoScroll);
pane.add(photoPanel);
layout.putConstraint(SpringLayout.WEST, photoPanel, 260, SpringLayout.WEST, pane);
layout.putConstraint(SpringLayout.NORTH, photoPanel, 40, SpringLayout.SOUTH, pane);
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.pack();
//frame.setSize(frame.getMaximumSize());
frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
frame.setLocationRelativeTo(null);
frame.setResizable(true);
frame.setVisible(true);
}
public static void main(String[] args) {
// TODO Auto-generated method stub
new grid();
}
}
So basically, you need some kind of container that lives in the scroll pane (commonly known as the view).
To this you should add your images.
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class ImageGrid {
public static void main(String[] args) {
new ImageGrid();
}
public ImageGrid() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private JPanel imagesPane;
public TestPane() {
setLayout(new BorderLayout());
imagesPane = new JPanel(new WrapLayout());
add(new JScrollPane(imagesPane));
JButton scan = new JButton("Scan");
scan.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String path = "C:\\Users\\shane\\Dropbox\\Ponies";
File[] files = new File(path).listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
String name = pathname.getName().toLowerCase();
return pathname.isFile() && (name.endsWith(".png")
|| name.endsWith(".jpg")
|| name.endsWith(".gif"));
}
});
imagesPane.removeAll();
for (File file : files) {
try {
ImagePane pane = new ImagePane(file);
imagesPane.add(pane);
} catch (Exception exp) {
exp.printStackTrace();
}
}
imagesPane.revalidate();
imagesPane.repaint();
}
});
add(scan, BorderLayout.SOUTH);
}
}
public class ImagePane extends JPanel {
private Image img;
public ImagePane(File source) throws IOException {
img = ImageIO.read(source);
if (img.getWidth(this) > 200 || img.getHeight(this) > 200) {
int width = img.getWidth(this);
int height = img.getWidth(this);
float scaleWidth = 200f / width;
float scaleHeight = 200f / height;
if (scaleWidth > scaleHeight) {
width = -1;
height = (int)(height * scaleHeight);
} else {
width = (int)(width * scaleWidth);
height = -1;
}
img = img.getScaledInstance(width, height, Image.SCALE_SMOOTH);
}
}
@Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
if (img != null) {
// int width = img.getWidth();
// int height = img.getHeight();
// float scale = 1f;
// AffineTransform at = new AffineTransform();
// at.translate(
// (getWidth() / 2) - ((img.getWidth() * scale) / 2),
// (getHeight() / 2) - ((img.getHeight() * scale) / 2));
// at.scale(scale, scale);
// g2d.setTransform(at);
g2d.drawImage(img, 0, 0, this);
}
g2d.dispose();
}
}
/**
* FlowLayout subclass that fully supports wrapping of components.
*/
public class WrapLayout extends FlowLayout {
private Dimension preferredLayoutSize;
/**
* Constructs a new
* <code>WrapLayout</code> with a left alignment and a default 5-unit
* horizontal and vertical gap.
*/
public WrapLayout() {
super();
}
/**
* Constructs a new
* <code>FlowLayout</code> with the specified alignment and a default 5-unit
* horizontal and vertical gap. The value of the alignment argument must be
* one of
* <code>WrapLayout</code>,
* <code>WrapLayout</code>, or
* <code>WrapLayout</code>.
*
* @param align the alignment value
*/
public WrapLayout(int align) {
super(align);
}
/**
* Creates a new flow layout manager with the indicated alignment and the
* indicated horizontal and vertical gaps.
* <p>
* The value of the alignment argument must be one of
* <code>WrapLayout</code>,
* <code>WrapLayout</code>, or
* <code>WrapLayout</code>.
*
* @param align the alignment value
* @param hgap the horizontal gap between components
* @param vgap the vertical gap between components
*/
public WrapLayout(int align, int hgap, int vgap) {
super(align, hgap, vgap);
}
/**
* Returns the preferred dimensions for this layout given the
* <i>visible</i> components in the specified target container.
*
* @param target the component which needs to be laid out
* @return the preferred dimensions to lay out the subcomponents of the
* specified container
*/
@Override
public Dimension preferredLayoutSize(Container target) {
return layoutSize(target, true);
}
/**
* Returns the minimum dimensions needed to layout the <i>visible</i>
* components contained in the specified target container.
*
* @param target the component which needs to be laid out
* @return the minimum dimensions to lay out the subcomponents of the
* specified container
*/
@Override
public Dimension minimumLayoutSize(Container target) {
Dimension minimum = layoutSize(target, false);
minimum.width -= (getHgap() + 1);
return minimum;
}
/**
* Returns the minimum or preferred dimension needed to layout the target
* container.
*
* @param target target to get layout size for
* @param preferred should preferred size be calculated
* @return the dimension to layout the target container
*/
private Dimension layoutSize(Container target, boolean preferred) {
synchronized (target.getTreeLock()) {
// Each row must fit with the width allocated to the containter.
// When the container width = 0, the preferred width of the container
// has not yet been calculated so lets ask for the maximum.
int targetWidth = target.getSize().width;
if (targetWidth == 0) {
targetWidth = Integer.MAX_VALUE;
}
int hgap = getHgap();
int vgap = getVgap();
Insets insets = target.getInsets();
int horizontalInsetsAndGap = insets.left + insets.right + (hgap * 2);
int maxWidth = targetWidth - horizontalInsetsAndGap;
// Fit components into the allowed width
Dimension dim = new Dimension(0, 0);
int rowWidth = 0;
int rowHeight = 0;
int nmembers = target.getComponentCount();
for (int i = 0; i < nmembers; i++) {
Component m = target.getComponent(i);
if (m.isVisible()) {
Dimension d = preferred ? m.getPreferredSize() : m.getMinimumSize();
// Can't add the component to current row. Start a new row.
if (rowWidth + d.width > maxWidth) {
addRow(dim, rowWidth, rowHeight);
rowWidth = 0;
rowHeight = 0;
}
// Add a horizontal gap for all components after the first
if (rowWidth != 0) {
rowWidth += hgap;
}
rowWidth += d.width;
rowHeight = Math.max(rowHeight, d.height);
}
}
addRow(dim, rowWidth, rowHeight);
dim.width += horizontalInsetsAndGap;
dim.height += insets.top + insets.bottom + vgap * 2;
// When using a scroll pane or the DecoratedLookAndFeel we need to
// make sure the preferred size is less than the size of the
// target containter so shrinking the container size works
// correctly. Removing the horizontal gap is an easy way to do this.
Container scrollPane = SwingUtilities.getAncestorOfClass(JScrollPane.class, target);
if (scrollPane != null && target.isValid()) {
dim.width -= (hgap + 1);
}
return dim;
}
}
/*
* A new row has been completed. Use the dimensions of this row
* to update the preferred size for the container.
*
* @param dim update the width and height when appropriate
* @param rowWidth the width of the row to add
* @param rowHeight the height of the row to add
*/
private void addRow(Dimension dim, int rowWidth, int rowHeight) {
dim.width = Math.max(dim.width, rowWidth);
if (dim.height > 0) {
dim.height += getVgap();
}
dim.height += rowHeight;
}
}
}
This example includes WrapLayout. The scaling is done for speed and simplicity, but the method used is unavisable, take a look at this for a better method.
I would normally load and scale the images in a background thread, like a SwingWorker
, but this is an example of the idea.
This recently deleted question on the same topic prompted this example that uses randomly-sized, synthetic images to illustrate the effect of scaling to a particular SIZE
in the largest dimension.
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Random;
import javax.swing.*;
/** @see https://stackoverflow.com/a/15982915/230513 */
public class Thumbnails {
private static final int SIZE = 128;
private static final Random r = new Random();
// Get a randomly sized image
static private Image getImage() {
int w = r.nextInt(SIZE) + SIZE / 2;
int h = r.nextInt(SIZE) + SIZE / 2;
BufferedImage bi = new BufferedImage(
w, h, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = bi.createGraphics();
g2d.setPaint(Color.lightGray);
g2d.fillRect(0, 0, w, h);
g2d.setColor(Color.black);
String s = w + "\u00D7" + h;
int x = (w - g2d.getFontMetrics().stringWidth(s)) / 2;
g2d.drawString(s, x, 24);
g2d.dispose();
return bi;
}
// Get a panel with an image scaled to SIZE in the largest dimension
// https://stackoverflow.com/a/15961424/230513
private static JPanel getPanel() {
Image original = getImage();
int w = original.getWidth(null);
int h = original.getHeight(null);
float scaleW = (float) SIZE / w;
float scaleH = (float) SIZE / h;
if (scaleW > scaleH) {
w = -1;
h = (int) (h * scaleH);
} else {
w = (int) (w * scaleW);
h = -1;
}
Image scaled = original.getScaledInstance(w, h, Image.SCALE_SMOOTH);
JPanel p = new JPanel(new GridLayout()){
@Override
public Dimension getPreferredSize() {
return new Dimension(SIZE, SIZE);
}
};
p.add(new JLabel(new ImageIcon(scaled)));
p.setBorder(BorderFactory.createLineBorder(Color.red));
return p;
}
private static void createAndShowGUI() {
JFrame f = new JFrame("PhotoAlbum55");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel panel = new JPanel(new GridLayout(6, 6));
for (int i = 0; i < 6 * 6; i++) {
panel.add(getPanel());
}
f.add(new JScrollPane(panel));
f.pack();
f.setSize(4 * SIZE, 4 * SIZE);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
createAndShowGUI();
}
});
}
}