Choosing a Layout Manager - Java - java

I'm having trouble to find the right Layout Manager. I have some images inside a JPanel, they are all different size so I wanted to use a Flow Layout to let the manager handle them. The manager fills all the first row as possible and then warps to another line. This is all ok, but what I want is to stop at second "warp". I just want to show 2 rows of images and if then you want to see more you must click a JButton to load the others. Actually the FlowLayout keeps inserting in a third line and the images are cutted by half because the panel is not "tall" enough. Any tips?
I've already tried with Flow Layout and Mig Layout without success.

Combine a FlowLayout(outerPanel) with a GridBagLayout(innerPannel)
u can define the rows by yourself, and then u put a JScrollPane over it so it wont cut your pictures and u will still be able to see them in full size(my suggestion)

I couldn't resist trying to solve this. I had hoped to make use of FlowLayouts, but in the end, I ended up using only GridBagLayouts: One for each row of images, plus one for the entire container. I may have over-engineered in terms of flexibility, but I imagine something like this may be useful to myself or others in the future.
In essence, every time the container changes size, or images are added/removed, or any of its visual properties change, updateLayout() is called, which rebuilds all of the GridBagLayout panels from scratch. I'm sure there are ways to make this more efficient, and I'm sure it's possible to write a LayoutManager2 implementation from scatch that does the job, but this performed reasonably well for me.
import java.util.List;
import java.util.ArrayList;
import java.util.Objects;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Rectangle;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.image.BufferedImage;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class ImagePanel
extends JPanel {
private static final long serialVersionUID = 1;
/** #serial */
private final List<Icon> images = new ArrayList<>();
/** #serial */
private final List<Icon> scaledImages = new ArrayList<>();
/** #serial */
private int maxRowCount = 2;
/** #serial */
private int hgap = 6;
/** #serial */
private int vgap = 6;
/** #serial */
private int maxImageWidth = 200;
/** #serial */
private int maxImageHeight = 200;
public ImagePanel() {
setLayout(new GridBagLayout());
addComponentListener(new ComponentAdapter() {
#Override
public void componentResized(ComponentEvent event) {
updateLayout();
}
});
}
#Override
public void addNotify() {
super.addNotify();
updateLayout();
}
#Override
public Dimension getPreferredSize() {
Rectangle screen = findGraphicsConfiguration().getBounds();
Dimension size = new Dimension();
Dimension row = new Dimension();
int rowsComputed = 0;
int gap = 0;
for (Icon image : scaledImages) {
if (row.width > 0 &&
row.width + gap + image.getIconWidth() > screen.width) {
if (++rowsComputed >= maxRowCount) {
break;
}
size.width = Math.max(size.width, row.width);
size.height += (size.height > 0 ? vgap : 0) + row.height;
row.setSize(0, 0);
gap = 0;
}
row.width += gap + image.getIconWidth();
row.height = Math.max(row.height, image.getIconHeight());
gap = hgap;
}
size.width = Math.max(size.width, row.width);
size.height += (size.height > 0 ? vgap : 0) + row.height;
return size;
}
private void updateLayout() {
int width = getWidth();
if (width == 0) {
return;
}
for (Component rowContainer : getComponents()) {
((JComponent) rowContainer).removeAll();
}
GridBagConstraints rowConstraints = new GridBagConstraints();
rowConstraints.gridwidth = GridBagConstraints.REMAINDER;
rowConstraints.weightx = 1;
rowConstraints.anchor = GridBagConstraints.FIRST_LINE_START;
int row = -1;
int rowWidth = 0;
GridBagConstraints gbc = new GridBagConstraints();
for (Icon image : scaledImages) {
JComponent rowContainer = (row >= 0 && row < getComponentCount() ?
(JComponent) getComponent(row) : null);
int gap = (rowWidth > 0 ? hgap : 0);
if (rowContainer == null ||
rowWidth + gap + image.getIconWidth() > width) {
if (++row >= maxRowCount) {
break;
}
gap = 0;
rowWidth = 0;
if (row < getComponentCount()) {
rowContainer = (JComponent) getComponent(row);
} else {
rowContainer = new JPanel(new GridBagLayout());
add(rowContainer, rowConstraints);
}
rowConstraints.insets.top = vgap;
}
gbc.insets.left = gap;
JComponent imageContainer = new JLabel(image);
rowContainer.add(imageContainer, gbc);
rowWidth += gap + image.getIconWidth();
}
for (int i = getComponentCount() - 1; i >= maxRowCount; i--) {
remove(i);
}
}
private GraphicsConfiguration findGraphicsConfiguration() {
GraphicsConfiguration config = getGraphicsConfiguration();
if (config == null) {
GraphicsEnvironment env =
GraphicsEnvironment.getLocalGraphicsEnvironment();
config = env.getDefaultScreenDevice().getDefaultConfiguration();
}
return config;
}
private Icon scale(Icon image) {
int imageWidth = image.getIconWidth();
int imageHeight = image.getIconHeight();
if (imageWidth > maxImageWidth || imageHeight > maxImageHeight) {
float scale = Math.min((float) maxImageWidth / imageWidth,
(float) maxImageHeight / imageHeight);
if (scale < 1) {
GraphicsConfiguration config = findGraphicsConfiguration();
BufferedImage scaledImage = config.createCompatibleImage(
(int) (imageWidth * scale),
(int) (imageHeight * scale));
Graphics2D g = scaledImage.createGraphics();
g.scale(scale, scale);
image.paintIcon(this, g, 0, 0);
g.dispose();
image = new ImageIcon(scaledImage);
}
}
return image;
}
public List<Icon> getImages() {
return new ArrayList<>(images);
}
public void clearImages() {
images.clear();
updateLayout();
revalidate();
}
public void addImage(Icon image) {
Objects.requireNonNull(image, "Image cannot be null");
images.add(image);
scaledImages.add(scale(image));
updateLayout();
revalidate();
}
public void removeImage(Icon image) {
int index = images.indexOf(image);
if (index >= 0) {
removeImage(index);
}
}
public void removeImage(int index) {
images.remove(index);
scaledImages.remove(index);
updateLayout();
revalidate();
}
public int getHgap() {
return hgap;
}
public void setHgap(int gap) {
if (gap < 0) {
throw new IllegalArgumentException("Gap must be at least zero");
}
int old = this.hgap;
this.hgap = gap;
if (old != gap) {
updateLayout();
revalidate();
}
firePropertyChange("hgap", old, gap);
}
public int getVgap() {
return vgap;
}
public void setVgap(int gap) {
if (gap < 0) {
throw new IllegalArgumentException("Gap must be at least zero");
}
int old = this.vgap;
this.vgap = gap;
if (old != gap) {
updateLayout();
revalidate();
}
firePropertyChange("vgap", old, gap);
}
public int getMaxRowCount() {
return maxRowCount;
}
public void setMaxRowCount(int count) {
if (count < 0) {
throw new IllegalArgumentException("Count must be at least zero");
}
int old = this.maxRowCount;
this.maxRowCount = count;
if (old != count) {
updateLayout();
revalidate();
}
firePropertyChange("maxRowCount", old, count);
}
public int getMaxImageWidth() {
return maxImageWidth;
}
private void recomputeScaledImages() {
scaledImages.clear();
for (Icon image : images) {
scaledImages.add(scale(image));
}
}
public void setMaxImageWidth(int width) {
if (width <= 0) {
throw new IllegalArgumentException("Width must be positive");
}
int old = this.maxImageWidth;
this.maxImageWidth = width;
if (old != width) {
recomputeScaledImages();
updateLayout();
revalidate();
}
firePropertyChange("maxImageWidth", old, width);
}
public int getMaxImageHeight() {
return maxImageHeight;
}
public void setMaxImageHeight(int height) {
if (height <= 0) {
throw new IllegalArgumentException("Height must be positive");
}
int old = this.maxImageHeight;
this.maxImageHeight = height;
if (old != height) {
recomputeScaledImages();
updateLayout();
revalidate();
}
firePropertyChange("maxImageHeight", old, height);
}
public static void main(final String[] args)
throws java.io.IOException {
if (args.length == 0) {
System.err.println("Usage: java " + ImagePanel.class.getName()
+ " <directory> | <url1> <url2> ...");
System.exit(2);
}
final List<java.net.URL> urls;
if (args.length == 1 && !args[0].contains(":")) {
urls = new ArrayList<>();
try (java.nio.file.DirectoryStream<java.nio.file.Path> dir =
java.nio.file.Files.newDirectoryStream(
java.nio.file.Paths.get(args[0]))) {
for (java.nio.file.Path file : dir) {
urls.add(file.toUri().toURL());
}
}
} else {
urls = new ArrayList<>(args.length);
for (String url : args) {
urls.add(new java.net.URL(url));
}
}
java.awt.EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
ImagePanel imagePanel = new ImagePanel();
for (java.net.URL url : urls) {
imagePanel.addImage(new ImageIcon(url));
}
javax.swing.JFrame frame =
new javax.swing.JFrame("ImagePanel");
frame.setDefaultCloseOperation(
javax.swing.JFrame.EXIT_ON_CLOSE);
JPanel panel = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.anchor = GridBagConstraints.FIRST_LINE_START;
gbc.weightx = 1;
gbc.weighty = 1;
panel.add(imagePanel, gbc);
frame.getContentPane().add(panel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
});
}
}

Related

marquee (ticker,text scroll) with java2d or javafx stutters

