4

I'm trying to display a high-width image in Java Swing (say 2000x100, like a heart rate strip). I need to show only a window of 500 width while it is slightly moving towards left. My current code (a bit complicated, it also has unnecessary animations) does it, but the feature I need to add is: The end of image should be concatenated with the beginning of the image. So it always repeats showing over and over.

In short, I need to join the two ends of image! How can I do that?

enter image description here

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.LinearGradientPaint;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class SlidingAnimation {

    public static void main(String[] args) {
        new SlidingAnimation();
    }

    public SlidingAnimation() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                JPanel j = new JPanel();
                j.add(new AnimatedBar(true));
                frame.add(j);
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }
}

class AnimatedBar extends JPanel {

    private BufferedImage img;

    private Timer timer;
    private long startTime = -1;
    private int playTime = 4000;
    private int window = 500;
    private int moveX=0;
    public static boolean keepRunning=true;

    private float progress;

    public AnimatedBar(boolean x) {
        try {
            if(x)
                img = ImageIO.read(new File("strip2.jpg"));
            else
                img=null;
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        if(x){
            timer = new Timer(40, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    if (startTime == -1) {
                        startTime = System.currentTimeMillis();
                    } else {
                        long currentTime = System.currentTimeMillis();
                        long diff = currentTime - startTime;

                        if (diff >= playTime) {
                            diff = 0;
                            startTime = -1;
                        }
                        progress = diff / (float) playTime;
                    }

                    repaint();
                }
            });
            timer.start();
        }
    }

    @Override
    public Dimension getPreferredSize() {
        return img == null ? new Dimension(50, 50) : new Dimension(img.getWidth()/3, img.getHeight());
    }

    protected BufferedImage generateImage() {

        BufferedImage buffer = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2d = buffer.createGraphics();
        g2d.setBackground(new Color(0, 0, 0, 0));
        g2d.clearRect(0, 0, buffer.getWidth(), buffer.getHeight());
        //            g2d.drawImage(img, 500, 0, this);
        g2d.drawImage(img,0,0,500,100,(moveX++),0,window+moveX,100,this);


        float startAt = progress- 0.05f;
        float endAt = progress + 0.05f;

        if (endAt <= 0.1f) {
            startAt = 0;
            endAt = Math.max(0.1f, progress);
        } else if (endAt >= 1f) {
            endAt = 1f;
            startAt = progress;
        }

        LinearGradientPaint lgp = new LinearGradientPaint(
                new Point2D.Float(0, 0),
                new Point2D.Float(img.getWidth(), 0),
                new float[]{startAt, endAt},
                new Color[]{new Color(0, 0, 0, 0), Color.RED});

        g2d.setPaint(lgp);

        g2d.setComposite(AlphaComposite.DstOut.derive(1f));
        g2d.fill(new Rectangle(0, 0, img.getWidth(), img.getHeight()));
        g2d.dispose();

        return buffer;

    }

    public void setImg(BufferedImage img) {
        this.img = img;
    }

    @Override
    protected void paintComponent(Graphics g) {
        if(keepRunning==false){
            img=null;
        }
        else{
            try {
                img = ImageIO.read(new File("strip2.jpg"));
            } catch (IOException e) {
            }
        }
            super.paintComponent(g);
            if (img != null) {
                Graphics2D g2d = (Graphics2D) g.create();
                int y = (getHeight() - img.getHeight()) / 2;
                int x = (getWidth() - img.getWidth()/3) / 2;
                g2d.drawImage(generateImage(), x, y, this);

                g2d.dispose();
        }
    }

}
Tina J
  • 4,983
  • 13
  • 59
  • 125

2 Answers2

7

To join the ends, paint the image twice as seen in this answer. The first paint would be the end of the image. The second paint would be the start of the image, offset by the width the end goes to.

E.G.

import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import javax.swing.*;

import java.net.URL;
import javax.imageio.ImageIO;

public class HeartBeat {

