Atraso de imagem de apresentação de slides em Java usando paintComponent

Estou montando um programa de apresentação de slides que medirá o tempo gasto pelo usuário em cada slide. A apresentação de slides passa por vários truques de mágica diferentes. Cada truque é mostrado duas vezes. Imagens provisórias são mostradas entre a repetição. Imagens de transição são mostradas entre cada truque.

Na primeira repetição de um truque, a cor do JPanel pisca na tela após um clique antes que a próxima imagem seja mostrada. Isso não acontece durante a segunda repetição do mesmo truque. É possível que a imagem esteja demorando muito para carregar.

Existe uma maneira fácil de pré-carregar as imagens para que não haja um atraso na primeira vez?

NOTE: Original code deleted.

EDIT 1/10/2013: Este código agora funciona em máquinas mais lentas. O segundo adendo do trashgod ajudou ao máximo. A estrutura de controle mouseClick solicita periodicamente que as classes SwingWorker carreguem 40 imagens ou menos do truque atual, ao mesmo tempo em que definem as imagens usadas como nulas. Eu simplifiquei o meu código para isso apenas para dois Image [] s e adicionei um método main para que ele ficasse sozinho. As imagens ainda precisam ser executadas. Este é agora um código bastante básico, e se você está tentando fazer um slideshow com muitas imagens, acho que seria um bom lugar para começar.

NOTA: Acho que descobri como implementar corretamente o SwingWorker enquanto ainda estiver usando vários Image [] s. trashgod e kleopatra esta implementação está em linha com o que você estava sugerindo? Eu não acabei usando o publish e process porque eu não conseguia descobrir como fazer isso funcionar apropriadamente com um array indexado, mas porque o StringWorker não carrega todas as imagens no array (apenas 40), e o código Chama StringWorker a cada 20 imagens, deve haver um bom buffer.

EDIT 1/10/2013 Alterou MouseListener estendendo em vez disso MouseAdapter na minha classe Mouse. Também fixei meu método paintComponent para incluir uma chamada para super.paintComponent (g). Adicionado métodos de publicação / processo para minha classe ImageWorker do SwingWorker. Adicionada uma classe wrapper, ArrayWrapper para permitir a passagem de imageArray [i] e seu índice correspondente int i com publish to process.

package slideshow3;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseAdapter;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import java.util.List;

public class SlideShow3 extends JFrame
{
    //screenImage will be replaced with each new slide
    private Image screenImage;
    private int width;
    private int height;

    //Create panel for displaying images using paintComponent()
    private SlideShow3.PaintPanel mainImagePanel;

    //Used for keybinding
    private Action escapeAction;

    //Image array variables for each trick
    private Image[] handCuffs; //h
    private Image[] cups; //c

    //Used to step through the trick arrays one image at a time
    private int h = 0;
    private int c = 0;

    //Used by timeStamp() for documenting time per slide
    private long time0 = 0;
    private long time1;

    public SlideShow3()
    {
        super();

        //Create instance of each Image array
        handCuffs = new Image[50];
        cups = new Image[176];

        //start(handCuffsString);
        start("handCuffs");

        try
        {
            screenImage = ImageIO.read(new File("images/begin1.jpg"));
        }
        catch (IOException nm) 
        {
            System.out.println("begin");
            System.out.println(nm.getMessage());
            System.exit(0);
        }

        /****************************************** 
         * Removes window framing. The next line sets fullscreen mode.
         * Once fullscreen is set width and height are determined for the window
         ******************************************/

        this.setUndecorated(true);
        GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().setFullScreenWindow(this);
        width = this.getWidth();
        height = this.getHeight();

        //Mouse click binding to slide advance control structure
        addMouseListener(new Mouse());

        //Create panel so that I can use key binding which requires JComponent
        mainImagePanel = new PaintPanel();      
        add(mainImagePanel);

        /****************************************** 
         * Key Binding
         * ESC will exit the slideshow
         ******************************************/

        // Key bound AbstractAction items 
        escapeAction = new EscapeAction();

        // Gets the mainImagePanel InputMap and pairs the key to the action
        mainImagePanel.getInputMap().put(KeyStroke.getKeyStroke("ESCAPE"), "doEscapeAction");

        // This line pairs the AbstractAction enterAction to the action "doEnterAction"
        mainImagePanel.getActionMap().put("doEscapeAction", escapeAction);

        /******************************************
         * End Key Binding
         ******************************************/
    }