I'm trying to write a smooth ticker (text running from rigth to left on the screen).
It is almost as I want it, but there are still some stutters. I would like it to be as smooth as cloud moving in the sky. 30 years ago I managed with a few lines of assembler code, but in Java I fail.
It get's worse if I increase the speed (number of pixels I move the text at once).
Is there some kind of synchronization to the screen refresh missing?
EDIT
I updated my code according to #camickr remark to launch the window in an exclusive fullscreen windows, which lead to a sligth improvement.
Other things I tried:
Added ExtendedBufferCapabilities which is supposed to consider vsync
Toolkit.getDefaultToolkit().sync();
enabled opengl
tried a gaming loop
added some debugging info
When I use 30 fps and move the text only one pixel, on a 4k display it looks quite good but is also very slow. As soon as I increase to speed to 2 pixels it's starts to stutter.
I'm starting to think that it is simply not possible to achieve my goal with java2d and I have to move to some opengl-library.
Here is my code:
package scrolling;
import java.awt.AWTException;
import java.awt.BufferCapabilities;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.ImageCapabilities;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.Timer;
import sun.java2d.pipe.hw.ExtendedBufferCapabilities;
/**
* A smooth scroll with green background to be used with a video cutter.
*
* sun.java2d.pipe.hw.ExtendedBufferCapabilities is restricted https://stackoverflow.com/questions/25222811/access-restriction-the-type-application-is-not-api-restriction-on-required-l
*
*/
public class MyScroll extends JFrame implements ActionListener {
private int targetFps = 30;
private boolean isOpenGl = false;
private boolean isVsync = true;
private boolean useGamingLoop = false;
private int speed = 1;
private String message;
private int fontSize = 120;
private Font theFont;
private transient int leftEdge; // Offset from window's right edge to left edge
private Color bgColor;
private Color fgColor;
private int winWidth;
private int winHeight;
private double position = 0.77;
private FontMetrics fontMetrics;
private int yPositionScroll;
private boolean isFullScreen;
private long lastTimerStart = 0;
private BufferedImage img;
private Graphics2D graphicsScroll;
private GraphicsDevice currentScreenDevice = null;
private int msgWidth = 0;
private Timer scrollTimer;
private boolean isRunning;
/* gaming loop variables */
private static final long NANO_IN_MILLI = 1000000L;
// num of iterations with a sleep delay of 0ms before
// game loop yields to other threads.
private static final int NO_DELAYS_PER_YIELD = 16;
// max num of renderings that can be skipped in one game loop,
// game's internal state is updated but not rendered on screen.
private static int MAX_RENDER_SKIPS = 5;
// private long prevStatsTime;
private long gameStartTime;
private long curRenderTime;
private long rendersSkipped = 0L;
private long period; // period between rendering in nanosecs
private long fps;
private long frameCounter;
private long lastFpsTime;
public void init() {
fontSize = getWidth() / 17;
if (getGraphicsConfiguration().getBufferCapabilities().isPageFlipping()) {
try { // no pageflipping available with opengl
BufferCapabilities cap = new BufferCapabilities(new ImageCapabilities(true), new ImageCapabilities(true), BufferCapabilities.FlipContents.BACKGROUND);
// ExtendedBufferCapabilities is supposed to do a vsync
ExtendedBufferCapabilities ebc = new ExtendedBufferCapabilities(cap, ExtendedBufferCapabilities.VSyncType.VSYNC_ON);
createBufferStrategy(2, ebc);
} catch (AWTException e) {
e.printStackTrace();
}
} else {
createBufferStrategy(2);
}
System.out.println(getDeviceConfigurationString(getGraphicsConfiguration()));
message = "This is a test. ";
leftEdge = 0;
theFont = new Font("Helvetica", Font.PLAIN, fontSize);
bgColor = getBackground();
fgColor = getForeground();
winWidth = getSize().width - 1;
winHeight = getSize().height;
yPositionScroll = (int) (winHeight * position);
initScrollImage();
}
/**
* Draw the entire text to a buffered image to copy it to the screen later.
*/
private void initScrollImage() {
Graphics2D og = (Graphics2D) getBufferStrategy().getDrawGraphics();
fontMetrics = og.getFontMetrics(theFont);
Rectangle2D rect = fontMetrics.getStringBounds(message, og);
img = new BufferedImage((int) rect.getWidth(), (int) rect.getHeight(), BufferedImage.TYPE_INT_ARGB);
// At each frame, we get a reference on the rendering buffer graphics2d.
// To handle concurrency, we 'cut' it into graphics context for each cube.
graphicsScroll = img.createGraphics();
graphicsScroll.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
graphicsScroll.setBackground(Color.BLACK);
graphicsScroll.setFont(theFont);
graphicsScroll.setColor(bgColor);
graphicsScroll.fillRect(0, 0, img.getWidth(), img.getHeight()); // clear offScreen Image.
graphicsScroll.setColor(fgColor);
msgWidth = fontMetrics.stringWidth(message);
graphicsScroll.setColor(Color.white);
graphicsScroll.drawString(message, 1, img.getHeight() - 10);
// for better readability in front of an image draw an outline arround the text
graphicsScroll.setColor(Color.black);
graphicsScroll.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
Font font = new Font("Helvetica", Font.PLAIN, fontSize);
graphicsScroll.translate(1, img.getHeight() - 10);
FontRenderContext frc = graphicsScroll.getFontRenderContext();
GlyphVector gv = font.createGlyphVector(frc, message);
graphicsScroll.draw(gv.getOutline());
}
public void start() {
scrollTimer = new Timer(1000 / targetFps, this);
scrollTimer.setRepeats(true);
scrollTimer.setCoalesce(true);
scrollTimer.start();
}
public void startGamingloop() {
// loop initialization
long beforeTime, afterTime, timeDiff, sleepTime;
long overSleepTime = 0L;
int noDelays = 0;
long excess = 0L;
gameStartTime = System.nanoTime();
// prevStatsTime = gameStartTime;
beforeTime = gameStartTime;
period = (1000L * NANO_IN_MILLI) / targetFps; // rendering FPS (nanosecs/targetFPS)
System.out.println("FPS: " + targetFps + ", vsync=");
System.out.println("FPS period: " + period);
// gaming loop http://www.javagaming.org/index.php/topic,19971.0.html
while (true) {
// **2) execute physics
updateLeftEdge();
// **1) execute drawing
drawScroll();
// Synchronise with the display hardware.
// Flip the buffer
if (!getBufferStrategy().contentsLost()) {
getBufferStrategy().show();
}
if (isVsync) {
Toolkit.getDefaultToolkit().sync();
}
afterTime = System.nanoTime();
curRenderTime = afterTime;
calculateFramesPerSecond();
timeDiff = afterTime - beforeTime;
sleepTime = (period - timeDiff) - overSleepTime;
if (sleepTime > 0) { // time left in cycle
// System.out.println("sleepTime: " + (sleepTime/NANO_IN_MILLI));
try {
Thread.sleep(sleepTime / NANO_IN_MILLI);// nano->ms
} catch (InterruptedException ex) {
}
overSleepTime = (System.nanoTime() - afterTime) - sleepTime;
} else { // sleepTime <= 0;
System.out.println("Rendering too slow");
// this cycle took longer than period
excess -= sleepTime;
// store excess time value
overSleepTime = 0L;
if (++noDelays >= NO_DELAYS_PER_YIELD) {
Thread.yield();
// give another thread a chance to run
noDelays = 0;
}
}
beforeTime = System.nanoTime();
/*
* If the rendering is taking too long, then update the game state without rendering it, to get the UPS nearer to the required frame rate.
*/
int skips = 0;
while ((excess > period) && (skips < MAX_RENDER_SKIPS)) {
// update state but don’t render
System.out.println("Skip renderFPS, run updateFPS");
excess -= period;
updateLeftEdge();
skips++;
}
rendersSkipped += skips;
}
}
private void calculateFramesPerSecond() {
if (curRenderTime - lastFpsTime >= NANO_IN_MILLI * 1000) {
fps = frameCounter;
frameCounter = 0;
lastFpsTime = curRenderTime;
}
frameCounter++;
}
public void setRunning(boolean isRunning) {
this.isRunning = isRunning;
}
#Override
public void actionPerformed(ActionEvent e) {
render();
}
private void render() {
if (!isFullScreen) {
repaint(0, yPositionScroll, winWidth, yPositionScroll + fontMetrics.getAscent());
} else {
getBufferStrategy().show();
}
if (isVsync) {
Toolkit.getDefaultToolkit().sync();
}
updateLeftEdge();
drawScroll();
}
/**
* Draws (part) of the prerendered image with text to a position in the screen defined by an increasing
* variable "leftEdge".
*
* #return time drawing took.
*/
private long drawScroll() {
long beforeDrawText = System.nanoTime();
if (winWidth - leftEdge + msgWidth < 0) { // Current scroll entirely off-screen.
leftEdge = 0;
}
int x = winWidth - leftEdge;
int sourceWidth = Math.min(leftEdge, img.getWidth());
Graphics2D og = (Graphics2D) getBufferStrategy().getDrawGraphics();
try { // copy the pre drawn scroll to the screen
og.drawImage(img.getSubimage(0, 0, sourceWidth, img.getHeight()), x, yPositionScroll, null);
} catch (Exception e) {
System.out.println(e.getMessage() + " " + x + " " + sourceWidth);
}
long afterDrawText = System.nanoTime();
return afterDrawText - beforeDrawText;
}
public static void main(String[] args) {
MyScroll scroll = new MyScroll();
System.setProperty("sun.java2d.opengl", String.valueOf(scroll.isOpenGl())); // enable opengl
System.setProperty("sun.java2d.renderer.verbose", "true");
String renderer = "undefined";
try {
renderer = sun.java2d.pipe.RenderingEngine.getInstance().getClass().getName();
System.out.println("Renderer " + renderer);
} catch (Throwable th) {
// may fail with JDK9 jigsaw (jake)
if (false) {
System.err.println("Unable to get RenderingEngine.getInstance()");
th.printStackTrace();
}
}
scroll.setBackground(Color.green);
scroll.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice[] screens = env.getScreenDevices();
// I want the external monitor attached to my notebook
GraphicsDevice device = screens[screens.length - 1];
boolean isFullScreenSupported = device.isFullScreenSupported();
scroll.setFullScreen(isFullScreenSupported);
scroll.setUndecorated(isFullScreenSupported);
scroll.setResizable(!isFullScreenSupported);
if (isFullScreenSupported) {
device.setFullScreenWindow(scroll);
scroll.setIgnoreRepaint(true);
scroll.validate();
} else {
// Windowed mode
Rectangle r = GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds();
scroll.setSize(r.width, r.height);
scroll.pack();
scroll.setExtendedState(JFrame.MAXIMIZED_BOTH);
scroll.setVisible(true);
}
// exit on pressing escape
scroll.addKeyListener(new KeyAdapter() {
#Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
scroll.setRunning(false);
if(scroll.getScrollTimer() != null) {
scroll.getScrollTimer().stop();
}
System.exit(0);
}
}
});
scroll.setVisible(true);
scroll.init();
if (scroll.isUseGamingLoop()) {
scroll.startGamingloop();
} else {
scroll.start();
}
}
private void updateLeftEdge() {
leftEdge += speed;
}
public Timer getScrollTimer() {
return scrollTimer;
}
public void setFullScreen(boolean isFullScreen) {
this.isFullScreen = isFullScreen;
}
public void setTargetFps(int targetFps) {
this.targetFps = targetFps;
}
public void setOpenGl(boolean isOpenGl) {
this.isOpenGl = isOpenGl;
}
public void setVsync(boolean isVsync) {
this.isVsync = isVsync;
}
public void setUseGamingLoop(boolean useGamingLoop) {
this.useGamingLoop = useGamingLoop;
}
public void setSpeed(int speed) {
this.speed = speed;
}
public void setMessage(String message) {
this.message = message;
}
private String getDeviceConfigurationString(GraphicsConfiguration gc){
return "Bounds: " + gc.getBounds() + "\n" +
"Buffer Capabilities: " + gc.getBufferCapabilities() + "\n" +
" Back Buffer Capabilities: " + gc.getBufferCapabilities().getBackBufferCapabilities() + "\n" +
" Accelerated: " + gc.getBufferCapabilities().getBackBufferCapabilities().isAccelerated() + "\n" +
" True Volatile: " + gc.getBufferCapabilities().getBackBufferCapabilities().isTrueVolatile() + "\n" +
" Flip Contents: " + gc.getBufferCapabilities().getFlipContents() + "\n" +
" Front Buffer Capabilities: " + gc.getBufferCapabilities().getFrontBufferCapabilities() + "\n" +
" Accelerated: " + gc.getBufferCapabilities().getFrontBufferCapabilities().isAccelerated() + "\n" +
" True Volatile: " + gc.getBufferCapabilities().getFrontBufferCapabilities().isTrueVolatile() + "\n" +
" Is Full Screen Required: " + gc.getBufferCapabilities().isFullScreenRequired() + "\n" +
" Is MultiBuffer Available: " + gc.getBufferCapabilities().isMultiBufferAvailable() + "\n" +
" Is Page Flipping: " + gc.getBufferCapabilities().isPageFlipping() + "\n" +
"Device: " + gc.getDevice() + "\n" +
" Available Accelerated Memory: " + gc.getDevice().getAvailableAcceleratedMemory() + "\n" +
" ID String: " + gc.getDevice().getIDstring() + "\n" +
" Type: " + gc.getDevice().getType() + "\n" +
" Display Mode: " + gc.getDevice().getDisplayMode() + "\n" +
"Image Capabilities: " + gc.getImageCapabilities() + "\n" +
" Accelerated: " + gc.getImageCapabilities().isAccelerated() + "\n" +
" True Volatile: " + gc.getImageCapabilities().isTrueVolatile() + "\n";
}
public boolean isOpenGl() {
return isOpenGl;
}
public boolean isUseGamingLoop() {
return useGamingLoop;
}
}
Output of graphics capabilities:
Renderer sun.java2d.pisces.PiscesRenderingEngine
Bounds: java.awt.Rectangle[x=3839,y=0,width=3840,height=2160]
Buffer Capabilities: sun.awt.X11GraphicsConfig$XDBECapabilities#68de145
Back Buffer Capabilities: java.awt.ImageCapabilities#27fa135a
Accelerated: false
True Volatile: false
Flip Contents: undefined
Front Buffer Capabilities: java.awt.ImageCapabilities#27fa135a
Accelerated: false
True Volatile: false
Is Full Screen Required: false
Is MultiBuffer Available: false
Is Page Flipping: true
Device: X11GraphicsDevice[screen=1]
Available Accelerated Memory: -1
ID String: :0.1
Type: 0
Display Mode: java.awt.DisplayMode#1769
Image Capabilities: java.awt.ImageCapabilities#27fa135a
Accelerated: false
True Volatile: false
EDIT 2:
I wrote the same in javafx now, same result, it is stuttering as soon as I move more than 3 pixels at once.
I'm running on an Intel i9 9900K, Nvidia GeForce RTX 2060 Mobile, Ubuntu, OpenJdk 14.
package scrolling;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import javax.swing.Timer;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.stage.Screen;
import javafx.stage.Stage;
/**
* A smooth scroll with green background to be used with a video cutter.
*
* https://stackoverflow.com/questions/51478675/error-javafx-runtime-components-are-missing-and-are-required-to-run-this-appli
* https://stackoverflow.com/questions/18547362/javafx-and-openjdk
*
*/
public class MyScroll extends Application {
private boolean useGamingLoop = false;
private int speed = 3;
private String message;
private transient double leftEdge; // Offset from window's right edge to left edge
private Color bgColor;
private Color fgColor;
private double winWidth;
private int winHeight;
private double position = 0.77;
private int yPositionScroll;
private Image img;
private int msgWidth = 0;
private Timer scrollTimer;
private boolean isRunning;
private ImageView imageView;
long lastUpdateTime;
long lastIntervall;
long nextIntervall;
String ADAPTIVE_PULSE_PROP = "com.sun.scenario.animation.adaptivepulse";
int frame = 0;
long timeOfLastFrameSwitch = 0;
#Override
public void start(final Stage stage) {
message = "This is a test. ";
leftEdge = 0;
bgColor = Color.green;
fgColor = Color.white;
winWidth = (int)Screen.getPrimary().getBounds().getWidth();
winHeight = (int)Screen.getPrimary().getBounds().getHeight();
yPositionScroll = (int) (winHeight * position);
initScrollImage(stage);
stage.setFullScreenExitHint("");
stage.setAlwaysOnTop(true);
new AnimationTimer() {
#Override
public void handle(long now) {
nextIntervall = now - lastUpdateTime;
System.out.println(lastIntervall - nextIntervall);
lastUpdateTime = System.nanoTime();
drawScroll(stage);
lastIntervall = nextIntervall;
}
}.start();
//Creating a Group object
Group root = new Group(imageView);
//Creating a scene object
Scene scene = new Scene(root);
//Adding scene to the stage
stage.setScene(scene);
stage.show();
stage.setFullScreen(true);
}
/**
* Draw the entire text to an imageview and add to the scene.
*/
private void initScrollImage(Stage stage) {
int fontSize = (int)winWidth / 17;
Font theFont = new Font("Helvetica", Font.PLAIN, fontSize);
BufferedImage tempImg = new BufferedImage((int)winWidth, winHeight, BufferedImage.TYPE_INT_ARGB);
FontMetrics fontMetrics = tempImg.getGraphics().getFontMetrics(theFont);
Rectangle2D rect = fontMetrics.getStringBounds(message, tempImg.getGraphics());
msgWidth = fontMetrics.stringWidth(message);
BufferedImage bufferedImage = new BufferedImage((int) rect.getWidth(), (int) rect.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics2D graphicsScroll = bufferedImage.createGraphics();
graphicsScroll.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
graphicsScroll.setBackground(Color.BLACK);
graphicsScroll.setFont(theFont);
graphicsScroll.setColor(bgColor);
graphicsScroll.fillRect(0, 0, bufferedImage.getWidth(), bufferedImage.getHeight()); // set background color
graphicsScroll.setColor(fgColor);
graphicsScroll.drawString(message, 1, bufferedImage.getHeight() - 10);
// for better readability in front of an image draw an outline arround the text
graphicsScroll.setColor(Color.black);
graphicsScroll.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
Font font = new Font("Helvetica", Font.PLAIN, fontSize);
graphicsScroll.translate(1, bufferedImage.getHeight() - 10);
FontRenderContext frc = graphicsScroll.getFontRenderContext();
GlyphVector gv = font.createGlyphVector(frc, message);
graphicsScroll.draw(gv.getOutline());
img = SwingFXUtils.toFXImage(bufferedImage, null);
imageView = new ImageView(img);
imageView.setSmooth(false);
imageView.setCache(true);
//Setting the preserve ratio of the image view
imageView.setPreserveRatio(true);
tempImg.flush();
}
/**
* Draws (part) of the prerendered image with text to a position in the screen defined by an increasing
* variable "leftEdge".
*
* #return time drawing took.
*/
private void drawScroll(Stage stage) {
leftEdge += speed;
if (winWidth - leftEdge + msgWidth < 0) { // Current scroll entirely off-screen.
leftEdge = 0;
}
// imageView.relocate(winWidth - leftEdge, yPositionScroll);
imageView.setX(winWidth - leftEdge);
}
public static void main(String[] args) {
// System.setProperty("sun.java2d.opengl", "true");
System.setProperty("prism.vsync", "true");
// System.setProperty("com.sun.scenario.animation.adaptivepulse", "true");
System.setProperty("com.sun.scenario.animation.vsync", "true");
launch(args);
}
public Timer getScrollTimer() {
return scrollTimer;
}
public void setSpeed(int speed) {
this.speed = speed;
}
public void setMessage(String message) {
this.message = message;
}
public boolean isUseGamingLoop() {
return useGamingLoop;
}
public void setRunning(boolean isRunning) {
this.isRunning = isRunning;
}
}
I'm running this with Windows 10. I don't think it will work well on a Unix system.
...
The code consists of 9 classes in 5 packages. The package name is in the code.
Marquee Class
package com.ggl.marquee;
import javax.swing.SwingUtilities;
import com.ggl.marquee.model.MarqueeModel;
import com.ggl.marquee.view.MarqueeFrame;
public class Marquee implements Runnable {
#Override
public void run() {
new MarqueeFrame(new MarqueeModel());
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Marquee());
}
}
CreateMarqueeActionListener Class
package com.ggl.marquee.controller;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JTextField;
import com.ggl.marquee.model.MarqueeModel;
import com.ggl.marquee.view.MarqueeFrame;
public class CreateMarqueeActionListener implements ActionListener {
private JTextField field;
private MarqueeFrame frame;
private MarqueeModel model;
public CreateMarqueeActionListener(MarqueeFrame frame, MarqueeModel model,
JTextField field) {
this.frame = frame;
this.model = model;
this.field = field;
}
#Override
public void actionPerformed(ActionEvent event) {
model.stopDtpRunnable();
model.resetPixels();
String s = field.getText().trim();
if (s.equals("")) {
frame.repaintMarqueePanel();
return;
}
s = " " + s + " ";
model.setTextPixels(model.getDefaultFont().getTextPixels(s));
frame.repaintMarqueePanel();
}
}
FontSelectionListener Class
package com.ggl.marquee.controller;
import javax.swing.DefaultListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import com.ggl.marquee.model.MarqueeFont;
import com.ggl.marquee.model.MarqueeModel;
public class FontSelectionListener implements ListSelectionListener {
private MarqueeModel model;
public FontSelectionListener(MarqueeModel model) {
this.model = model;
}
#Override
public void valueChanged(ListSelectionEvent event) {
DefaultListSelectionModel selectionModel = (DefaultListSelectionModel) event
.getSource();
if (!event.getValueIsAdjusting()) {
int index = selectionModel.getMinSelectionIndex();
if (index >= 0) {
MarqueeFont font = model.getDefaultListModel().get(index);
model.setDefaultFont(font);
}
}
}
}
FontGenerator Class
package com.ggl.marquee.model;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class FontGenerator {
private static final boolean DEBUG = false;
private Font font;
private FontHeights fontHeights;
private Map<Character, MarqueeCharacter> characterMap;
public FontGenerator(Font font) {
this.font = font;
this.characterMap = new HashMap<Character, MarqueeCharacter>();
}
public void execute() {
int width = 50;
BufferedImage bi = generateCharacterImage(width, "B");
int[] result1 = getCharacterHeight(bi);
bi = generateCharacterImage(width, "g");
int[] result2 = getCharacterHeight(bi);
fontHeights = new FontHeights(result1[0], result1[1], result2[1]);
if (DEBUG) System.out.println(fontHeights.getAscender() + ", "
+ fontHeights.getBaseline() + ", "
+ fontHeights.getDescender());
for (int x = 32; x < 127; x++) {
char c = (char) x;
StringBuilder builder = new StringBuilder(3);
builder.append('H');
builder.append(c);
builder.append('H');
bi = generateCharacterImage(width, builder.toString());
int[][] pixels = convertTo2D(bi);
MarqueeCharacter mc = getCharacterPixels(pixels);
if (DEBUG) {
System.out.println(builder.toString() + " " +
mc.getWidth() + "x" + mc.getHeight());
}
characterMap.put(c, mc);
}
}
private BufferedImage generateCharacterImage(int width, String string) {
BufferedImage bi = new BufferedImage(
width, width, BufferedImage.TYPE_INT_RGB);
Graphics g = bi.getGraphics();
g.setFont(font);
g.setColor(Color.WHITE);
g.fillRect(0, 0, width, width);
g.setColor(Color.BLACK);
g.drawString(string, 0, width / 2);
return bi;
}
private int[] getCharacterHeight(BufferedImage bi) {
int[][] pixels = convertTo2D(bi);
int minHeight = bi.getHeight();
int maxHeight = 0;
for (int i = 0; i < pixels.length; i++) {
for (int j = 0; j < pixels[i].length; j++) {
if (pixels[i][j] < -1) {
minHeight = Math.min(i, minHeight);
maxHeight = Math.max(i, maxHeight);
}
}
}
int[] result = new int[2];
result[0] = minHeight;
result[1] = maxHeight;
return result;
}
private MarqueeCharacter getCharacterPixels(int[][] pixels) {
List<Boolean[]> list = new ArrayList<Boolean[]>();
int startRow = fontHeights.getAscender();
int endRow = fontHeights.getDescender();
int height = fontHeights.getCharacterHeight();
int startColumn = getCharacterColumnStart(pixels);
int endColumn = getCharacterColumnEnd(pixels);
for (int i = startColumn; i <= endColumn; i++) {
Boolean[] characterColumn = new Boolean[height];
int k = 0;
for (int j = startRow; j <= endRow; j++) {
if (pixels[j][i] < -1) characterColumn[k] = true;
else characterColumn[k] = false;
k++;
}
list.add(characterColumn);
}
MarqueeCharacter mc = new MarqueeCharacter(list.size(), height);
for (int i = 0; i < list.size(); i++) {
Boolean[] characterColumn = list.get(i);
mc.setColumn(characterColumn);
}
return mc;
}
private int getCharacterColumnStart(int[][] pixels) {
int start = fontHeights.getAscender();
int end = fontHeights.getBaseline();
int letterEndFlag = 0;
int column = 1;
while (letterEndFlag < 1) {
boolean pixelDetected = false;
for (int i = start; i <= end; i++) {
if (pixels[i][column] < -1) {
pixelDetected = true;
}
}
column++;
// End of first letter
if ((letterEndFlag == 0) && !pixelDetected) letterEndFlag = 1;
}
return column;
}
private int getCharacterColumnEnd(int[][] pixels) {
int start = fontHeights.getAscender();
int end = fontHeights.getBaseline();
int height = fontHeights.getCharacterHeight2();
int letterEndFlag = 0;
int column = pixels.length - 1;
while (letterEndFlag < 4) {
int pixelCount = 0;
for (int i = start; i <= end; i++) {
if (pixels[i][column] < -1) {
pixelCount++;
}
}
column--;
// End of first letter
if (pixelCount >= height) letterEndFlag++;
// Start of first letter
// if ((letterEndFlag == 0) && (pixelCount > 0)) letterEndFlag = 1;
}
return column;
}
private int[][] convertTo2D(BufferedImage image) {
final int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer())
.getData();
final int width = image.getWidth();
final int height = image.getHeight();
int[][] result = new int[height][width];
for (int pixel = 0, row = 0, col = 0; pixel < pixels.length; pixel++) {
result[row][col] = pixels[pixel];
col++;
if (col == width) {
col = 0;
row++;
}
}
return result;
}
public MarqueeCharacter getCharacter(Character c) {
MarqueeCharacter mc = characterMap.get(c);
return (mc == null) ? characterMap.get('?') : mc;
}
public int getCharacterHeight() {
return fontHeights.getCharacterHeight();
}
}
FontHeights Class
package com.ggl.marquee.model;
public class FontHeights {
private final int ascender;
private final int baseline;
private final int descender;
public FontHeights(int ascender, int baseline, int descender) {
this.ascender = ascender;
this.baseline = baseline;
this.descender = descender;
}
public int getCharacterHeight() {
return descender - ascender + 1;
}
public int getCharacterHeight2() {
return baseline - ascender + 1;
}
public int getAscender() {
return ascender;
}
public int getBaseline() {
return baseline;
}
public int getDescender() {
return descender;
}
}
MarqueeCharacter Class
package com.ggl.marquee.model;
import java.security.InvalidParameterException;
public class MarqueeCharacter {
private static int columnCount;
private int height;
private int width;
private boolean[][] pixels;
public MarqueeCharacter(int width, int height) {
this.width = width;
this.height = height;
this.pixels = new boolean[width][height];
columnCount = 0;
}
public void setColumn(Boolean[] value) {
int height = value.length;
if (this.height != height) {
String s = "The number of values must equal the column height - "
+ this.height;
throw new InvalidParameterException(s);
}
for (int i = 0; i < height; i++) {
pixels[columnCount][i] = value[i];
}
columnCount++;
}
public boolean[][] getPixels() {
return pixels;
}
public boolean isComplete() {
return (width == columnCount);
}
public int getHeight() {
return height;
}
public int getWidth() {
return width;
}
}
MarqueeFont Class
package com.ggl.marquee.model;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.font.FontRenderContext;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
public class MarqueeFont {
private static final boolean DEBUG = false;
private int fontHeight;
private Font font;
public MarqueeFont(Font font) {
this.font = font;
FontRenderContext frc = new FontRenderContext(null, true, true);
Rectangle2D r2D = font.getStringBounds("HgH", frc);
this.fontHeight = (int) Math.round(r2D.getHeight());
if (DEBUG) {
System.out.println(font.getFamily() + " " + fontHeight + " pixels");
}
}
public boolean[][] getTextPixels(String s) {
FontRenderContext frc = new FontRenderContext(null, true, true);
Rectangle2D r2D = font.getStringBounds(s, frc);
int rWidth = (int) Math.round(r2D.getWidth());
int rHeight = (int) Math.round(r2D.getHeight());
int rX = (int) Math.round(r2D.getX());
int rY = (int) Math.round(r2D.getY());
if (DEBUG) {
System.out.print(s);
System.out.print(", rWidth = " + rWidth);
System.out.print(", rHeight = " + rHeight);
System.out.println(", rX = " + rX + ", rY = " + rY);
}
BufferedImage bi = generateCharacterImage(rX, -rY, rWidth, rHeight, s);
int[][] pixels = convertTo2D(bi);
if (DEBUG) {
displayPixels(pixels);
}
return createTextPixels(pixels);
}
private BufferedImage generateCharacterImage(int x, int y, int width,
int height, String string) {
BufferedImage bi = new BufferedImage(width, height,
BufferedImage.TYPE_INT_RGB);
Graphics g = bi.getGraphics();
g.setFont(font);
g.setColor(Color.WHITE);
g.fillRect(0, 0, width, height);
g.setColor(Color.BLACK);
g.drawString(string, x, y);
return bi;
}
private int[][] convertTo2D(BufferedImage image) {
final int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer())
.getData();
final int width = image.getWidth();
final int height = image.getHeight();
int[][] result = new int[height][width];
int row = 0;
int col = 0;
for (int pixel = 0; pixel < pixels.length; pixel++) {
result[row][col] = pixels[pixel];
col++;
if (col == width) {
col = 0;
row++;
}
}
return result;
}
private void displayPixels(int[][] pixels) {
for (int i = 0; i < pixels.length; i++) {
String s = String.format("%03d", (i + 1));
System.out.print(s + ". ");
for (int j = 0; j < pixels[i].length; j++) {
if (pixels[i][j] == -1) {
System.out.print(" ");
} else {
System.out.print("X ");
}
}
System.out.println("");
}
}
private boolean[][] createTextPixels(int[][] pixels) {
// The int array pixels is in column, row order.
// We have to flip the array and produce the output
// in row, column order.
if (DEBUG) {
System.out.println(pixels[0].length + "x" + pixels.length);
}
boolean[][] textPixels = new boolean[pixels[0].length][pixels.length];
for (int i = 0; i < pixels.length; i++) {
for (int j = 0; j < pixels[i].length; j++) {
if (pixels[i][j] == -1) {
textPixels[j][i] = false;
} else {
textPixels[j][i] = true;
}
}
}
return textPixels;
}
public Font getFont() {
return font;
}
public int getFontHeight() {
return fontHeight;
}
#Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append(font.getFamily());
builder.append(", ");
builder.append(getStyleText());
builder.append(", ");
builder.append(font.getSize());
builder.append(" pixels");
return builder.toString();
}
private StringBuilder getStyleText() {
StringBuilder builder = new StringBuilder();
int style = font.getStyle();
if (style == Font.PLAIN) {
builder.append("normal");
} else if (style == Font.BOLD) {
builder.append("bold");
} else if (style == Font.ITALIC) {
builder.append("italic");
} else if (style == (Font.BOLD + Font.ITALIC)) {
builder.append("bold italic");
} else {
builder.append("unknown style");
}
return builder;
}
}
MarqueeFontFactory Class
package com.ggl.marquee.model;
import java.awt.Font;
import javax.swing.DefaultListModel;
public class MarqueeFontFactory {
private DefaultListModel<MarqueeFont> fontList;
private MarqueeFont defaultFont;
public MarqueeFontFactory() {
this.fontList = new DefaultListModel<MarqueeFont>();
addElements();
}
private void addElements() {
this.defaultFont = new MarqueeFont(new Font("Arial", Font.BOLD, 16));
fontList.addElement(defaultFont);
fontList.addElement(new MarqueeFont(new Font("Cambria", Font.BOLD, 16)));
fontList.addElement(new MarqueeFont(new Font("Courier New", Font.BOLD,
16)));
fontList.addElement(new MarqueeFont(new Font("Georgia", Font.BOLD, 16)));
fontList.addElement(new MarqueeFont(new Font("Lucida Calligraphy",
Font.BOLD, 16)));
fontList.addElement(new MarqueeFont(new Font("Times New Roman",
Font.BOLD, 16)));
fontList.addElement(new MarqueeFont(new Font("Verdana", Font.BOLD, 16)));
}
public DefaultListModel<MarqueeFont> getFontList() {
return fontList;
}
public void setDefaultFont(MarqueeFont defaultFont) {
this.defaultFont = defaultFont;
}
public MarqueeFont getDefaultFont() {
return defaultFont;
}
public int getCharacterHeight() {
int maxHeight = 0;
for (int i = 0; i < fontList.getSize(); i++) {
MarqueeFont font = fontList.get(i);
int height = font.getFontHeight();
maxHeight = Math.max(height, maxHeight);
}
return maxHeight;
}
}
MarqueeModel Class
package com.ggl.marquee.model;
import javax.swing.DefaultListModel;
import com.ggl.marquee.runnable.DisplayTextPixelsRunnable;
import com.ggl.marquee.view.MarqueeFrame;
public class MarqueeModel {
private static final int marqueeWidth = 120;
private boolean[][] marqueePixels;
private boolean[][] textPixels;
private DisplayTextPixelsRunnable dtpRunnable;
private MarqueeFontFactory fonts;
private MarqueeFrame frame;
public MarqueeModel() {
this.fonts = new MarqueeFontFactory();
this.marqueePixels = new boolean[marqueeWidth][getMarqueeHeight()];
}
public void setFrame(MarqueeFrame frame) {
this.frame = frame;
}
public MarqueeFontFactory getFonts() {
return fonts;
}
public DefaultListModel<MarqueeFont> getDefaultListModel() {
return fonts.getFontList();
}
public MarqueeFont getDefaultFont() {
return fonts.getDefaultFont();
}
public void setDefaultFont(MarqueeFont defaultFont) {
fonts.setDefaultFont(defaultFont);
}
public boolean[][] getMarqueePixels() {
return marqueePixels;
}
public boolean getMarqueePixel(int width, int height) {
return marqueePixels[width][height];
}
public int getMarqueeWidth() {
return marqueeWidth;
}
public int getMarqueeHeight() {
return fonts.getCharacterHeight();
}
public boolean[][] getTextPixels() {
return textPixels;
}
public int getTextPixelWidth() {
return textPixels.length;
}
private void startDtpRunnable() {
dtpRunnable = new DisplayTextPixelsRunnable(frame, this);
new Thread(dtpRunnable).start();
}
public void stopDtpRunnable() {
if (dtpRunnable != null) {
dtpRunnable.stopDisplayTextPixelsRunnable();
dtpRunnable = null;
}
}
public void setTextPixels(boolean[][] textPixels) {
this.textPixels = textPixels;
if (textPixels.length < getMarqueeWidth()) {
this.marqueePixels = copyCharacterPixels(0, textPixels,
marqueePixels);
} else {
startDtpRunnable();
}
}
public void resetPixels() {
for (int i = 0; i < getMarqueeWidth(); i++) {
for (int j = 0; j < getMarqueeHeight(); j++) {
marqueePixels[i][j] = false;
}
}
}
public void setAllPixels() {
for (int i = 0; i < getMarqueeWidth(); i++) {
for (int j = 0; j < getMarqueeHeight(); j++) {
marqueePixels[i][j] = true;
}
}
}
public boolean[][] copyCharacterPixels(int position,
boolean[][] characterPixels, boolean[][] textPixels) {
for (int i = 0; i < characterPixels.length; i++) {
for (int j = 0; j < characterPixels[i].length; j++) {
textPixels[i + position][j] = characterPixels[i][j];
}
}
return textPixels;
}
public void copyTextPixels(int position) {
for (int i = 0; i < marqueePixels.length; i++) {
int k = i + position;
k %= textPixels.length;
for (int j = 0; j < textPixels[i].length; j++) {
marqueePixels[i][j] = textPixels[k][j];
}
}
}
}
DisplayAllPixelsRunnable Class
package com.ggl.marquee.runnable;
import javax.swing.SwingUtilities;
import com.ggl.marquee.model.MarqueeModel;
import com.ggl.marquee.view.MarqueeFrame;
public class DisplayAllPixelsRunnable implements Runnable {
private MarqueeFrame frame;
private MarqueeModel model;
public DisplayAllPixelsRunnable(MarqueeFrame frame, MarqueeModel model) {
this.frame = frame;
this.model = model;
}
#Override
public void run() {
model.setAllPixels();
repaint();
try {
Thread.sleep(3000L);
} catch (InterruptedException e) {
}
model.resetPixels();
repaint();
}
private void repaint() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
frame.repaintMarqueePanel();
}
});
}
}
DisplayTextPixelsRunnable Class
package com.ggl.marquee.runnable;
import javax.swing.SwingUtilities;
import com.ggl.marquee.model.MarqueeModel;
import com.ggl.marquee.view.MarqueeFrame;
public class DisplayTextPixelsRunnable implements Runnable {
private static int textPixelPosition;
private volatile boolean running;
private MarqueeFrame frame;
private MarqueeModel model;
public DisplayTextPixelsRunnable(MarqueeFrame frame, MarqueeModel model) {
this.frame = frame;
this.model = model;
textPixelPosition = 0;
}
#Override
public void run() {
this.running = true;
while (running) {
model.copyTextPixels(textPixelPosition);
repaint();
sleep();
textPixelPosition++;
textPixelPosition %= model.getTextPixelWidth();
}
}
private void repaint() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
frame.repaintMarqueePanel();
}
});
}
private void sleep() {
try {
Thread.sleep(50L);
} catch (InterruptedException e) {
}
}
public synchronized void stopDisplayTextPixelsRunnable() {
this.running = false;
}
}
ControlPanel Class
package com.ggl.marquee.view;
import java.awt.BorderLayout;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import com.ggl.marquee.controller.CreateMarqueeActionListener;
import com.ggl.marquee.controller.FontSelectionListener;
import com.ggl.marquee.model.MarqueeFont;
import com.ggl.marquee.model.MarqueeModel;
public class ControlPanel {
private JButton submitButton;
private JPanel panel;
private MarqueeFrame frame;
private MarqueeModel model;
public ControlPanel(MarqueeFrame frame, MarqueeModel model) {
this.frame = frame;
this.model = model;
createPartControl();
}
private void createPartControl() {
panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
JPanel fontPanel = new JPanel();
fontPanel.setLayout(new BorderLayout());
JLabel fontLabel = new JLabel("Fonts");
fontPanel.add(fontLabel, BorderLayout.NORTH);
JList<MarqueeFont> fontList = new JList<MarqueeFont>(
model.getDefaultListModel());
fontList.setSelectedValue(model.getDefaultFont(), true);
fontList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
fontList.setVisibleRowCount(3);
ListSelectionModel listSelectionModel = fontList.getSelectionModel();
listSelectionModel.addListSelectionListener(new FontSelectionListener(
model));
JScrollPane fontScrollPane = new JScrollPane(fontList);
fontPanel.add(fontScrollPane, BorderLayout.CENTER);
panel.add(fontPanel);
JPanel fieldPanel = new JPanel();
JLabel fieldLabel = new JLabel("Marquee Text: ");
fieldPanel.add(fieldLabel);
JTextField field = new JTextField(30);
fieldPanel.add(field);
panel.add(fieldPanel);
JPanel buttonPanel = new JPanel();
submitButton = new JButton("Submit");
submitButton.addActionListener(new CreateMarqueeActionListener(frame,
model, field));
buttonPanel.add(submitButton);
panel.add(buttonPanel);
}
public JPanel getPanel() {
return panel;
}
public JButton getSubmitButton() {
return submitButton;
}
}
MarqueeFrame Class
package com.ggl.marquee.view;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
import com.ggl.marquee.model.MarqueeModel;
import com.ggl.marquee.runnable.DisplayAllPixelsRunnable;
public class MarqueeFrame {
private ControlPanel controlPanel;
private DisplayAllPixelsRunnable dapRunnable;
private JFrame frame;
private MarqueeModel model;
private MarqueePanel marqueePanel;
public MarqueeFrame(MarqueeModel model) {
this.model = model;
model.setFrame(this);
createPartControl();
}
private void createPartControl() {
frame = new JFrame();
// frame.setIconImage(getFrameImage());
frame.setTitle("Marquee");
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.addWindowListener(new WindowAdapter() {
#Override
public void windowClosing(WindowEvent event) {
exitProcedure();
}
});
JPanel mainPanel = new JPanel();
mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.PAGE_AXIS));
marqueePanel = new MarqueePanel(model);
mainPanel.add(marqueePanel);
controlPanel = new ControlPanel(this, model);
mainPanel.add(controlPanel.getPanel());
frame.add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.getRootPane().setDefaultButton(controlPanel.getSubmitButton());
frame.setVisible(true);
dapRunnable = new DisplayAllPixelsRunnable(this, model);
new Thread(dapRunnable).start();
}
private void exitProcedure() {
frame.dispose();
System.exit(0);
}
public void repaintMarqueePanel() {
marqueePanel.repaint();
}
}
MarqueePanel Class
package com.ggl.marquee.view;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.JPanel;
import com.ggl.marquee.model.MarqueeModel;
public class MarqueePanel extends JPanel {
private static final long serialVersionUID = -1677343084333836763L;
private static final int pixelWidth = 4;
private static final int gapWidth = 2;
private static final int totalWidth = pixelWidth + gapWidth;
private static final int yStart = gapWidth + totalWidth + totalWidth;
private MarqueeModel model;
public MarqueePanel(MarqueeModel model) {
this.model = model;
int width = model.getMarqueeWidth() * totalWidth + gapWidth;
int height = model.getMarqueeHeight() * totalWidth + yStart + yStart;
setPreferredSize(new Dimension(width, height));
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.BLACK);
g.fillRect(0, 0, getWidth(), getHeight());
int x = gapWidth;
int y = yStart;
for (int i = 0; i < model.getMarqueeWidth(); i++) {
for (int j = 0; j < model.getMarqueeHeight(); j++) {
if (model.getMarqueePixel(i, j)) {
g.setColor(Color.PINK);
} else {
g.setColor(Color.BLACK);
}
g.fillRect(x, y, pixelWidth, pixelWidth);
y += totalWidth;
}
y = yStart;
x += totalWidth;
}
}
}
I did a few changes upon the same example (Marquee) posted by #Gilbert Le Blanc.
First of all, in the Marquee example the Thread.sleep() method was being used instead of a more modern approach with a TimerTask. I cannot stress enough about how much difference there is between Thread.sleep() and TimerTask.
Thread.sleep() is inaccurate. How inaccurate depends on the underlying operating system and its timers and schedulers. I've experienced that garbage collection going on in parallel can lead to excessive sleep.
Source: src
This led to an important problem
The painting function is called inconsistently instead of the expected fixed time for 30fps which is roughly 40ms, this generates some of the shuttering problem.
Source: me
which is solved partially by the TimerTask approach
On at least one major operating system (Windows), blocking on a timer has a much better precision and reliability than sleeping.
Source: GameDev.net
I have noticed a huge improvement when i rewrote the same example but using timerTask and it's surely more fluid than using the example you provided (2D Swing);
I compared it as following, the speed of the Marquee example is more or less the same speed of your 2d swing test put at 10, so try running your test with int speed = 10; and the one provided above and see if shutter more or less.
Alternatively, you can try to run the native Marquee Example and the new one with TimerTask and you should see an important major difference.
I think implementing a buffer strategy with this one is the way to go for achieving a truly fluid scroll experience...otherwise there is always OpenGL
The classes i changed:
DisplayTextPixelsRunnable is now DisplayTextPixelsTimerTask
package com.ggl.marquee.runnable;
import javax.swing.SwingUtilities;
import com.ggl.marquee.model.MarqueeModel;
import com.ggl.marquee.view.MarqueeFrame;
import java.util.TimerTask;
public class DisplayTextPixelsTimerTask extends TimerTask {
private static int textPixelPosition;
private final MarqueeFrame frame;
private final MarqueeModel model;
public DisplayTextPixelsTimerTask(MarqueeFrame frame, MarqueeModel model) {
this.frame = frame;
this.model = model;
textPixelPosition = 0;
}
#Override
public void run() {
model.copyTextPixels(textPixelPosition);
repaint();
textPixelPosition++;
textPixelPosition %= model.getTextPixelWidth();
}
private void repaint() {
SwingUtilities.invokeLater(frame::repaintMarqueePanel);
}
}
MarqueeModel
package com.ggl.marquee.model;
import javax.swing.DefaultListModel;
import com.ggl.marquee.runnable.DisplayTextPixelsTimerTask;
import com.ggl.marquee.view.MarqueeFrame;
import java.util.Timer;
import java.util.TimerTask;
public class MarqueeModel {
private static final int marqueeWidth = 120;
private static final long FPS_TARGET = 30;
private static final long DELAY_TIME = (long) (1000.0d / FPS_TARGET);
private boolean[][] marqueePixels;
private boolean[][] textPixels;
private TimerTask dtpRunnable;
private MarqueeFontFactory fonts;
private MarqueeFrame frame;
public MarqueeModel() {
this.fonts = new MarqueeFontFactory();
this.marqueePixels = new boolean[marqueeWidth][getMarqueeHeight()];
}
public void setFrame(MarqueeFrame frame) {
this.frame = frame;
}
public MarqueeFontFactory getFonts() {
return fonts;
}
public DefaultListModel<MarqueeFont> getDefaultListModel() {
return fonts.getFontList();
}
public MarqueeFont getDefaultFont() {
return fonts.getDefaultFont();
}
public void setDefaultFont(MarqueeFont defaultFont) {
fonts.setDefaultFont(defaultFont);
}
public boolean[][] getMarqueePixels() {
return marqueePixels;
}
public boolean getMarqueePixel(int width, int height) {
return marqueePixels[width][height];
}
public int getMarqueeWidth() {
return marqueeWidth;
}
public int getMarqueeHeight() {
return fonts.getCharacterHeight();
}
public boolean[][] getTextPixels() {
return textPixels;
}
public int getTextPixelWidth() {
return textPixels.length;
}
private void startDtpRunnable() {
dtpRunnable = new DisplayTextPixelsTimerTask(frame, this);
//running timer task as daemon thread
Timer timer = new Timer(true);
timer.scheduleAtFixedRate(dtpRunnable, 0, DELAY_TIME);
}
public void stopDtpRunnable() {
if (dtpRunnable != null) {
dtpRunnable.cancel();
dtpRunnable = null;
}
}
public void setTextPixels(boolean[][] textPixels) {
this.textPixels = textPixels;
if (textPixels.length < getMarqueeWidth()) {
this.marqueePixels = copyCharacterPixels(0, textPixels,
marqueePixels);
} else {
startDtpRunnable();
}
}
public void resetPixels() {
for (int i = 0; i < getMarqueeWidth(); i++) {
for (int j = 0; j < getMarqueeHeight(); j++) {
marqueePixels[i][j] = false;
}
}
}
public void setAllPixels() {
for (int i = 0; i < getMarqueeWidth(); i++) {
for (int j = 0; j < getMarqueeHeight(); j++) {
marqueePixels[i][j] = true;
}
}
}
public boolean[][] copyCharacterPixels(int position,
boolean[][] characterPixels, boolean[][] textPixels) {
for (int i = 0; i < characterPixels.length; i++) {
for (int j = 0; j < characterPixels[i].length; j++) {
textPixels[i + position][j] = characterPixels[i][j];
}
}
return textPixels;
}
public void copyTextPixels(int position) {
for (int i = 0; i < marqueePixels.length; i++) {
int k = i + position;
k %= textPixels.length;
for (int j = 0; j < textPixels[i].length; j++) {
marqueePixels[i][j] = textPixels[k][j];
}
}
}
}
I don't think it's possible to do more than that using only Java.
To be honest, the Marquee example is the smoothest i saw.