    public static void main(String[] args) throws Exception {
        URL url = new URL("https://i.stack.imgur.com/i8UJD.jpg");
        final BufferedImage bi = ImageIO.read(url);
        Runnable r = new Runnable() {

            @Override
            public void run() {
                final BufferedImage canvas = new BufferedImage(
                        bi.getWidth(), bi.getHeight(),
                        BufferedImage.TYPE_INT_RGB);
                final JLabel animationLabel = new JLabel(new ImageIcon(canvas));
                ActionListener animator = new ActionListener() {

                    int x = 0;

                    @Override
                    public void actionPerformed(ActionEvent e) {
                        Graphics2D g = canvas.createGraphics();

                        // paint last part of image in left of canvas
                        g.drawImage(bi, x, 0, null);
                        // paint first part of image immediately to the right
                        g.drawImage(bi, x + bi.getWidth(), 0, null);

                        // reset x to prevent hitting integer overflow
                        if (x%bi.getWidth()==0) x = 0;

                        g.dispose();
                        animationLabel.repaint();
                        x--;
                    }
                };
                Timer timer = new Timer(40, animator);
                timer.start();
                JOptionPane.showMessageDialog(null, animationLabel);
                timer.stop();
            }
        };
        // Swing GUIs should be created and updated on the EDT
        // http://docs.oracle.com/javase/tutorial/uiswing/concurrency
        SwingUtilities.invokeLater(r);
    }
}
Community
  • 1
  • 1
Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
  • 1
    Could you maybe add a comment or two where you get the offset explaining what exactly is going on there? I think that would be useful for people trying to work through how the image wraps. – MirroredFate Oct 23 '14 at 00:39
  • 1
    As just a quick note, depending on what this display is being used for, you may want to reset x when it mods to 0... otherwise I think you'll run into a negative integer overflow in like three years... probably doesn't matter, but maybe. – MirroredFate Oct 23 '14 at 00:46
  • This is a nice one! But can you add an option with a value (maybe a timer?) to specify moving step by step in a discrete way as opposed to continuously moving? like, when 'step=0' it could be like now, and when 'step=10', it moves every 10 pixels. Maybe you can add Also some comments to describe the logic can be awesome :) – Tina J Oct 23 '14 at 01:13
  • *"Also some comments to describe the logic"* Yes, I added them. There really isn't that much logic to explain. *"But can you add an option.."* I'll leave that as an exercise for the end user. In any case, it seems that *"I need to join the two ends of image! How can I do that?"* has already been answered (using two different approaches). – Andrew Thompson Oct 23 '14 at 01:37
  • @AndrewThompson: I'm facing with a stupid question! How can I add the moving image to an external jpanel? like jp1.add(HeartBeat) – Tina J Oct 28 '14 at 06:12
  • *"How can I add the moving image to an external jpanel?"* That is worthy of another (new) question, but make sure you define 'external' clearly. I'm not sure what you mean by it. – Andrew Thompson Oct 28 '14 at 06:22
5

The end of image should be concatenated with the beginning of the image. So it always repeats showing

Check out the Marquee Panel. You can add a JLabel with an ImageIcon to the MarqueePanel.

The MarqueePanel provides various method to customize the scrolling.

Edit:

The basic code would be:

MarqueePanel panel = new MarqueePanel();
panel.setWrap(true);
panel.setWrapAmount(0);
panel.setPreferredWidth(250);

JLabel label = new JLabel( new ImageIcon( "heartbeat.jpg" ) );
panel.add( label );

frame.add( panel );

If you want the image to fully appear at the left when it is displayed you can change the startScrolling() method and use scrollOffset = 0;.

camickr
  • 321,443
  • 19
  • 166
  • 288
  • Thanks. But as can be seen, when the text ends and goes away, it starts again. Like a circle, I need the text start ASA it ends, so the end is glued to the start! – Tina J Oct 23 '14 at 00:20
  • Ummm, the 'wrap' option might help. I hope there could be no space in between of two ends supposed to join together. – Tina J Oct 23 '14 at 00:25
  • @TinaJasmin, you will also have to set the `wrap amount` to 0 when using `wrap` if you don't want a gap. – camickr Oct 23 '14 at 00:36
  • Can you kindly point me to a sample code using the above heart strip image? This seems an interesting and easy approach. Just want to make sure the gap will really remain 0. – Tina J Oct 23 '14 at 00:41
  • That's good. But the only thing I need to do is the start phase! Is it possible to first fill the display window, then move left? Basically I do not want to see any spaces/gaps! – Tina J Oct 23 '14 at 01:09
  • 1
    `Is it possible to first fill the display window,` - already answered in the edit I made. – camickr Oct 23 '14 at 03:03
  • I changed my mind and chose your answer as the best answer! So far so good. But there is a minor problem! When the window is selected and focused, the image moves, but when I click on another window, it stops! How can I make it always move? – Tina J Oct 28 '14 at 22:42
  • 1
    OK, got my answer! setScrollWhenFocused :) – Tina J Oct 28 '14 at 22:54