    public static void main(String[] args) 
    {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() 
            {
                SlideShow3 show = new SlideShow3();
                show.setVisible(true);
            }
        });
    }

    //This method executes a specific SwingWorker class to preload images
    public void start(String e) 
    {
        if(e.equals("handCuffs"))
        {
            new ImageWorker(handCuffs.length, h, e).execute();
        }
        else if(e.equals("cups"))
        {
            new ImageWorker(cups.length, c, e).execute();
        }
    }

    //Stretches and displays images in fullscreen window
    private class PaintPanel extends JPanel
    {
        @Override
        public void paintComponent(Graphics g) 
        { 
            if(screenImage != null)
            {
                super.paintComponent(g);
                g.drawImage(screenImage, 0, 0, width, height, this);
            }  
        }
    }

    /******************************************
     * The following SwingWorker class Pre-loads all necessary images.
     ******************************************/

    private class ArrayWrapper
    {
        private int i;
        private Image image;

        public ArrayWrapper(Image image, int i)
        {
            this.i = i;
            this.image = image;
        }

        public int getIndex()
        {
            return i;
        }

        public Image getImage()
        {
            return image;
        }
    }

    private class ImageWorker extends SwingWorker<Image[], ArrayWrapper>
    {
        private int currentPosition;
        private int arraySize;
        private String trickName;
        private Image[] imageArray;

        public ImageWorker(int arraySize, int currentPosition, String trick)
        {
            super();
            this.currentPosition = currentPosition;
            this.arraySize = arraySize;
            this.trickName = trick;
        }

        @Override
        public Image[] doInBackground()
        {
            imageArray = new Image[arraySize];
            for(int i = currentPosition; i < currentPosition+40 && i < arraySize; i++)
            {
                try 
                {
                    imageArray[i] = ImageIO.read(new File("images/" + trickName + (i+1) + ".jpg"));
                    ArrayWrapper wrapArray = new ArrayWrapper(imageArray[i], i);
                    publish(wrapArray);
                } 
                catch (IOException e) 
                {
                    System.out.println(trickName);
                    System.out.println(e.getMessage());
                    System.exit(0);
                }
            }
            return imageArray;
        }

        @Override
        public void process(List<ArrayWrapper> chunks)
        {
            for(ArrayWrapper element: chunks)
            {
                if(trickName.equals("handCuffs"))
                {
                    handCuffs[element.getIndex()] = element.getImage();
                }
                else if(trickName.equals("cups"))
                {
                    cups[element.getIndex()] = element.getImage();
                }
            }
        }

        @Override
        public void done()
        {
            try
            {
                if(trickName.equals("handCuffs"))
                {
                    handCuffs = get();
                }
                else if(trickName.equals("cups"))
                {
                    cups = get();
                }
            }
            catch(InterruptedException ignore){}
            catch(java.util.concurrent.ExecutionException e)
            {
                String why = null;
                Throwable cause = e.getCause();
                if(cause != null)
                {
                    why = cause.getMessage();
                }
                else
                {
                    why = e.getMessage();
                }
                System.err.println("Error retrieving file: " + why);
            }
        }
    }

     /******************************************
     * End SwingWorker Pre-Loading Classes
     ******************************************/

    //Prints out time spent on each slide
    public void timeStamp()
    {
        time1 = System.currentTimeMillis();
        if(time0 != 0)
        {
            System.out.println(time1 - time0);
        }
        time0 = System.currentTimeMillis();
    } 

    /******************************************
     * User Input Classes for Key Binding Actions and Mouse Click Actions
     ******************************************/

    private class EscapeAction extends AbstractAction
    {
        @Override
        public void actionPerformed(ActionEvent e)
        {
            System.exit(0);
        }
    }

    public class Mouse extends MouseAdapter
    {
        @Override
        public void mouseClicked(MouseEvent e) 
        {
            if(!(h<handCuffs.length) && !(c<cups.length))
            {
                timeStamp();
                System.exit(0);
            }
            else if(h<handCuffs.length)
            {
                timeStamp();
                screenImage = handCuffs[h];
                repaint();
                System.out.print("handCuffs[" + (h+1) + "]\t");
                h++;
                //purge used slides and refresh slide buffer
                if(h == 20 || h == 40)
                {
                    for(int i = 0; i < h; i++)
                    {
                        handCuffs[i] = null;
                    }
                    start("handCuffs");
                }
                if(h == 45)
                {
                    start("cups");
                }
            }
            else if(c<cups.length)
            {
                timeStamp();
                screenImage = cups[c];
                repaint();
                System.out.print("cups[" + (c+1) + "]\t");
                c++;
                //purge used slides and refresh slide buffer
                if(c == 20 || c == 40 || c == 60 || c == 80 || c == 100 || c == 120 || c == 140 || c == 160)
                {
                    for(int i = 0; i < c; i++)
                    {
                        cups[i] = null;
                    }
                    start("cups");
                }
            }
        }
    }

    /******************************************
     * End User Input Classes for Key Binding Actions and Mouse Click Actions
     ******************************************/ 
}

questionAnswers(1)

yourAnswerToTheQuestion