cargar de manera más eficiente las imágenes para JList, presumiblemente con SwingWorker

user9851015:

Tengo un problema en el que espero SwingWorker me puede ayudar, pero no estoy muy seguro de cómo integrarlo en mi programa.

El problema:

En una CardLayout Tengo un botón en Card1 que abre Tarjeta2. Card2 tiene un JList con un intérprete personalizado (que se extiende JLabel) que mostrará un promedio de 1 a 6 imágenes que son:

  • PNG
  • alrededor de 500 kb de tamaño
  • cargado a través de ImageIO con el cambio de tarjetas

el procesador de operaciones se aplica pesados ​​como el escalado de imagen o distorsión y que establece la imagen como icono de JLabel.

Esto casi puede tardar hasta un segundo si alrededor de 6 imágenes tienen que ser prestados, que es que no sucede con frecuencia, pero incluso esa fracción de segundo de vez en cuando se siente la falta de respuesta del mal.

Ahora pensé que un SwingWorker podría ayudar aquí, pero estoy confundido a fondo como a la forma en que tendría que integrarla.

Suponiendo que habíamos Este fragmento de código

import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class Example {

    private JPanel mainPanel = new JPanel();
    private JList<Product> list = new JList();
    private JScrollPane scroll = new JScrollPane();
    private Map<String, Color> colorMap = new HashMap<>();

    public Example() {
        colorMap.put("red", Color.red);
        colorMap.put("blue", Color.blue);
        colorMap.put("cyan", Color.cyan);
        colorMap.put("green", Color.green);
        colorMap.put("yellow", Color.yellow);

        mainPanel.setBackground(new Color(129, 133, 142));

        scroll.setViewportView(list);
        scroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        scroll.setPreferredSize(new Dimension(80,200));

        list.setCellRenderer(new CustomRenderer());
        DefaultListModel model = new DefaultListModel();

        model.addElement(new Product("red"));
        model.addElement(new Product("yellow"));
        model.addElement(new Product("blue"));
        model.addElement(new Product("red"));
        model.addElement(new Product("cyan"));
        list.setModel(model);

        mainPanel.add(scroll);
    }

    public static void main(String[] args) throws IOException {

        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame("WorkerTest");
                frame.setContentPane(new Example().mainPanel);
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLocation(300, 300);
                frame.setMinimumSize(new Dimension(160, 255));
                frame.setVisible(true);
            }
        });
    }

    class CustomRenderer extends JLabel implements ListCellRenderer<Product> {

        private  Product product;
        public CustomRenderer() {
            setOpaque(false);
        }

        @Override
        public Component getListCellRendererComponent(JList<? extends Product> list, Product product, int index, boolean isSelected, boolean cellHasFocus) {
            this.product = product;

            /**
             * in the actual code image is png with alpha channel respectively named to the productID of the JList object
             *
             * String id = product.getId();
             * image = ImageIO.read(getClass().getResource("../../resources/images/" + id + ".png"));
             */

            BufferedImage image1 = new BufferedImage(80, 50, BufferedImage.TYPE_INT_RGB);
            BufferedImage image2 = new BufferedImage( 80, 75, BufferedImage.TYPE_INT_RGB);
            Graphics g = image2.getGraphics();

            /**
             * this is only an example, in the actual code I might also apply gaussian blurs or rescale several time
             */
            g.drawImage(image1,0,0,null);

            setIcon(new ImageIcon(image2));

            return this;
        }

        public void paintComponent(Graphics g) {
            super.paintComponent(g);

            g.setColor(colorMap.get(product.getColor()));
            g.fillRect(0,0,80,75);
        }
    }

    class Product {

        String productID;
        String color;

        public Product(String color) {
            this.color = color;
        }

        public String getColor() {
            return color;
        }

        public String getProductID() {
            return productID;
        }
    }
}

iba a tener que llamar a un SwingWorker de cada llamada getListCellRendererComponent para hacerse cargo de las operaciones de imagen?