JButton image pixelated

I'm currently working on a personal use application in Java. I have started the design phase and I have a problem with adding an icon for my button. The image is always pixelated and I don't understand why.
Here is the code of the panel where the button is located:
BufferedImage lockIcon = ImageIO.read(new File(System.getProperty("user.dir")+"\\src\\main\\resources\\icon_lock.png"));
this.setSize(1280, 100);
GridBagLayout gridBagLayout = new GridBagLayout();
gridBagLayout.columnWidths = new int[]{100, 717, 0};
gridBagLayout.rowHeights = new int[]{100, 0};
gridBagLayout.columnWeights = new double[]{0.0, 0.0, Double.MIN_VALUE};
gridBagLayout.rowWeights = new double[]{0.0, Double.MIN_VALUE};
setLayout(gridBagLayout);
this.setBackground(new Color(142, 59, 70));
JButton lockingButton = new JButton(new ImageIcon(lockIcon));
lockingButton.setBorder(BorderFactory.createEmptyBorder());
lockingButton.setContentAreaFilled(false);
GridBagConstraints gbc_lockButton = new GridBagConstraints();
gbc_lockButton.fill = GridBagConstraints.BOTH;
gbc_lockButton.insets = new Insets(10, 5, 0, 5);
gbc_lockButton.gridx = 0;
gbc_lockButton.gridy = 0;
add(lockingButton, gbc_lockButton);
My icon is a .png with a size of 100x100 pixels and a resolution of 300 dpi and the size of my frame is 1280x720 pixels. I've already tried to change the image size and I also to modify the layout of my panel but it didn't change anything.
Here is a screenshot of the result:
So my question is: How can I solve this problem, what am I doing wrong?
I tested your code and to be honest I'm a bit confused...
JButton itself does not scale the image to fill, it just gets centered, so I don't see a reason for it to loose quality (tested it with my own icon and it looked fine).
In general I do not recommend scaling icons with the rest of the UI (dynamic size determined by the LayoutManager), so you should be able to scale the image before adding it to the button using following line:
image = image.getScaledInstance(width, height, Image.SCALE_SMOOTH);
If you still wish to scale the image with the UI you override the buttons getIcon() method and do the scaling there.
As stated in my comment, if you want to do really high end scaling you could try the options provided here: Java - resize image without losing quality
BONUS:
I'm not sure why it would pixelate in your case, but I can give you something that may help:
I created a Component specifically for Images and specialized on being used as buttons.
It will scale the image with the smooth option. It will also abide if the layout forces a size or you set one manually and scale the image (keeping the aspect ratio) instead of centering.
If no size is set, it will default to text height.
You may also call jImage.generateStateImages(); to let it automatically create clicked, hovered and disabled images (by altering brightness and gray-scaling).
As soon as you add an action listener it will start behaving (but not looking) like a JButton.
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.awt.image.FilteredImageSource;
import java.awt.image.ImageFilter;
import java.awt.image.ImageProducer;
import java.awt.image.WritableRaster;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.GrayFilter;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
public class JImage extends JLabel{
private static final long serialVersionUID = 1L;
private Image image;
private Image disabledImage;
private Image hoveredImage;
private Image clickedImage;
private final Map<Image, Image> scaledInstances = new HashMap<Image, Image>();
private boolean enabled = true;
private boolean hovered;
private boolean clicked;
private final List<ActionListener> actionListeners = new ArrayList<ActionListener>();
private boolean isCursorSet = false;
private boolean mouseListenersInitialized = false;
public JImage(Icon icon){
this(toImage(icon));
}
public JImage(Image image){
this.image = image;
addFocusListener(new FocusListener() {
#Override
public void focusLost(FocusEvent e) {
repaint();
}
#Override
public void focusGained(FocusEvent e) {
repaint();
}
});
addKeyListener(new KeyAdapter() {
#Override
public void keyPressed(KeyEvent e) {
if(e.getKeyCode() == KeyEvent.VK_ENTER){
if(!actionListeners.isEmpty()){
doClick();
}
}
}
});
}
private void initMouseListeners(){
addMouseListener(new MouseAdapter() {
#Override
public void mouseReleased(MouseEvent e) {
clicked = false;
repaint();
}
#Override
public void mousePressed(MouseEvent e) {
clicked = true;
repaint();
}
#Override
public void mouseExited(MouseEvent e) {
hovered = false;
repaint();
}
#Override
public void mouseEntered(MouseEvent e) {
hovered = true;
repaint();
}
#Override
public void mouseClicked(MouseEvent e) {
doClick(e.getButton());
}
});
mouseListenersInitialized = true;
}
public void doClick(){
doClick(MouseEvent.BUTTON1);
}
public void doClick(int mouseButton){
if(isEnabled() && mouseButton == MouseEvent.BUTTON1){
for(ActionListener actionListener : getActionListeners()) actionListener.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "mouseClicked"));
}
}
#Override
public Dimension getPreferredSize(){
if(super.isPreferredSizeSet()){
return super.getPreferredSize();
}else if(getImage() != null){
int height = getFontMetrics(getFont()).getHeight();
double imageRatio = (double) getImage().getWidth(null) / getImage().getHeight(null);
int width = (int) (height * imageRatio);
return new Dimension(width, height);
}else return new Dimension(0, 0);
}
#Override
public Dimension getMinimumSize(){
return getPreferredSize();
}
#Override
protected void paintComponent(Graphics g){
super.setCursor(getCursor());
super.paintComponent(g);
Image image;
if(!isEnabled() && getDisabledImage() != null) image = getDisabledImage();
else if(clicked && getClickedImage() != null) image = getClickedImage();
else if((hovered || isFocusOwner()) && getHoveredImage() != null) image = getHoveredImage();
else image = this.getImage();
if(image != null){
if(image.getWidth(null) == 0 || image.getHeight(null) == 0) System.out.println("not yet loaded");
Insets insets;
if(getBorder() == null){
insets = new Insets(0, 0, 0, 0);
}else{
insets = getBorder().getBorderInsets(this);
}
int width = getWidth() - insets.right - insets.left;
int height = getHeight() - insets.bottom - insets.top;
double imageRatio = (double) image.getWidth(null) / image.getHeight(null);
double containerRatio = (double) width / height;
if(imageRatio > containerRatio){
height = (int) (width / imageRatio);
}else{
width = (int) (height * imageRatio);
}
int x = (getWidth() - width) / 2;
int y = (getHeight() - height) / 2;
Image scaled = scaledInstances.get(image);
if(scaled == null || scaled.getWidth(null) != width || scaled.getHeight(null) != height){
scaled = image.getScaledInstance(width, height, Image.SCALE_SMOOTH);
scaledInstances.put(image, scaled);
}
g.drawImage(scaled, x, y, null);
}
}
public void scalePreferredSize(float scale){
Dimension prefSize = getPreferredSize();
prefSize.width *= scale;
prefSize.height *= scale;
setPreferredSize(prefSize);
}
/**
* Convenience method to use JImages as Buttons
* <br/>Adds a click listener.
* <br/>Also changes the cursor to a hand cursor when on top of this component.
* <br/>To revert this, call {#link #setCursor(java.awt.Cursor)}
* #param actionListener the listener to be called on click
*/
public void addActionListener(final ActionListener actionListener) {
actionListeners.add(actionListener);
if(!mouseListenersInitialized) initMouseListeners();
setFocusable(true);
}
protected List<ActionListener> getActionListeners(){
return actionListeners;
}
public void generateStateImages(){
generateStateImages(true, true, true);
}
public void generateStateImages(boolean disabled, boolean hovered, boolean clicked){
if(image == null) throw new IllegalStateException("Cannot generate state images while default image is null.");
if(disabled){
this.disabledImage = grayScale(image);
}
if(hovered){
this.hoveredImage = changeBrightness(image, 1.2f);
}
if(clicked){
this.clickedImage = changeBrightness(image, 0.8f);
}
if(!mouseListenersInitialized) initMouseListeners();
}
public Image getImage() {
return image;
}
public void setImage(Image image) {
this.image = image;
repaint();
}
public Image getDisabledImage() {
return disabledImage;
}
public void setDisabledImage(Image disabledImage) {
this.disabledImage = disabledImage;
if(!mouseListenersInitialized && disabledImage != null) initMouseListeners();
repaint();
}
public Image getHoveredImage() {
return hoveredImage;
}
public void setHoveredImage(Image hoveredImage) {
this.hoveredImage = hoveredImage;
if(!mouseListenersInitialized && hoveredImage != null) initMouseListeners();
repaint();
}
public Image getClickedImage() {
return clickedImage;
}
public void setClickedImage(Image clickedImage) {
this.clickedImage = clickedImage;
if(!mouseListenersInitialized && clickedImage != null) initMouseListeners();
repaint();
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
repaint();
}
public boolean isHovered() {
return hovered;
}
public boolean isClicked() {
return clicked;
}
#Override
public Cursor getCursor(){
if(!isCursorSet && !actionListeners.isEmpty() && isEnabled()) return Cursor.getPredefinedCursor(isEnabled() ? Cursor.HAND_CURSOR : Cursor.DEFAULT_CURSOR);
else return super.getCursor();
}
#Override
public void setCursor(Cursor cursor){
super.setCursor(cursor);
isCursorSet = true;
}
/**
* #return a copy that will update its image when the original its updated.
*/
public JImage createDelegate(){
final JImage thisImage = this;
JImage delegate = new JImage(getImage()){
public boolean isEnabled() {
return thisImage.isEnabled();
}
public List<ActionListener> getActionListeners(){
List<ActionListener> actionListeners = new ArrayList<ActionListener>(thisImage.getActionListeners());
actionListeners.addAll(super.getActionListeners());
return actionListeners;
}
public Image getImage() {
return thisImage.getImage();
}
public Image getDisabledImage() {
return thisImage.getDisabledImage();
}
public Image getHoveredImage() {
return thisImage.getHoveredImage();
}
public Image getClickedImage() {
return thisImage.getClickedImage();
}
};
return delegate;
}
// Extracted from my image utilities
private static Image grayScale(Image image){
ImageFilter filter = new GrayFilter(true, 50);
ImageProducer producer = new FilteredImageSource(image.getSource(), filter);
return Toolkit.getDefaultToolkit().createImage(producer);
}
// Extracted from my image utilities
private static Image changeBrightness(Image image, float mult) {
BufferedImage newImage = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_ARGB);
newImage.getGraphics().drawImage(image, 0, 0, null);
WritableRaster wr = newImage.getRaster();
int[] pixel = new int[4];
int[] overflows = new int[3];
for(int i = 0; i < wr.getWidth(); i++){
for(int j = 0; j < wr.getHeight(); j++){
wr.getPixel(i, j, pixel);
pixel[0] = (int) (pixel[0] * mult);
pixel[1] = (int) (pixel[1] * mult);
pixel[2] = (int) (pixel[2] * mult);
// if a value exceeds 255, the other color values will instead be raised to bleach the image
overflows[0] = 0;
overflows[1] = 0;
overflows[2] = 0;
if(pixel[0] > 255){
overflows[1] += pixel[0] - 255;
overflows[2] += pixel[0] - 255;
pixel[0] = 255;
}
if(pixel[1] > 255){
pixel[0] += pixel[1] - 255;
pixel[2] += pixel[1] - 255;
pixel[1] = 255;
}
if(pixel[2] > 255){
pixel[0] += pixel[2] - 255;
pixel[1] += pixel[2] - 255;
pixel[2] = 255;
}
pixel[0] = Math.min(255, pixel[0] + overflows[0]);
pixel[1] = Math.min(255, pixel[1] + overflows[1]);
pixel[2] = Math.min(255, pixel[2] + overflows[2]);
wr.setPixel(i, j, pixel);
}
}
return newImage;
}
// Extracted from my image utilities
private static Image toImage(Icon icon){
if (icon instanceof ImageIcon) {
return ((ImageIcon) icon).getImage();
} else {
int w = icon.getIconWidth();
int h = icon.getIconHeight();
BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
Graphics g = image.getGraphics();
icon.paintIcon(null, g, 0, 0);
g.dispose();
return image;
}
}
}

