Comment actualiser les graphiques d'une classe Window en Java


J'essaie de dessiner sur un panneau vlcj (liaison java de la bibliothèque VLC) afin que je puisse lire une vidéo et dessiner dessus. Et je rencontre quelques problèmes. Voici le code de base complet:

Code-liste 1: AppOverlay.java

package app;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.HeadlessException;
import java.awt.Window;
import java.awt.image.BufferedImage;
import java.io.IOException;

import javax.imageio.ImageIO;

import com.sun.jna.platform.WindowUtils;

@SuppressWarnings("serial")
public class AppOverlay extends Window implements Runnable {

    private final boolean isRunning;
    private final int fps;
    private BufferedImage graphics;
    private BufferedImage img;
    private int x, y;
    private boolean ltr;

    public AppOverlay(Window owner) {
        super(owner, WindowUtils.getAlphaCompatibleGraphicsConfiguration());
        setBackground(new Color(0,0,0,0));

        graphics    = new BufferedImage(1280,800, BufferedImage.TYPE_INT_ARGB);
        isRunning   = true;
        img         = null;
        ltr         = true;
        fps         = 60;
        x           = 0;
        y           = 0;
    }

    @Override
    public void run(){
        while(isRunning){

            try{
                Thread.sleep(1000/fps);
            } catch(InterruptedException e){
                e.printStackTrace();
            }

            if(ltr) {
                if(x < 1280) x++;
                else ltr = false;
            } else {
                if(x < 0) ltr = true;
                else x--;
            }

            repaint();
        }
    }

    public void createAndShowGUI() {
        setVisible(true);

        Thread thread = new Thread(this);
        thread.start();

        String path = "Drive:\\path\\to\\image.png";
        try {
            img = ImageIO.read(new java.io.FileInputStream(path));
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    @Override
    public void paint(Graphics g) {
        Graphics2D g2d = (Graphics2D)g;
        Graphics2D gfx = graphics.createGraphics();
        gfx.setColor(new Color(255,255,255,0));
        gfx.clearRect(0, 0, 1280, 800);
        if(img != null) gfx.drawImage(img, x, y, null);
        gfx.dispose();
        g2d.drawImage(graphics, 0, 0, null);
    }
}

Code-liste 2: AppPlayer.java

package app;

import uk.co.caprica.vlcj.player.component.EmbeddedMediaPlayerComponent;

@SuppressWarnings("serial")
public class AppPlayer extends EmbeddedMediaPlayerComponent {

}

Code-liste 3: AppFrame.java

package app;

import java.awt.Dimension;

import javax.swing.JFrame;

@SuppressWarnings("serial")
public class AppFrame extends JFrame {

    private AppPlayer appPlayer;
    private AppOverlay overlay;

    public AppFrame(){
        super();
    }

    public void createAndShowGUI() {

        appPlayer = new AppPlayer();
        appPlayer.setPreferredSize(new Dimension(1280,800));
        getContentPane().add(appPlayer);

        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setTitle("App");
        setVisible(true);
        pack();

        overlay = new AppOverlay(this);
        appPlayer.mediaPlayer().overlay().set(overlay);
        appPlayer.mediaPlayer().overlay().enable(true);
        overlay.createAndShowGUI();
    }
}

Code-liste 4: Principal.java

package main;

import javax.swing.SwingUtilities;

import app.AppFrame;

public class Main {

    public static void main(String[] args) {
        final AppFrame app = new AppFrame();
        SwingUtilities.invokeLater( new Runnable() {
            @Override
            public void run() {
                app.createAndShowGUI();
            }
        });
    }

}

Avec cela et la bibliothèque vlcj-4, vous devriez être capable de tester mon code vous-même. Mon problème est que la superposition (classe AppOverlay qui étend la classe Window) n'affiche ni n'actualise l'animation sauf si je désélectionne la fenêtre (je clique sur une autre fenêtre ou sur le bureau ou la barre d'outils du système d'exploitation) afin que la fenêtre (application) soit inactive puis sélectionne à nouveau la fenêtre (l'application). Il ne chargera qu'une seule image et c'est tout. Je dois désélectionner et resélectionner à nouveau la fenêtre pour qu'elle charge un autre cadre (ce n'est que le cas pour la superposition c'est-à-dire que si je joue une vidéo dans la classe AppPlayer, la vidéo sera très bien lue.

Ce que je veux, c'est pouvoir dessiner des graphiques animés sur la superposition. Je sais qu'avec la classe JPanel, il existe la méthode paintComponent () mais la classe Window n'a pas cette méthode (seules les méthodes paint() et repaint () sont disponibles).

Que dois-je faire pour résoudre ce problème?

MODIFIER:

J'ai essayé d'ajouter un JPanel sur lequel je dessine au lieu de dessiner directement sur le AppOverlay

Code-liste 5: AppPanel.java

package app;

import java.awt.Color;
import java.awt.Graphics;

import javax.swing.JPanel;

public class AppPanel extends JPanel implements Runnable {
    private int x, y;
    private boolean ltr;