Es SwingWorker incluso la herramienta adecuada para este problema?

cualquier ayuda en cuanto a cómo puedo hacer que esta parte de mi interfaz gráfica de usuario más rápida sería muy apreciada.

EDIT: aerodeslizador completa de anguilas mencionó que la precarga de las imágenes podría ayudar y que la carga de las imágenes del procesador es fundamentalmente erróneo.

Esto me lleva a otra pregunta:

Tengo una lista (llamémoslo LIST1) con alrededor de 3000 objetos cada objeto tiene un 8 kb jpg miniatura que se carga a través de ID de objeto (también durante la prestación) Aparecerá la lista de alrededor de 6 a 12 de estos miniatura al mismo tiempo (debido a la dimensión de la Lista)

cuando el usuario selecciona un objeto que puede pulsar un botón para mostrar Tarjeta2 del CardLayout menciona en la pregunta original y de la lista (list2) con el objeto y todo lo que es objeto relacionado en forma de viñeta no (500kb png + operación de imagen pesada). Ahora creo que sería factible para precargar la imagen en miniatura no del objeto y de las relaciones seleccionado en la primera lista, que sería de alrededor de 1-6 imágenes. Si he entendido bien lo que Hovercraft completa de anguilas dijo, entonces yo podría utilizar un SwingWorker para cargar estas imágenes después de la selección de un objeto desde la lista1.

Pero ¿qué pasa con los alrededor de 3.000 imágenes de lista1, el programa aparentemente no es lento o no responde, ya que son bastante pequeñas en tamaño y no hay operaciones pesadas en las miniaturas, pero siguen siendo carga del formulario de render de la lista1. ¿Tendría sentido para precargar los varios miles de imágenes en miniatura?

por cierto. siente libre de decirme si este tipo de edición de preguntas no se deseaba y si debe hacerse en una pregunta de sí mismo.

Marco13:

Un enfoque podría ser el siguiente:

Siempre que se solicite un componente procesador de celdas para un determinado parámetro (Producto), se comprueba si la imagen correspondiente ya está cargado. Si no es así, se inicia un trabajador de oscilación que hace el trabajo de carga y el procesamiento de la imagen en el fondo. Cuando se realiza el trabajador, la imagen se coloca en una caché para la búsqueda posterior. Mientras tanto, deja que el procesador sólo decir "Loading..."o algo así.

Una muy rápida implementación está aquí:

introducir descripción de la imagen aquí

Y como un MCVE:

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;

import javax.swing.DefaultListModel;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.ListCellRenderer;
import javax.swing.SwingWorker;

public class LazyImageLoadingCellRendererTest
{

    private JPanel mainPanel = new JPanel();
    private JList<Product> list = new JList<Product>();
    private JScrollPane scroll = new JScrollPane();