Drawing lines between JLabel in 2d array grid

I'm trying to draw lines between cells which are JLabel but fail to get their coordinates.
The lines must follow a path from a startPoint to an endPoint.
In my View class, the below code creates the grid:
void createGrid(int rows, int cols) {
MyMouseListener listener = new MyMouseListener();
setRows(rows);
mainPanel.setLayout(new GridLayout(rows, cols, 1, 1));
mainPanel.setBorder(BorderFactory.createLineBorder(Color.BLACK));
mainPanel.setBackground(Color.DARK_GRAY);
grid = new JLabel[rows][cols];
for (int r = 0; r < grid.length; r++) {
for (int c = 0; c < grid[r].length; c++) {
grid[r][c] = new JLabel(iconMap.get(Token.VIDE));
grid[r][c].addMouseListener(listener);
grid[r][c].setOpaque(true);
grid[r][c].setBackground(Color.WHITE);
//grid[r][c].setPreferredSize(new Dimension(ICON_W, ICON_W));
mainPanel.add(grid[r][c]);
}
}
}
While this one creates the icons:
private Icon createIcon(Color color) {
BufferedImage img = new BufferedImage(ICON_W, ICON_W, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = img.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(color);
g2.fillOval(1, 1, ICON_W - 2, ICON_W - 2); // int ICON_W = 20;
g2.dispose();
return new ImageIcon(img);
}
The whole code for the View class: https://pastebin.com/sgyn6rSR
Code for the path algo: https://pastebin.com/Gcw9YVMF
I've tried something like below, but doesn't work
Graphics2D g2d = (Graphics2D) g.create();
for (JLabel[] grid : cells) {
g2d.setColor(Color.BLUE);
Point startPoint = grid[0].getLocation();
Point endPoint = grid[1].getLocation();
startPoint.x += (grid[0].getWidth() / 2);
startPoint.y += (grid[1].getHeight()/ 2);
endPoint.x += (grid[0].getWidth() / 2);
endPoint.y += (grid[1].getHeight()/ 2);
if(startPoint != null){
g2d.draw(new Line2D.Double(startPoint, endPoint));
}
startPoint = endPoint;
}
Override the paintComponent() method of the panel you add the labels to.
The basic code would be:
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
for (int i = 0; i < getComponentCount() - 1; i++)
{
Component component1 = getComponent(i);
Point point1 = component1.getLocation();
Component component2 = getComponent(i + 1);
Point point2 = component2.getLocation();
g.drawLine(point1.x, point1.y, point2.x, point2.y);
}
}
Once you get this working you can modify the logic to draw the line from the from/to the center of each component.
That took a long time. Sorry but I have a lot of context to the original question, including the base View and Path code :P
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Line2D;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.StringJoiner;
import javax.swing.BorderFactory;
import javax.swing.DebugGraphics;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class View extends JPanel {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
DrawLines drawLines = new DrawLines();
JFrame frame = new JFrame();
frame.setGlassPane(drawLines);
frame.add(new View(20, 20, drawLines));
frame.getGlassPane().setVisible(true);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
enum Token {
VIDE, CERCLE_BLEU, CERCLE_ROUGE
}
private static final int ICON_W = 20;
public static final String CELL_SELECTION = "cell selection";
private int rows;
private Token[][] tokens;
private JLabel[][] grid;
private Map<Token, Icon> iconMap = new EnumMap<>(Token.class);
private int selectedRow;
private int selectedCol;
//a collection of cells representing a path
private ArrayList<Cell> path;
private DrawLines drawLines;
View(int rows, int cols, DrawLines drawLines) {
this.drawLines = drawLines;
iconMap.put(Token.VIDE, createIcon(new Color(0, 0, 0, 0)));
iconMap.put(Token.CERCLE_BLEU, createIcon(Color.BLUE));
iconMap.put(Token.CERCLE_ROUGE, createIcon(Color.RED));
createGrid(rows, cols);
}
private Icon createIcon(Color color) {
BufferedImage img = new BufferedImage(ICON_W, ICON_W, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = img.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(color);
g2.fillOval(1, 1, ICON_W - 2, ICON_W - 2);
g2.dispose();
return new ImageIcon(img);
}
void createGrid(int rows, int cols) {
tokens = new Token[rows][cols];
MyMouseListener listener = new MyMouseListener();
setRows(rows);
setLayout(new GridLayout(rows, cols, 1, 1));
setBorder(BorderFactory.createLineBorder(Color.BLACK));
setBackground(Color.DARK_GRAY);
grid = new JLabel[rows][cols];
for (int r = 0; r < grid.length; r++) {
for (int c = 0; c < grid[r].length; c++) {
grid[r][c] = new JLabel(iconMap.get(Token.VIDE));
grid[r][c].addMouseListener(listener);
grid[r][c].setOpaque(true);
grid[r][c].setBackground(Color.WHITE);
grid[r][c].setPreferredSize(new Dimension(ICON_W, ICON_W));
add(grid[r][c]);
}
}
set(6, 6, Token.CERCLE_ROUGE);
set(6, 7, Token.CERCLE_ROUGE);
set(6, 8, Token.CERCLE_ROUGE);
set(6, 9, Token.CERCLE_ROUGE);
set(7, 9, Token.CERCLE_ROUGE);
set(8, 9, Token.CERCLE_ROUGE);
set(9, 9, Token.CERCLE_ROUGE);
set(10, 9, Token.CERCLE_ROUGE);
set(10, 6, Token.CERCLE_ROUGE);
set(10, 7, Token.CERCLE_ROUGE);
set(10, 8, Token.CERCLE_ROUGE);
set(10, 9, Token.CERCLE_ROUGE);
set(7, 6, Token.CERCLE_ROUGE);
set(8, 6, Token.CERCLE_ROUGE);
set(9, 6, Token.CERCLE_ROUGE);
//
// set(7, 6, Token.CERCLE_ROUGE);
// set(7, 7, Token.CERCLE_BLEU);
// set(7, 8, Token.CERCLE_BLEU);
//
// set(8, 6, Token.CERCLE_ROUGE);
// set(8, 7, Token.CERCLE_ROUGE);
// set(8, 8, Token.CERCLE_ROUGE);
//
// set(5, 7, Token.CERCLE_ROUGE);
// set(5, 8, Token.CERCLE_ROUGE);
// set(5, 9, Token.CERCLE_ROUGE);
// set(6, 9, Token.CERCLE_ROUGE);
// set(7, 9, Token.CERCLE_ROUGE);
Path path = new Path(tokens);
if (path.findPath(new int[]{6, 6})) {
setPath(path.getPath());
}
}
void set(int row, int col, Token token) {
grid[row][col].setIcon(iconMap.get(token));
tokens[row][col] = token;
}
int getSelectedRow() {
return selectedRow;
}
int getSelectedCol() {
return selectedCol;
}
void setCell(Token token, int row, int col) {
grid[row][col].setIcon(iconMap.get(token));
}
int getRows() {
return rows;
}
void setRows(int rows) {
this.rows = rows;
}
//added to each cell to listen to mouse clicks
//fires property change with cell index
private class MyMouseListener extends MouseAdapter {
#Override
public void mousePressed(MouseEvent e) {
JLabel selection = (JLabel) e.getSource();
for (int r = 0; r < grid.length; r++) {
for (int c = 0; c < grid[r].length; c++) {
if (selection == grid[r][c]) {
selectedRow = r;
selectedCol = c;
int index = (r * grid[r].length) + c;
selection.setIcon(iconMap.get(Token.CERCLE_BLEU));
tokens[selectedRow][selectedCol] = Token.CERCLE_BLEU;
System.out.println(c + "x" + r + " = " + index);
firePropertyChange(CELL_SELECTION, -1, index);
Path path = new Path(tokens);
if (path.findPath(new int[]{selectedRow, selectedCol})) {
setPath(path.getPath());
}
}
}
}
}
}
//add listener to listen to property changes fired by MyMouseListener
public void addPropertyChangeListener(PropertyChangeListener viewListener) {
addPropertyChangeListener(viewListener);
}
void setPath(ArrayList<Cell> path) {
this.path = path;
if (path != null) {
drawPath();
}
}
//highlight path by changing background color.
//It can be changed to draw lines between cells
private void drawPath() {
if (path != null) {
List<JLabel> labels = new ArrayList<>(25);
for (Cell cell : path) {
JLabel label = grid[cell.y][cell.x];
label.setBackground(Color.YELLOW);
labels.add(label);
}
for (int r = 0; r < grid.length; r++) {
for (int c = 0; c < grid[r].length; c++) {
if (!path.contains(new Cell(c, r))) {
grid[r][c].setBackground(Color.WHITE);
}
}
}
drawLines.setPath(labels);
}
}
void refresh() {
repaint();
}
//This is what I added to try drawing lines
public static class DrawLines extends JLabel {
List<JLabel> path;
public void setPath(List<JLabel> path) {
this.path = new ArrayList<>(path);
this.path.add(path.get(0));
repaint();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
if (path != null) {
Graphics2D g2d = (Graphics2D) g.create();
Point startPoint = null;
for (JLabel label : path) {
g2d.setColor(Color.DARK_GRAY);
Point endPoint = label.getLocation();
endPoint.x += (label.getWidth() / 2);
endPoint.y += (label.getHeight() / 2);
if (startPoint != null) {
g2d.draw(new Line2D.Float(startPoint, endPoint));
}
startPoint = endPoint;
}
g2d.dispose();
}
}
}
class Cell {
private int x, y;
public Cell(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
#Override
public int hashCode() {
int hash = 7;
return hash;
}
#Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Cell other = (Cell) obj;
if (this.x != other.x) {
return false;
}
if (this.y != other.y) {
return false;
}
return true;
}
#Override
public String toString() {
return x + "x" + y;
}
}
class Path extends Stack<Cell> {
private Token[][] grid;
//a path shorter than min can not surround any cell
private static final int MIN_PATH_LEGTH = 3;
//a collection of cells that has been tested
private ArrayList<Cell> checked;
//represents the cell where the search starts from
Cell origin;
//represents the token of the origin
Token originToken;
private int rows;
private int cols;
Path(Token[][] grid) {
this.grid = grid;
rows = grid.length;
cols = grid[0].length;
}
//search for a path
boolean findPath(int[] origin) {
int row = origin[0], col = origin[1];
this.origin = new Cell(col, row);
//represents the token of the origin
originToken = grid[row][col];
//initialize list of checked items
checked = new ArrayList<>();
boolean found = findPath(row, col);
if (found) {
printPath();
} else {
System.out.println("No path found");
}
return found;
}
//recursive method to find path. a cell is represented by its row, col
//returns true when path was found
private boolean findPath(int row, int col) {
//check if cell has the same token as origin
if (grid[row][col] != originToken) {
return false;
}
Cell cell = new Cell(col, row);
//check if this cell was tested before to avoid checking again
if (checked.contains(cell)) {
return false;
}
//get cells neighbors
ArrayList<Cell> neighbors = getNeighbors(row, col);
//check if solution found. If path size > min and cell
//neighbors contain the origin it means that path was found
if ((size() >= MIN_PATH_LEGTH) && neighbors.contains(origin)) {
add(cell);
return true;
}
//add cell to checked list
checked.add(cell);
//add cell to path
add(cell);
//if path was not found check cell neighbors
for (Cell neighbor : neighbors) {
boolean found = findPath(neighbor.getY(), neighbor.getX());
if (found) {
return true;
}
}
//path not found
pop(); //remove last element from stack
return false;
}
//return a list of all neighbors of cell row, col
private ArrayList<Cell> getNeighbors(int row, int col) {
ArrayList<Cell> neighbors = new ArrayList<Cell>();
for (int colNum = col - 1; colNum <= (col + 1); colNum += 1) {
for (int rowNum = row - 1; rowNum <= (row + 1); rowNum += 1) {
if (!((colNum == col) && (rowNum == row))) {
if (withinGrid(rowNum, colNum)) {
neighbors.add(new Cell(colNum, rowNum));
}
}
}
}
return neighbors;
}
private boolean withinGrid(int colNum, int rowNum) {
if ((colNum < 0) || (rowNum < 0)) {
return false;
}
if ((colNum >= cols) || (rowNum >= rows)) {
return false;
}
return true;
}
//use for testing
private void printPath() {
StringJoiner sj = new StringJoiner(",", "Path: ", "");
for (Cell cell : this) {
sj.add(cell.toString());
}
System.out.println(sj);
}
public ArrayList<Cell> getPath() {
ArrayList<Cell> cl = new ArrayList<>();
cl.addAll(this);
return cl;
}
}
}
But The lines are not repainting. Is there as way to save them?
The DrawLines will draw the path you pass it. To provide a "history", you need to maintain another List of all the paths you pass, for example...
public static class DrawLines extends JLabel {
private List<List<JLabel>> history;
public DrawLines() {
history = new ArrayList<>(25);
}
public void setPath(List<JLabel> path) {
List<JLabel> copy = new ArrayList<>(path);
copy.add(path.get(0));
history.add(copy);
repaint();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
Point startPoint = null;
for (List<JLabel> path : history) {
for (JLabel label : path) {
g2d.setColor(Color.DARK_GRAY);
Point endPoint = label.getLocation();
endPoint.x += (label.getWidth() / 2);
endPoint.y += (label.getHeight() / 2);
if (startPoint != null) {
g2d.draw(new Line2D.Float(startPoint, endPoint));
}
startPoint = endPoint;
}
}
g2d.dispose();
}
}
Here is the View class modified to add lines as requested.
Please review and see comments :
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeListener;
import java.util.EnumMap;
import java.util.Map;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class View {
public static final String CELL_SELECTION = "cell selection";
private final static int GAP = 2;
private static int iconWidth;
private int rows;
private JPanel mainPanel;
private JLabel[][] grid;
private Map<Token, Icon> iconMap = new EnumMap<>(Token.class);
private int selectedRow;
private int selectedCol;
//a collection of cells representing a path
private CellsList path;
//a pane to hold two panels one on top of the other
JPanel layeredPanel;
View() {
mainPanel = new JPanel();
//add layerd pane to hold two panels one on top of the other
layeredPanel = new JPanel();
//set GridBagLayout to allow placing two components one on top of the other
layeredPanel.setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.weightx = 1;
gbc.weighty = 1;
gbc.fill = GridBagConstraints.BOTH;
//add main panel to layered panel
layeredPanel.add(mainPanel, gbc);
//create and add a drawing panel
DrawJPanel drawPanel = new DrawJPanel();
drawPanel.setOpaque(false); //set if to transparent so it does not
//hide mainPanel under it
layeredPanel.add(drawPanel, gbc);
//set z-order (depth) of two panels
layeredPanel.setComponentZOrder(mainPanel,1);
layeredPanel.setComponentZOrder(drawPanel,0);
}
void createGrid(int rows, int cols) {
MyMouseListener listener = new MyMouseListener();
setRows(rows);
setIcons();
mainPanel.setLayout(new GridLayout(rows, cols, 1, 1));
mainPanel.setBorder(BorderFactory.createLineBorder(Color.BLACK));
mainPanel.setBackground(Color.BLACK);
grid = new JLabel[rows][cols];
for (int r = 0; r < grid.length; r++) {
for (int c = 0; c < grid[r].length; c++) {
grid[r][c] = new JLabel(iconMap.get(Token.VIDE));
grid[r][c].addMouseListener(listener);
grid[r][c].setOpaque(true);
grid[r][c].setBackground(Color.WHITE);
mainPanel.add(grid[r][c]);
}
}
layeredPanel.setPreferredSize(mainPanel.getPreferredSize());
}
int getSelectedRow() {
return selectedRow;
}
int getSelectedCol() {
return selectedCol;
}
void setCell(Token token, int row, int col) {
grid[row][col].setIcon(iconMap.get(token));
}
int getRows() {
return rows;
}
void setRows(int rows) {
this.rows = rows;
}
//added to each cell to listen to mouse clicks
//fires property change with cell index
private class MyMouseListener extends MouseAdapter {
#Override
public void mousePressed(MouseEvent e) {
JLabel selection = (JLabel) e.getSource();
for (int r = 0; r < grid.length; r++) {
for (int c = 0; c < grid[r].length; c++) {
if (selection == grid[r][c]) {
selectedRow = r;
selectedCol = c;
int index = (r * grid[r].length) + c;
mainPanel.firePropertyChange(CELL_SELECTION, -1, index);
}
}
}
}
}
void start() {
JFrame frame = new JFrame("MVC Pha");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(layeredPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
//add listener to listen to property changes fired by MyMouseListener
void addPropertyChangeListener(PropertyChangeListener viewListener) {
mainPanel.addPropertyChangeListener(viewListener);
}
void setPath(CellsList path) {
this.path = path;
if(path != null) {
drawPath();
}
}
//highlight path by changing background color.
//It can be changed to draw lines between cells
private void drawPath() {
for (int row = 0; row < grid.length; row++) {
for (int col = 0; col < grid[row].length; col++) {
if((path != null) && path.contains(new int[] {row,col})) {
grid[row][col].setBackground(Color.YELLOW);
} else {
grid[row][col].setBackground(Color.WHITE);
}
}
}
}
//receives rectangle and returns its center point
private Point getComponentCenter(Rectangle bounds) {
int xCenter = (int) ((bounds.getX()+ bounds.getMaxX())/2);
int yCenter = (int) ((bounds.getMinY()+ bounds.getMaxY())/2);
return new Point(xCenter, yCenter);
}
void refresh() {
layeredPanel.repaint();
}
private void setIcons() {
if(rows <= 20) {
iconWidth = 24;
} else if( rows <= 40 ) {
iconWidth = 20;
} else if( rows <= 60 ) {
iconWidth = 15;
} else if( rows <= 80 ) {
iconWidth = 12;
} else {
iconWidth = 8;
}
iconMap.put(Token.VIDE, createIcon(new Color(0, 0, 0, 0)));
iconMap.put(Token.CERCLE_BLEU, createIcon(Color.BLUE));
iconMap.put(Token.CERCLE_ROUGE, createIcon(Color.RED));
}
private Icon createIcon(Color color) {
BufferedImage img = new BufferedImage(iconWidth, iconWidth, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = img.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(color);
g2.fillOval(1, 1, iconWidth - GAP, iconWidth - GAP);
g2.dispose();
return new ImageIcon(img);
}
//a panel to draw line on
class DrawJPanel extends JPanel {
#Override
protected void paintComponent(Graphics g){
super.paintComponent(g);
if((path == null) || path.isEmpty() ) {
return;
}
//calculate points to construct line along the path
Point[] polygon = new Point[path.size()];
int i=0;
for(int[] address : path) { //iterate over path
//get cell location (row / col)
int row = address[0]; int col = address[1];
//get label in that location
JLabel lable = grid[row][col];
//add center of label as a point in polygon
polygon[i++] = getComponentCenter(lable.getBounds());
}
//draw points in polygon
g.setColor(Color.CYAN); //set line color and width
Graphics2D g2 = (Graphics2D) g;
g2.setStroke(new BasicStroke(2));
int j;
for (j = 0; j < (polygon.length -1); j++) {
Point point1 = polygon[j];
Point point2 = polygon[j+1];
g2.draw(new Line2D.Float(point1, point2));
}
//add line to connect last point to first
g2.draw(new Line2D.Float(polygon[j], polygon[0]));
}
}
}
The following answer present the same solution as in my previous answer.
For the benefit of all (other users, those who try to answer, as well as for the poster), I think the question should have been asked and answer as a mcve :
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.Line2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class View {
private static final String WHITE = "white";
private static final String BLUE = "blue";
private final static int GAP = 2;
private static final int iconWidth = 24;
private Map<String, Icon> iconMap = new HashMap<>();
private JPanel mainPanel;
private JLabel[][] grid;
//a collection of cells representing a path
private ArrayList<int[]> path;
//a pane to hold two panels one on top of the other
JPanel layeredPanel;
View(int rows) {
JFrame frame = new JFrame("Line between JLabels");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainPanel = new JPanel();
//add layered pane to hold two panels one on top of the other
layeredPanel = new JPanel();
//set GridBagLayout to allow placing two components one on top of the other
layeredPanel.setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.weightx = 1;
gbc.weighty = 1;
gbc.fill = GridBagConstraints.BOTH;
//add main panel to layered panel
layeredPanel.add(mainPanel, gbc);
//create and add a drawing panel
DrawJPanel drawPanel = new DrawJPanel();
drawPanel.setOpaque(false); //set if to transparent so it does not
//hide mainPanel under it
layeredPanel.add(drawPanel, gbc);
//set z-order (depth) of two panels
layeredPanel.setComponentZOrder(mainPanel,1);
layeredPanel.setComponentZOrder(drawPanel,0);
path = new ArrayList<>();
setTestData();
createGrid(rows,rows);
frame.getContentPane().add(layeredPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
void createGrid(int rows, int cols) {
setIcons();
mainPanel.setLayout(new GridLayout(rows, cols, 1, 1));
mainPanel.setBorder(BorderFactory.createLineBorder(Color.BLACK));
mainPanel.setBackground(Color.BLACK);
grid = new JLabel[rows][cols];
for (int r = 0; r < grid.length; r++) {
for (int c = 0; c < grid[r].length; c++) {
if(isInPath(new int[] {r,c})) {
grid[r][c] = new JLabel(iconMap.get(BLUE));
} else {
grid[r][c] = new JLabel(iconMap.get(WHITE));
}
grid[r][c].setOpaque(true);
grid[r][c].setBackground(Color.WHITE);
mainPanel.add(grid[r][c]);
}
}
layeredPanel.setPreferredSize(mainPanel.getPreferredSize());
}
//receives rectangle and returns its center point
private Point getComponentCenter(Rectangle bounds) {
int xCenter = (int) ((bounds.getX()+ bounds.getMaxX())/2);
int yCenter = (int) ((bounds.getMinY()+ bounds.getMaxY())/2);
return new Point(xCenter, yCenter);
}
private void setIcons() {
iconMap.put(WHITE, createIcon(new Color(0, 0, 0, 0)));
iconMap.put(BLUE, createIcon(Color.BLUE));
}
private Icon createIcon(Color color) {
BufferedImage img = new BufferedImage(iconWidth, iconWidth, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = img.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(color);
g2.fillOval(1, 1, iconWidth - GAP, iconWidth - GAP);
g2.dispose();
return new ImageIcon(img);
}
private boolean isInPath(int[] cell) {
if((path == null) || path.isEmpty() ) {
return false;
}
for(int[] p : path) {
if((cell[0]== p[0]) && (cell[1] == p[1])) {
return true;
}
}
return false;
}
//a panel to draw line on
class DrawJPanel extends JPanel {
#Override
protected void paintComponent(Graphics g){
super.paintComponent(g);
if((path == null) || path.isEmpty() ) {
return;
}
//calculate points to construct line along the path
Point[] polygon = new Point[path.size()];
int i=0;
for(int[] address : path) { //iterate over path
//get cell location (row / col)
int row = address[0]; int col = address[1];
//get label in that location
JLabel lable = grid[row][col];
//add center of label as a point in polygon
polygon[i++] = getComponentCenter(lable.getBounds());
}
//draw points in polygon
g.setColor(Color.CYAN); //set line color and width
Graphics2D g2 = (Graphics2D) g;
g2.setStroke(new BasicStroke(2));
int j;
for (j = 0; j < (polygon.length -1); j++) {
Point point1 = polygon[j];
Point point2 = polygon[j+1];
g2.draw(new Line2D.Float(point1, point2));
}
//add line to connect last point to first
g2.draw(new Line2D.Float(polygon[j], polygon[0]));
}
}
public static void main(String[] args) {
new View(10);
}
private void setTestData() {
path.add(new int[] {3,3});
path.add(new int[] {3,4});
path.add(new int[] {3,5});
path.add(new int[] {4,6});
path.add(new int[] {5,5});
path.add(new int[] {5,4});
path.add(new int[] {5,3});
path.add(new int[] {4,4});
}
}

Close InternalJFrame without selected the frame

I am really new for Java. I have question about getting the JInternalFrame . I searched the web and find the example. I modified the example a little bit, adding a close menuitem but it didn't work my code. In the project if I closed the JInternalFrame without select it first, then I have error. I tried to loop through the WindowMenu for getting the selected JcheckboxMenuitem, but it didn't get any components. Would someone tell me what to do.
There is the code:
// This is example is from Kjell Dirdal.
// Referenced from http://www.javaworld.com/javaworld/jw-05-2001/jw-0525-mdi.html
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.beans.PropertyVetoException;
import javax.swing.DefaultDesktopManager;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComponent;
import javax.swing.JDesktopPane;
import javax.swing.JFrame;
import javax.swing.JInternalFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JViewport;
import javax.swing.event.MenuEvent;
import javax.swing.event.MenuListener;
public class KjellDirdalNotepad extends JFrame {
private MDIDesktopPane desktop = new MDIDesktopPane();
private JMenuBar menuBar = new JMenuBar();
private JMenu fileMenu = new JMenu("File");
private JMenuItem newMenu = new JMenuItem("New");
private JScrollPane scrollPane = new JScrollPane();
private JMenuItem closeMenu=new JMenuItem("Close");
private int index=1;
private WindowMenu wMenu=null;
public KjellDirdalNotepad() {
menuBar.add(fileMenu);
wMenu=new WindowMenu(desktop);
menuBar.add(wMenu);
fileMenu.add(newMenu);
fileMenu.add(closeMenu);
setJMenuBar(menuBar);
setTitle("MDI Test");
scrollPane.getViewport().add(desktop);
getContentPane().setLayout(new BorderLayout());
getContentPane().add(scrollPane, BorderLayout.CENTER);
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
newMenu.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
desktop.add(new TextFrame(String.valueOf(index)));
index=index+1;
}
});
//I added the close menu
closeMenu.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
JInternalFrame f=desktop.getSelectedFrame();
if (f==null)
{
for (Component child: wMenu.getComponents()){
if (child instanceof WindowMenu.ChildMenuItem){
JCheckBoxMenuItem item=(JCheckBoxMenuItem) child;
if( item.isSelected()){
DisplayTestMsg(item.getText());
}
}
}
}
else{
f.dispose();
}
}
});
}
public void DisplayTestMsg(String msg){
//custom title, error icon
JTextArea textArea=new JTextArea(msg);
textArea.setColumns(30);
textArea.setLineWrap( true );
textArea.setWrapStyleWord( true );
textArea.setSize(textArea.getPreferredSize().width, 1);
JOptionPane.showMessageDialog(null,
textArea,
"Test",
JOptionPane.WARNING_MESSAGE);
}
public static void main(String[] args) {
KjellDirdalNotepad notepad = new KjellDirdalNotepad();
notepad.setSize(600, 400);
notepad.setVisible(true);
}
}
class TextFrame extends JInternalFrame {
private JTextArea textArea = new JTextArea();
private JScrollPane scrollPane = new JScrollPane();
public TextFrame(String title) {
setSize(200, 300);
setTitle("Edit Text-" + title);
setMaximizable(true);
setIconifiable(true);
setClosable(true);
setResizable(true);
scrollPane.getViewport().add(textArea);
getContentPane().setLayout(new BorderLayout());
getContentPane().add(scrollPane, BorderLayout.CENTER);
}
}
/**
* An extension of WDesktopPane that supports often used MDI functionality. This
* class also handles setting scroll bars for when windows move too far to the
* left or bottom, providing the MDIDesktopPane is in a ScrollPane.
*/
class MDIDesktopPane extends JDesktopPane {
private static int FRAME_OFFSET = 20;
private MDIDesktopManager manager;
public MDIDesktopPane() {
manager = new MDIDesktopManager(this);
setDesktopManager(manager);
setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
}
public void setBounds(int x, int y, int w, int h) {
super.setBounds(x, y, w, h);
checkDesktopSize();
}
public Component add(JInternalFrame frame) {
JInternalFrame[] array = getAllFrames();
Point p;
int w;
int h;
Component retval = super.add(frame);
checkDesktopSize();
if (array.length > 0) {
p = array[0].getLocation();
p.x = p.x + FRAME_OFFSET;
p.y = p.y + FRAME_OFFSET;
} else {
p = new Point(0, 0);
}
frame.setLocation(p.x, p.y);
if (frame.isResizable()) {
w = getWidth() - (getWidth() / 3);
h = getHeight() - (getHeight() / 3);
if (w < frame.getMinimumSize().getWidth())
w = (int) frame.getMinimumSize().getWidth();
if (h < frame.getMinimumSize().getHeight())
h = (int) frame.getMinimumSize().getHeight();
frame.setSize(w, h);
}
moveToFront(frame);
frame.setVisible(true);
try {
frame.setSelected(true);
} catch (PropertyVetoException e) {
frame.toBack();
}
return retval;
}
public void remove(Component c) {
super.remove(c);
checkDesktopSize();
}
/**
* Cascade all internal frames
*/
public void cascadeFrames() {
int x = 0;
int y = 0;
JInternalFrame allFrames[] = getAllFrames();
manager.setNormalSize();
int frameHeight = (getBounds().height - 5) - allFrames.length * FRAME_OFFSET;
int frameWidth = (getBounds().width - 5) - allFrames.length * FRAME_OFFSET;
for (int i = allFrames.length - 1; i >= 0; i--) {
allFrames[i].setSize(frameWidth, frameHeight);
allFrames[i].setLocation(x, y);
x = x + FRAME_OFFSET;
y = y + FRAME_OFFSET;
}
}
/**
* Tile all internal frames
*/
public void tileFrames() {
java.awt.Component allFrames[] = getAllFrames();
manager.setNormalSize();
int frameHeight = getBounds().height / allFrames.length;
int y = 0;
for (int i = 0; i < allFrames.length; i++) {
allFrames[i].setSize(getBounds().width, frameHeight);
allFrames[i].setLocation(0, y);
y = y + frameHeight;
}
}
/**
* Sets all component size properties ( maximum, minimum, preferred) to the
* given dimension.
*/
public void setAllSize(Dimension d) {
setMinimumSize(d);
setMaximumSize(d);
setPreferredSize(d);
}
/**
* Sets all component size properties ( maximum, minimum, preferred) to the
* given width and height.
*/
public void setAllSize(int width, int height) {
setAllSize(new Dimension(width, height));
}
private void checkDesktopSize() {
if (getParent() != null && isVisible())
manager.resizeDesktop();
}
}
/**
* Private class used to replace the standard DesktopManager for JDesktopPane.
* Used to provide scrollbar functionality.
*/
class MDIDesktopManager extends DefaultDesktopManager {
private MDIDesktopPane desktop;
public MDIDesktopManager(MDIDesktopPane desktop) {
this.desktop = desktop;
}
public void endResizingFrame(JComponent f) {
super.endResizingFrame(f);
resizeDesktop();
}
public void endDraggingFrame(JComponent f) {
super.endDraggingFrame(f);
resizeDesktop();
}
public void setNormalSize() {
JScrollPane scrollPane = getScrollPane();
int x = 0;
int y = 0;
Insets scrollInsets = getScrollPaneInsets();
if (scrollPane != null) {
Dimension d = scrollPane.getVisibleRect().getSize();
if (scrollPane.getBorder() != null) {
d.setSize(d.getWidth() - scrollInsets.left - scrollInsets.right, d.getHeight()
- scrollInsets.top - scrollInsets.bottom);
}
d.setSize(d.getWidth() - 20, d.getHeight() - 20);
desktop.setAllSize(x, y);
scrollPane.invalidate();
scrollPane.validate();
}
}
private Insets getScrollPaneInsets() {
JScrollPane scrollPane = getScrollPane();
if (scrollPane == null)
return new Insets(0, 0, 0, 0);
else
return getScrollPane().getBorder().getBorderInsets(scrollPane);
}
private JScrollPane getScrollPane() {
if (desktop.getParent() instanceof JViewport) {
JViewport viewPort = (JViewport) desktop.getParent();
if (viewPort.getParent() instanceof JScrollPane)
return (JScrollPane) viewPort.getParent();
}
return null;
}
protected void resizeDesktop() {
int x = 0;
int y = 0;
JScrollPane scrollPane = getScrollPane();
Insets scrollInsets = getScrollPaneInsets();
if (scrollPane != null) {
JInternalFrame allFrames[] = desktop.getAllFrames();
for (int i = 0; i < allFrames.length; i++) {
if (allFrames[i].getX() + allFrames[i].getWidth() > x) {
x = allFrames[i].getX() + allFrames[i].getWidth();
}
if (allFrames[i].getY() + allFrames[i].getHeight() > y) {
y = allFrames[i].getY() + allFrames[i].getHeight();
}
}
Dimension d = scrollPane.getVisibleRect().getSize();
if (scrollPane.getBorder() != null) {
d.setSize(d.getWidth() - scrollInsets.left - scrollInsets.right, d.getHeight()
- scrollInsets.top - scrollInsets.bottom);
}
if (x <= d.getWidth())
x = ((int) d.getWidth()) - 20;
if (y <= d.getHeight())
y = ((int) d.getHeight()) - 20;
desktop.setAllSize(x, y);
scrollPane.invalidate();
scrollPane.validate();
}
}
}
/**
* Menu component that handles the functionality expected of a standard
* "Windows" menu for MDI applications.
*/
class WindowMenu extends JMenu {
private MDIDesktopPane desktop;
private JMenuItem cascade = new JMenuItem("Cascade");
private JMenuItem tile = new JMenuItem("Tile");
public WindowMenu(MDIDesktopPane desktop) {
this.desktop = desktop;
setText("Window");
cascade.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
WindowMenu.this.desktop.cascadeFrames();
}
});
tile.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
WindowMenu.this.desktop.tileFrames();
}
});
addMenuListener(new MenuListener() {
public void menuCanceled(MenuEvent e) {
}
public void menuDeselected(MenuEvent e) {
removeAll();
}
public void menuSelected(MenuEvent e) {
buildChildMenus();
}
});
}
/* Sets up the children menus depending on the current desktop state */
private void buildChildMenus() {
int i;
ChildMenuItem menu;
JInternalFrame[] array = desktop.getAllFrames();
add(cascade);
add(tile);
if (array.length > 0)
addSeparator();
cascade.setEnabled(array.length > 0);
tile.setEnabled(array.length > 0);
for (i = 0; i < array.length; i++) {
menu = new ChildMenuItem(array[i]);
menu.setState(i == 0);
menu.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
JInternalFrame frame = ((ChildMenuItem) ae.getSource()).getFrame();
frame.moveToFront();
try {
frame.setSelected(true);
} catch (PropertyVetoException e) {
e.printStackTrace();
}
}
});
menu.setIcon(array[i].getFrameIcon());
add(menu);
}
}
/*
* This JCheckBoxMenuItem descendant is used to track the child frame that
* corresponds to a give menu.
*/
class ChildMenuItem extends JCheckBoxMenuItem {
private JInternalFrame frame;
public ChildMenuItem(JInternalFrame frame) {
super(frame.getTitle());
this.frame = frame;
}
public JInternalFrame getFrame() {
return frame;
}
}
}
If you want to remove the JPanel when a button is pressed, you can call JFrame.remove(panel) such as:
final JFrame frame = new JFrame("test");
final JPanel panel = new JPanel();
//add the panel at the start
frame.add(panel);
// when the buttons clicked
button.addActionListener(new ActionListener() {
#Override
public void ActionPerformed(ActionEvent e) {
frame.remove(panel);
frame.repaint();
};
});
I figured it out. I added the method on Class WindowMenu as belows. This method is called when the close button is click without selected internal frame.
public JInternalFrame getFrontFrame(){
InternalFrame[] array = desktop.getAllFrames();
JInternalFrame f=(JInternalFrame)array[0];
return f;
}