    public AppPanel() {
        x   = 0;
        y   = 0;
        ltr = true;
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.setColor(new Color(0,0,0,0));
        g.clearRect(0, 0, 1280, 800);
        g.setColor(Color.RED);
        g.fillRect(x, y, 100, 100);
    }

    @Override
    public void run() {
        while(true){

            try{
                Thread.sleep(1000/60);
            } catch(InterruptedException e){
                e.printStackTrace();
            }
            if(ltr) {
                if(x < 1280) x++;
                else ltr = false;
            } else {
                if(x < 0) ltr = true;
                else x--;
            }

            repaint();
        }
    }
}

Puis l'ajouter à l'AppOverlay.

Code-liste 6: AppOverlay.java, avec modification partielle

public class AppOverlay extends Window implements Runnable {
    //previous field declaration above ...
    AppPanel panel;
    AppPlayer player = null;

    public AppOverlay(Window owner) {
        //previous constructor instructions above...

        panel = new AppPanel();
        add(panel);
    }

    public void createAndShowGUI(AppPlayer player) {
        setVisible(true);

        /*
        Thread thread = new Thread(this);
        thread.start();

        String path = "Drive:\\path\\to\\image.png";
        try {
            img = ImageIO.read(new java.io.FileInputStream(path));
        } catch (IOException e) {
            e.printStackTrace();
        }
        */

        Thread panelThread = new Thread(panel);
        panelThread.start();
    }
}

Cela affichera les graphiques du JPanel et les animera au besoin.

Si vous connaissez un moyen de rendre l'arrière-plan JPanel transparent (afin que nous puissions voir à travers lui) tout en le laissant afficher ses graphiques. Cela résoudrait le problème à coup sûr.

Author: Paiku Han, 2019-10-11

3 answers

J'ai joué un peu avec votre exemple et j'ai trouvé quelque chose qui fonctionnait, mais je n'appellerais pas cela une bonne solution.

Le problème principal semble être qu'il n'y a aucun moyen de dire à la superposition de se rafraîchir (ou je ne l'ai tout simplement pas trouvé). Juste repainting la superposition ne la met pas à jour à l'écran, donc la solution de contournement que j'ai utilisée est de la masquer et de la montrer à nouveau.

Pour le timeing de l'intervalle de mise à jour, j'ai utilisé un javax.swing.Timer. (Dans une version réelle, vous voulez probablement démarrer et arrêter la minuterie via le MediaPlayerEventListener).

En tant qu'effet secondaire, la méthode repaint est appelée et la coordonnée x est ajustée pour déplacer l'image autour de l'écran.

Dans l'exemple simplifié ci-dessous (utilisez votre main pour l'exécuter), j'ai déplacé un rectangle rouge avec la coordonnée x au lieu d'une image inconnue.

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.HeadlessException;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JFrame;
import javax.swing.Timer;

import com.sun.jna.platform.WindowUtils;

import uk.co.caprica.vlcj.player.component.EmbeddedMediaPlayerComponent;
import uk.co.caprica.vlcj.player.embedded.OverlayApi;

public class AppFrame extends JFrame {

    private static final long serialVersionUID = -1569823648323129877L;

    public class Overlay extends Window {

        private static final long serialVersionUID = 8337750467830040964L;

        private int x, y;
        private boolean ltr = true;

        public Overlay(Window owner) throws HeadlessException {
            super(owner, WindowUtils.getAlphaCompatibleGraphicsConfiguration());
            setBackground(new Color(0,0,0,0));
        }