    public LazyImageLoadingCellRendererTest()
    {
        mainPanel.setBackground(new Color(129, 133, 142));

        scroll.setViewportView(list);
        scroll.setHorizontalScrollBarPolicy(
            JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        scroll.setPreferredSize(new Dimension(80, 200));

        list.setCellRenderer(new LazyImageLoadingCellRenderer<Product>(list,
            LazyImageLoadingCellRendererTest::loadAndProcessImage));
        DefaultListModel<Product> model = new DefaultListModel<Product>();

        for (int i=0; i<1000; i++)
        {
            model.addElement(new Product("id" + i));
        }
        list.setModel(model);

        mainPanel.add(scroll);
    }

    public static void main(String[] args) throws IOException
    {

        EventQueue.invokeLater(new Runnable()
        {
            @Override
            public void run()
            {
                JFrame frame = new JFrame("WorkerTest");
                frame.setContentPane(
                    new LazyImageLoadingCellRendererTest().mainPanel);
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLocation(300, 300);
                frame.setMinimumSize(new Dimension(160, 255));
                frame.setVisible(true);
            }
        });
    }

    private static final Random random = new Random(0);

    private static BufferedImage loadAndProcessImage(Product product)
    {
        String id = product.getProductID();
        int w = 100;
        int h = 20;
        BufferedImage image =
            new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = image.createGraphics();
        g.setColor(Color.GREEN);
        g.fillRect(0, 0, w, h);
        g.setColor(Color.BLACK);
        g.drawString(id, 10, 16);
        g.dispose();

        long delay = 500 + random.nextInt(3000);
        try
        {
            System.out.println("Load time of " + delay + " ms for " + id);
            Thread.sleep(delay);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        return image;
    }

    class Product
    {
        String productID;

        public Product(String productID)
        {
            this.productID = productID;
        }

        public String getProductID()
        {
            return productID;
        }
    }

}

class LazyImageLoadingCellRenderer<T> extends JLabel
    implements ListCellRenderer<T>
{
    private final JList<?> owner;
    private final Function<? super T, ? extends BufferedImage> imageLookup;
    private final Set<T> pendingImages;
    private final Map<T, BufferedImage> loadedImages;

    public LazyImageLoadingCellRenderer(JList<?> owner,
        Function<? super T, ? extends BufferedImage> imageLookup)
    {
        this.owner = Objects.requireNonNull(
            owner, "The owner may not be null");
        this.imageLookup = Objects.requireNonNull(imageLookup,
            "The imageLookup may not be null");
        this.loadedImages = new ConcurrentHashMap<T, BufferedImage>();
        this.pendingImages =
            Collections.newSetFromMap(new ConcurrentHashMap<T, Boolean>());
        setOpaque(false);
    }

    class ImageLoadingWorker extends SwingWorker<BufferedImage, Void>
    {
        private final T element;

        ImageLoadingWorker(T element)
        {
            this.element = element;
            pendingImages.add(element);
        }

        @Override
        protected BufferedImage doInBackground() throws Exception
        {
            try
            {
                BufferedImage image = imageLookup.apply(element);
                loadedImages.put(element, image);
                pendingImages.remove(element);
                return image;
            }
            catch (Exception e)
            {
                e.printStackTrace();
                return null;
            }
        }

        @Override
        protected void done()
        {
            owner.repaint();
        }
    }

    @Override
    public Component getListCellRendererComponent(JList<? extends T> list,
        T value, int index, boolean isSelected, boolean cellHasFocus)
    {
        BufferedImage image = loadedImages.get(value);
        if (image == null)
        {
            if (!pendingImages.contains(value))
            {
                //System.out.println("Execute for " + value);
                ImageLoadingWorker worker = new ImageLoadingWorker(value);
                worker.execute();
            }
            setText("Loading...");
            setIcon(null);
        }
        else
        {
            setText(null);
            setIcon(new ImageIcon(image));
        }
        return this;
    }
}

Nota:

Esto es realmente sólo un ejemplo rápido que muestra el enfoque general. Por supuesto, esto se podría mejorar en muchos aspectos. Aunque el proceso de carga real que ya se retiró en un Function(por lo que es genéricamente aplicable para cualquier tipo de imagen, independientemente de dónde viene), una advertencia importante es que: Se tratará de cargar todas las imágenes. Una extensión agradable sería añadir un poco de elegancia aquí, y asegurarse de que sólo las cargas de las imágenes para las que las células son actualmente visibles. Por ejemplo, cuando se tiene una lista de 1000 elementos, y quiere ver los últimos 10 elementos, entonces no debería tener que esperar por 990 elementos para ser cargados. Los últimos elementos deben ser priorizados mayory cargado primero. Sin embargo, para esto, una infraestructura poco más grande (sobre todo: una cola de tareas propias y alguna conexión más fuerte con la lista y su panel de desplazamiento) pueden ser necesarios. (Voy a abordo posiblemente este día, ya que podría ser un bonito y lo interesante que tienen, pero hasta entonces, el ejemplo anterior podría hacerlo ...)

Supongo que te gusta

Origin http://43.154.161.224:23101/article/api/json?id=200016&siteId=1
Recomendado
Clasificación