Paint in very high resolution

I am currently trying to paint a Waveform in very high resolution (due to zooming). The Waveform is drawn in a JScrollPane therefore. I want to be able to paint about 50.000-60.000 pixels width with it.
Unfortunately it stops painting properly at about 34000 pixel width. The issue is that it doesn't paint ca. the first screen size anymore, but the rest is drawn properly. As I only have very little experience in graphical stuff I thought you might be able to help me to decide how to fix this as best as possible.
I thought about dealing with it via repainting the first screen size (e.g. with repaint(Rectangle)) or maybe with parting the picture into 3 or more frames. If I choose the 2nd option I don't know if I should just part it an paint it all together or only paint it when its visible on the Viewport. Or maybe there is another better solution I cant figure out?
I hope you can help me with this and save me loads of hours to try out everything. Thanks in advance.
So here is the requested executable. You can see improper drawing at about 34.000 pixels width. Current pixels can be read in System.out . Drawing doesnt work with mp3, as not supported. I suggest tryouts with .wav.
Main Class:
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GraphicsEnvironment;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.UnsupportedAudioFileException;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import java.awt.GridBagLayout;
import java.awt.GridBagConstraints;
public class Main {
private JFrame mainFrame;
private JPanel upperPanel;
/**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
Main window = new Main();
window.mainFrame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Initialize Application
*/
public Main() {
initializeMainFrame();
initializePanels();
}
private void initializePanels() {
upperPanel = new MainPanel();
upperPanel.setPreferredSize(new Dimension(1000, 500));
GridBagConstraints c = new GridBagConstraints();
c.anchor = GridBagConstraints.NORTH;
c.weightx = 1.0;
c.weighty = 1.0;
c.fill = GridBagConstraints.BOTH;
c.gridy = 0;
c.gridwidth = GridBagConstraints.REMAINDER;
mainFrame.add(upperPanel, c);
}
private void initializeMainFrame() {
mainFrame = new JFrame("Waveform Example");
mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
GraphicsEnvironment ge = GraphicsEnvironment
.getLocalGraphicsEnvironment();
Rectangle gebounds = ge.getMaximumWindowBounds();
mainFrame.setSize(gebounds.getSize());
mainFrame.setLocationRelativeTo(null);
mainFrame.setVisible(true);
mainFrame.setLayout(new GridBagLayout());
JMenuBar menuBar = new JMenuBar();
JMenu fileMenu = new JMenu("File");
JMenuItem importAudio = new JMenuItem("Import Audio");
menuBar.setLayout(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
c.fill = GridBagConstraints.HORIZONTAL;
c.anchor = GridBagConstraints.WEST;
c.weightx = 0.3;
c.weighty = 0.3;
c.gridx = 0;
c.gridy = 0;
mainFrame.setJMenuBar(menuBar);
menuBar.add(fileMenu, c);
c.gridx = 1;
c.gridy = 0;
fileMenu.add(importAudio);
importAudio.addActionListener(new importAudioActionListener());
}
private class importAudioActionListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent event) {
File f = getFile();
if (f != null) {
AudioInfo audioInfo = createAudioInfo(f);
mainFrame.remove(upperPanel);
upperPanel = new MainPanel(audioInfo);
upperPanel.setPreferredSize(new Dimension(1000, 500));
GridBagConstraints c = new GridBagConstraints();
c.anchor = GridBagConstraints.NORTH;
c.gridy = 0;
c.weightx = 1.0;
c.weighty = 1.0;
c.fill = GridBagConstraints.BOTH;
c.gridwidth = GridBagConstraints.REMAINDER;
mainFrame.add(upperPanel, c);
mainFrame.pack();
}
}
private AudioInfo createAudioInfo(File f) {
AudioInputStream audioInputStream = null;
try {
audioInputStream = AudioSystem.getAudioInputStream(f);
} catch (UnsupportedAudioFileException e1) {
System.out.println("Invalid Audio Format");
} catch (IOException e1) {
System.out.println("Invalid Input File");
}
AudioInfo retInfo = new AudioInfo(audioInputStream,
(int) f.length());
return retInfo;
}
private File getFile() {
// New file chooser only shows and accepts MP3 files.
JFileChooser fc = new JFileChooser();
fc.setAcceptAllFileFilterUsed(false);
fc.showOpenDialog(null);
File f = null;
try {
f = fc.getSelectedFile();
} catch (Exception fnfe) {
f = null;
System.out.println("File not found!");
}
return f;
}
}
}
Panel that contains JPanel:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Rectangle;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
public class MainPanel extends JPanel implements MouseWheelListener,
ComponentListener {
private boolean finishedZoom = true;
private int mouseX;
private static final long serialVersionUID = 1L;
private AudioInfo audioInfo;
private int scale = 1;
private Dimension panelSize;
private int mouseXScaled;
private int mouseYScaled;
private JScrollPane scrollPane;
private int sizeNormalizer = 150;
private JPanel thisPanel = this;
private JPanel content;
public MainPanel() {
}
public MainPanel(AudioInfo audioInfo) {
this.audioInfo = audioInfo;
this.setLayout(new BorderLayout());
panelSize = new Dimension(1000, 500);
content = getContent();
scrollPane = new JScrollPane(content);
scrollPane.setPreferredSize(panelSize);
scrollPane
.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
this.add(scrollPane, BorderLayout.CENTER);
this.setPreferredSize(panelSize);
content.addMouseWheelListener(this);
content.addComponentListener(this);
}
private JPanel getContent() {
GridBagConstraints c = new GridBagConstraints();
c.fill = GridBagConstraints.BOTH;
c.weightx = 1.0;
c.weighty = 1.0;
JPanel retContent = new JPanel(false);
retContent.setPreferredSize(panelSize);
retContent.setLayout(new GridBagLayout());
WaveformPanel waveformPanel = new WaveformPanel(audioInfo);
waveformPanel.setPreferredSize(panelSize);
retContent.setBackground(Color.green);
c.gridwidth = GridBagConstraints.REMAINDER; // end row
retContent.add(waveformPanel, c);
return retContent;
}
public void mouseWheelMoved(MouseWheelEvent e) {
boolean changed = false;
double notches = e.getWheelRotation();
if (e.isControlDown() && finishedZoom) {
int newScale = (int) (scale + notches * (-1) * 2);
int newWidth = (int) ((thisPanel.getPreferredSize().getWidth()) * newScale);
if (newWidth > content.getPreferredSize().getWidth()) {
System.out.println("new width original: " + newWidth);
content.setVisible(false);
content.setPreferredSize(new Dimension(
newWidth,
(int) ((thisPanel.getPreferredSize().getHeight() - sizeNormalizer) / 3 * 2)));
content.setVisible(true);
mouseXScaled = e.getX() / scale * newScale;
mouseYScaled = e.getY() / scale * newScale;
scale = newScale;
changed = true;
} else if (newWidth < content.getPreferredSize().getWidth()
&& newWidth > thisPanel.getWidth()) {
content.setVisible(false);
content.setPreferredSize(new Dimension(
newWidth,
(int) ((thisPanel.getPreferredSize().getHeight() - sizeNormalizer) / 3 * 2)));
content.setVisible(true);
mouseXScaled = e.getX() / scale * newScale;
mouseYScaled = e.getY() / scale * newScale;
scale = newScale;
changed = true;
} else if (newWidth <= thisPanel.getWidth()) {
newWidth = (int) (thisPanel.getPreferredSize().getWidth());
newScale = 1;
content.setVisible(false);
content.setPreferredSize(new Dimension(
newWidth,
(int) ((thisPanel.getPreferredSize().getHeight() - sizeNormalizer) / 3 * 2)));
content.setVisible(true);
mouseXScaled = e.getX() / scale * newScale;
mouseYScaled = e.getY() / scale * newScale;
scale = newScale;
}
if (changed) {
finishedZoom = false;
}
mouseX = e.getX();
} else if (!e.isControlDown()) {
int scrollBarValue = scrollPane.getHorizontalScrollBar().getValue();
Rectangle viewRect = scrollPane.getViewport().getViewRect();
scrollPane
.getHorizontalScrollBar()
.setValue(
(int) ((int) scrollBarValue + ((viewRect.width - 100) * notches)));
}
}
public int getHorizontalScroll() {
return scrollPane.getHorizontalScrollBar().getValue();
}
#Override
public void componentHidden(ComponentEvent arg0) {
// TODO Auto-generated method stub
}
#Override
public void componentMoved(ComponentEvent arg0) {
// TODO Auto-generated method stub
}
#Override
public void componentResized(ComponentEvent arg0) {
if (mouseXScaled != 0 && mouseYScaled != 0) {
int scrollBarVal = scrollPane.getHorizontalScrollBar().getValue();
int newX = (int) (scrollBarVal + mouseXScaled - mouseX);
scrollPane.getHorizontalScrollBar().setValue(newX);
finishedZoom = true;
}
}
#Override
public void componentShown(ComponentEvent arg0) {
// TODO Auto-generated method stub
}
}
AudioInfo Class:
import java.io.IOException;
import javax.sound.sampled.AudioInputStream;
public class AudioInfo {
private static final int NUM_BITS_PER_BYTE = 8;
private AudioInputStream encodedInputSream;
private int[][] encodedSamplesContainer;
private byte[] encodedBuffer;
// cached values
private int sampleMax = 0;
private int sampleMin = 0;
private double biggestSample;
public AudioInfo(AudioInputStream encodedInputStream, int fileSize) {
encodedBuffer = new byte[fileSize];
this.encodedInputSream = encodedInputStream;
encodedBuffer = createSampleArrayCollection(encodedInputStream,
encodedBuffer);
encodedSamplesContainer = getSampleArray(encodedBuffer);
if (sampleMax > sampleMin) {
biggestSample = sampleMax;
} else {
biggestSample = Math.abs(((double) sampleMin));
}
}
protected int getNumberOfChannels() {
return 2;
}
/**
* Reads the audio input stream into a tmp array and then inserts the tmp
* array into a buffer array. Resets the mark of the audio input stream
* after finish buffering. Then cuts the array from fileSize*10 to the final
* size.
*/
private byte[] createSampleArrayCollection(AudioInputStream inputStream,
byte[] inBuffer) {
byte[] buffer = new byte[inBuffer.length];
int sumReadBytes = 0;
try {
// inputStream.mark(Integer.MAX_VALUE);
//inputStream.reset();
boolean end = false;
while (!end) {
int available = inputStream.available();
if (available <= 0) {
end = true;
}
if (!end) {
byte[] tmp = new byte[available];
int readBytes = inputStream.read(tmp);
tmp = cutArray(tmp, readBytes);
insertArray(buffer, tmp, sumReadBytes);
sumReadBytes += readBytes;
}
}
//inputStream.reset();
} catch (IOException e) {
e.printStackTrace();
}
buffer = cutArray(buffer, sumReadBytes);
return buffer;
}
/**
*
* #param cutThis
* array that has to be cut
* #param cutPoint
* index at which the array will be cut off
* #return the buffer array cut off at the point of cutpoint
*/
private byte[] cutArray(byte[] cutThis, int cutPoint) {
byte[] tmp = new byte[cutPoint];
for (int i = 0; i < tmp.length; i++) {
tmp[i] = cutThis[i];
}
return tmp;
}
/**
*
* #param insertIntoThis
* the array you want to insert in the other
* #param tmp
* the array that is going to be inserted
*/
private byte[] insertArray(byte[] insertIntoThis, byte[] tmp,
int nextEmptyField) {
for (int i = 0; i < tmp.length; i++) {
insertIntoThis[nextEmptyField] = tmp[i];
nextEmptyField++;
}
return insertIntoThis;
}
/**
*
* #param eightBitByteArray
* Array of an eight bit byte array.
* #return int audio information array for every channel.
*/
private int[][] getSampleArray(byte[] eightBitByteArray) {
int[][] toReturn = new int[getNumberOfChannels()][eightBitByteArray.length
/ (2 * getNumberOfChannels()) + 1];
int index = 0;
// loop through the byte[]
for (int t = 0; t + 4 < eightBitByteArray.length;) {
// for each iteration, loop through the channels
for (int a = 0; a < getNumberOfChannels(); a++) {
// do the byte to sample conversion
// see AmplitudeEditor for more info
int low = (int) eightBitByteArray[t];
t++;
int high = (int) eightBitByteArray[t];
t++;
int sample = (high << 8) + (low & 0x00ff);
if (sample < sampleMin) {
sampleMin = sample;
} else if (sample > sampleMax) {
sampleMax = sample;
}
// set the value.
toReturn[a][index] = sample;
}
index++;
}
return toReturn;
}
/**
*
* #param panelHeight
* #return calculated yScaleFactor
*/
public double getYScaleFactor(int panelHeight) {
return (panelHeight / (biggestSample * 2 * 1.5));
}
/**
*
* #param channel
* number of the channel you want the audio information of
* #return int array of the audio information of the given channel.
*/
protected int[] getAudio(int channel) {
return encodedSamplesContainer[channel];
}
/**
*
* #param xScale
* #return calculates the increment for given xScale
*/
protected int getIncrement(double xScale) {
try {
int increment = (int) (encodedSamplesContainer[0].length / (encodedSamplesContainer[0].length * xScale));
return increment;
} catch (Exception e) {
e.printStackTrace();
}
return -1;
}
}
Waveform Panel Class:
import javax.swing.*;
import java.awt.*;
public class WaveformPanel extends JPanel {
private static final long serialVersionUID = 1L;
private static final Color BACKGROUND_COLOR = Color.black;
private static final Color REFERENCE_LINE_COLOR = Color.blue;
private static final Color WAVEFORM_COLOR = Color.blue;
private AudioInfo helper;
private int[] samples;
public WaveformPanel(AudioInfo helper) {
super();
this.helper = helper;
setBackground(BACKGROUND_COLOR);
samples = helper.getAudio(0);
}
/**
* Paints the component of the melted channel audio data.
*/
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int lineHeight = getHeight() / 2;
g.setColor(REFERENCE_LINE_COLOR);
g.drawLine(0, lineHeight, (int) getWidth(), lineHeight);
drawWaveform(g, samples);
}
protected double getXScaleFactor(int panelWidth) {
double width = (double) panelWidth;
return (width / ((double) samples.length));
}
private double getIncrement(double xScale) {
try {
double increment = (samples.length / (samples.length * xScale));
return increment;
} catch (Exception e) {
e.printStackTrace();
}
return -1;
}
/**
* #param g
* graphic of this panel
* #param samples
* audio samples of a channel
*
* Draws a waveform with given input on a graphic.
*/
protected void drawWaveform(Graphics g, int[] samples) {
int buffer = 30;
if (samples == null) {
return;
}
double oldX = 0;
double xIndex = 0;
double increment = getIncrement(getXScaleFactor(getWidth() - buffer * 2));
g.setColor(WAVEFORM_COLOR);
System.out.println("width: " + this.getWidth());
double t = 0;
int drawLength = samples.length;
for (; t < drawLength; t += increment) {
double scaleFactor = helper.getYScaleFactor(getHeight());
double scaledSample = samples[(int) t] * scaleFactor;
double y = ((getHeight() / 2) - (scaledSample));
double yMirror = ((getHeight() / 2) + scaledSample);
g.drawLine((int) (oldX + buffer), (int) yMirror,
(int) (xIndex + buffer), (int) y);
xIndex++;
oldX = xIndex;
}
}
}
As an alternative, see this MCTaRE that successfully renders an image that is twice that width. Scroll it to half width (or any width for that matter) to see ..the image with no artifacts.
Note that I called setPreferredSize in that example to save a few code lines, but see Should I avoid the use of set(Preferred|Maximum|Minimum)Size methods in Java Swing? (Yes.)
import java.awt.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
class BigImageWaveform {
public static void main(String[] args) {
final BufferedImage bi = new BufferedImage(
2*34000, 500, BufferedImage.TYPE_INT_RGB);
draw(bi);
Runnable r = new Runnable() {
#Override
public void run() {
JScrollPane jsp = new JScrollPane(
new JLabel(new ImageIcon(bi)),
JScrollPane.VERTICAL_SCROLLBAR_NEVER,
JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
Dimension d = jsp.getPreferredSize();
jsp.setPreferredSize(new Dimension(1000, (int)d.getHeight()));
JOptionPane.showMessageDialog(null, jsp);
}
};
// Swing GUIs should be created and updated on the EDT
// http://docs.oracle.com/javase/tutorial/uiswing/concurrency
SwingUtilities.invokeLater(r);
}
public static void draw(BufferedImage bi) {
Graphics2D g = bi.createGraphics();
int w = bi.getWidth();
int h = bi.getHeight();
GradientPaint gp = new GradientPaint(
0f,0f,Color.RED,
101f,0f,Color.GREEN,true);
g.setPaint(gp);
g.fillRect(0,0,w,h);
gp = new GradientPaint(
0f,0f,new Color(0,0,255,128),
97f,103f,new Color(220,0,220,164), true);
g.setPaint(gp);
g.fillRect(0,0,w,h);
gp = new GradientPaint(
0f,0f,new Color(0,0,0,0),
(float)w,0f,new Color(0,0,0,128), true);
g.setPaint(gp);
g.fillRect(0,0,w,h);
g.dispose();
}
}
After testing this on two more potent Windows Systems I came to the opinion that its either a Linux problem or a performance problem, as my laptop is about 2 years old and was pretty cheap.
If anybody could test it out on a Linux System it would be great. Otherwise I am going to mark this issue as answered.

Categories

Resources