        @Override
        public void paint(Graphics g) {

            super.paint(g);

            if (ltr) {
                if (x < 1180)
                    x += 1;
                else
                    ltr = false;
            } else {
                if (x < 0)
                    ltr = true;
                else
                    x -= 1;
            }

            g.setColor(Color.RED);
            g.fillRect(x, y, 100, 100);

            String s = Integer.toString(x);
            g.setColor(Color.WHITE);
            g.drawChars(s.toCharArray(), 0, s.length(), x+10, y+50);
        }
    }

    private EmbeddedMediaPlayerComponent appPlayer;

    public void createAndShowGUI() {

        appPlayer = new EmbeddedMediaPlayerComponent();
        appPlayer.setPreferredSize(new Dimension(1280, 800));
        getContentPane().add(appPlayer);

        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setTitle("App");
        setVisible(true);
        pack();

        Overlay overlay = new Overlay(this);

        OverlayApi api = appPlayer.mediaPlayer().overlay();
        api.set(overlay);
        api.enable(true);

        //appPlayer.mediaPlayer().media().play(" ... ");

        Timer timer = new Timer(0, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                api.enable(false);
                api.enable(true);
            }
        });
        timer.setRepeats(true);
        timer.setDelay(200);
        timer.start();
    }
}

Si c'est une option pour vous, il pourrait être beaucoup plus facile à utiliser un animated gif à la place. Au moins, cela fonctionne tout seul (pas besoin de Minuterie).


Mise à Jour:

Comme vous l'avez compris à l'aide d'un JPanel semble mieux fonctionner. Suffit d'utiliser setOpaque(false) pour le rendre transparent.

Voici un exemple ajusté.

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.HeadlessException;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;

import uk.co.caprica.vlcj.player.component.EmbeddedMediaPlayerComponent;
import uk.co.caprica.vlcj.player.embedded.OverlayApi;

public class AppFrame2 extends JFrame {

    private static final long serialVersionUID = -1569823648323129877L;

    public class OverlayPanel extends JPanel {

        private static final long serialVersionUID = 8070414617530302145L;

        private int x, y;
        private boolean ltr = true;

        public OverlayPanel() {
            this.setOpaque(false);
        }

        @Override
        public void paint(Graphics g) {

            super.paint(g);

            if (ltr) {
                if (x < 1180)
                    x += 1;
                else
                    ltr = false;
            } else {
                if (x < 0)
                    ltr = true;
                else
                    x -= 1;
            }

            g.setColor(Color.RED);
            g.fillRect(x, y, 100, 100);

            String s = Integer.toString(x);
            g.setColor(Color.WHITE);
            g.drawChars(s.toCharArray(), 0, s.length(), x+10, y+50);
        }
    }

    public class Overlay extends Window {

        private static final long serialVersionUID = 8337750467830040964L;

        OverlayPanel panel;

        public Overlay(Window owner) throws HeadlessException {
            super(owner);
            setBackground(new Color(0,0,0,0));

            panel = new OverlayPanel();
            this.add(panel);
        }
    }

    private EmbeddedMediaPlayerComponent appPlayer;

    public void createAndShowGUI() {

        appPlayer = new EmbeddedMediaPlayerComponent();
        appPlayer.setPreferredSize(new Dimension(1280, 800));
        getContentPane().add(appPlayer);

        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setTitle("App");
        setVisible(true);
        pack();

        Overlay overlay = new Overlay(this);

        OverlayApi api = appPlayer.mediaPlayer().overlay();
        api.set(overlay);
        api.enable(true);

        //appPlayer.mediaPlayer().media().play(" ... ");

        Timer timer = new Timer(0, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                overlay.panel.repaint();
            }
        });
        timer.setRepeats(true);
        timer.setDelay(17);
        timer.start();
    }
}
 1
Author: second, 2019-10-18 00:49:05

Vous avez déjà fait la majeure partie du travail. Il suffit de repeindre le cadre chaque fois que vous dessinez dessus en appelant app.repaint();

 0
Author: Khalil Kabara, 2019-10-13 23:54:41

Vous pouvez utiliser les méthodes suivantes de JComponent: ( http://download.oracle.com/javase/6/docs/api/javax/swing/JComponent.html )

void    repaint(long tm, int x, int y, int width, int height)
 //**Adds the specified region to the dirty region list if the component is showing.*//
void    repaint(Rectangle r)
 /**Adds the specified region to the dirty region list if the component is showing.*//
You can call those before redraw()
 0
Author: TanvirChowdhury, 2019-10-14 03:34:17