Stopniowe wyświetlanie dużej ilośći obrazów w jednym oknie

0

Cześć wszystkim forumowiczom!
Na wstępie chciałem zaznaczyć, że jestem tu nowy więc proszę o wyrozumiałość ;)

Zamierzam stworzyć galerię, do której dodaje się folder z obrazami. Za pomocą niej będzie się wybierać obraz, który następnie będzie dalej przetwarzany przez program. Niestety mam problem z samym ich wyświetleniem.

Np.: mam folder zawierający 1500 obrazów. Mój program powinien działać tak:

  • wczytuje wszystkie zdjęcia z folderu i dodaje do tablicy dynamicznej,
  • powiększa JScrollPane'a tak, aby zmieściły wszystkie obrazy,
  • tworzy 20 obiektów klasy Item, które wyświetlają obraz i dodaje je do JScrollPane'a
  • jeśli przesuniemy suwak w dół i puścimy go to zmienia obrazy obiektów klasy Item na te, które powinny znajdować się w tym miejscu i przesuwa je w dół tam gdzie znajduje się suwak

Dzięki temu można wybrać z tych 1500 obrazów te, które nas interesują usuwając i wczytując je po kolei.

Niestety w moim programie dochodzi do wycieku pamięci gdy ma się wczytać trzecia "dwudziestka" obrazów. Wywala błąd:
Exception in thread "Image Fetcher 1" Exception in thread "Image Fetcher 2" java.lang.OutOfMemoryError: Java heap space

Oto kod głównej klasy...

 
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import javax.swing.*;


class MainWindow extends JFrame {

	Item ci[];
	ArrayList<Image> im = new ArrayList<Image>();
	
	JScrollPane jsp;
	JScrollBar jsb;
	JPanel panel = new JPanel();
	int maxy = 20, cy = -1;				
	

    MainWindow() {
        setSize(300, 630);
	setLocation(30, 200);
	setLayout(null);
	setDefaultCloseOperation(EXIT_ON_CLOSE);
	
	panel.setLayout(null);	
	panel.setLocation(0, 0);
			
	jsp = new JScrollPane(panel);
	jsp.setSize(275, 550);
	jsp.setLocation(0, 0);
	add(jsp);
			
	jsb = jsp.getVerticalScrollBar();
	jsb.addAdjustmentListener(new AdjustmentListener() {
	       public void adjustmentValueChanged(AdjustmentEvent ae) {
			if(!jsb.getValueIsAdjusting()) {
				cy = (int) (jsp.getViewport().getViewPosition().getY()-50)/140;
				if(cy > maxy  && cy != 0) showItems(cy); 
			}
					
		}
	});
	
	JButton butt = new JButton("Przeglądaj..");
	butt.setSize(100, 25);
	butt.setLocation(100, 560);
	butt.addActionListener(new ActionListener() {
		public void actionPerformed(ActionEvent ae) {
			JFileChooser fc = new JFileChooser();
			fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
			int result = fc.showOpenDialog(null);
					
			if(result == JFileChooser.APPROVE_OPTION) {
				File f = fc.getSelectedFile();
				String path = f.getAbsolutePath();
					
				try(DirectoryStream<Path> dir = Files.newDirectoryStream(Paths.get(path), 
                                "*.{jpg, png, gif}")) {
					int y = 50;
					for(Path p : dir) {
						Image img = 
                                                Toolkit.getDefaultToolkit().getImage(p.toString());
							
                                                 im.add(img);
						 y += 140;
					}
					panel.setPreferredSize(new Dimension(250, y+15));
					jsp.revalidate();	
							
					ci = new Item[20];
					System.out.println("  ci[0] = null - pierwsze dodanie");
							
					y = 50;
					for(int i = 0; i < 20; i++) {
						System.out.println("Dodanawanie pierwsze " + i + " z " 
                                                + maxy);
							
                                                ci[i] = new Item(im.get(i));
						ci[i].setLocation(0, y);
						panel.add(ci[i]);
						y += 140;
					}
				}
				catch(IOException io) { System.out.println("io " + io); }
			}
		}
	});
	add(butt);
	setVisible(true);
    }
    
    private void showItems(int item) {
		maxy = item+20;
		
		System.out.println("Nowe maxy -  " + maxy);
		for(int i = item,  y = (item*140)+50, j = 0; j < 20; i++, j++) {
			System.out.println("Dodano " + i + " z " + maxy);
			ci[j].setImage(im.get(i));
			ci[j].setLocation(0, y);
			y += 140;
		}
	} 
    
    public static void main(String args[]) {
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				new MainWindow();
			}
		});
	}
}

...i kod klasy Item:

import java.awt.*;
import java.awt.geom.*;
import java.awt.event.*;
import javax.swing.*;

class Item extends JComponent {

	int height, width, rwidth;						//rwidth - szerokość rzeczywista
	Image img;									
	
	Item(Image img) {
		this.img = img;
		
		setSize(280, 160);
		ImageIcon ic = new ImageIcon(img);					
		height = ic.getIconHeight();
		rwidth = ic.getIconWidth();
		ic = null;

	}
	
	protected void paintComponent(Graphics g) {
		super.paintComponent(g);
		g.drawImage(img, 50, 20, (100 * rwidth) / height, 100, this);
	}
	
	public void setImage(Image img) {
		this.img = img;
		repaint();
	}
}

Moglibyście nakierować mnie jak to naprawić? ;) Będę wdzięczny!

1

Jak myślisz ile może zająć obiekt, który dziedziczy po klasie Image?
Jeżeli nie wiesz, to wyobraź sobie typowy obrazek tapety fullHD (1920x1080) w truekolorze. Jest on wczytywany z pliku i dekompresowany przez Javę. Po dekompresji zajmuje prawie 8 MB. A teraz wyobraź sobie 1500 takich obrazków, których referencje wpakowałeś do tablicy im. Ile będzie zajmować 1500 obrazków? 11250 MB, czyli prawie 11 GB. Masz tyle ramu wirtualnego w systemie? Nawet jeżeli masz, to JVM ma ograniczenia daleko mniejsze od tej wartości. Krótko mówiąc wywalaj te obrazki z ArrayListy, to śmieciara będzie je z pamięci usuwać. Pojemność ci nie ma żadnego znaczenia bo każda referencja się liczy.

0

Obraz zajmuję pamięć gdy zostaje narysowany więc skoro usuwam obiekty ci to obrazy znajdujące się w nich nie zostają usuwane?
Czyli rozumiem, że jak wymazuje obraz z ekranu to nie zwalnia on zajmowanej pamięci i w skutku powstaje wyciek?

1

Czyli rozumiem, że jak wymazuje obraz z ekranu to nie zwalnia on zajmowanej pamięci i w skutku powstaje wyciek?

Pamięć zwalniasz kiedy usuwany jest obiekt BufferedImage, którego widzisz jako Image. Problem widzisz z opóźnieniem ponieważ obrazki są wczytywane z opóźnieniem w stosunku do konstrukcji tych obiektów. Usunięcie referencji z ci nic nie daje ponieważ każdy dotychczas wczytany obrazek ma jeszcze swoją drugą referencję w arrayliście

im.add(img);
, której nigdy nie opróżniasz. W efekcie żadne obiekty BufferedImage nie są usuwane. Zlikwiduj tę listę lub zamień ją na listę ścieżek dostępu do plikow, zamiast listy obrazków.

0

Dobra, teraz rozumiem :) Wielkie dzięki za naprowadzenie mnie i pokazaniu błędu ;) Wstawiam jeszcze poprawiony kod głównej klasy dla osób, które mając podobny problem odwiedzą ten temat ;)

Program działa i wczytuje stopniowo wybrany folder z obrazami.

Kod:

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import javax.swing.*;


public class MainWindow extends JFrame {

	Item ci[];
	Image im[] = new Image[20];
	String path;
	
	JScrollPane jsp;
	JScrollBar jsb;
	JPanel panel = new JPanel();
	int maxy = 20, cy = -1;				
	

    MainWindow() {
    	setSize(300, 630);
		setLocation(30, 200);
		setLayout(null);
		setDefaultCloseOperation(EXIT_ON_CLOSE);
	
		panel.setLayout(null);	
		panel.setLocation(0, 0);
			
		jsp = new JScrollPane(panel);
		jsp.setSize(275, 550);
		jsp.setLocation(0, 0);
		add(jsp);
			
		jsb = jsp.getVerticalScrollBar();
		jsb.addAdjustmentListener(new AdjustmentListener() {
			public void adjustmentValueChanged(AdjustmentEvent ae) {
				if(!jsb.getValueIsAdjusting()) {
					cy = (int) (jsp.getViewport().getViewPosition().getY()-50)/140;
					if(cy > maxy  && cy != 0) showItems(cy); 
				}
					
			}
		});
	
		JButton butt = new JButton("Przeglądaj..");
			butt.setSize(100, 25);
			butt.setLocation(100, 560);
			butt.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent ae) {
					JFileChooser fc = new JFileChooser();
					fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
					int result = fc.showOpenDialog(null);
					
					if(result == JFileChooser.APPROVE_OPTION) {
						File f = fc.getSelectedFile();
						path = f.getAbsolutePath();
					
						try(DirectoryStream<Path> dir = Files.newDirectoryStream(Paths.get(path), "*.{jpg, png, gif}")) {
							int i = 0;
							for(Path p : dir) {
								if(i < 20) {
									im[i] = Toolkit.getDefaultToolkit().getImage(p.toString());
									//y += 140;
								}
								//else break;
								i++;
							}
							panel.setPreferredSize(new Dimension(250, i*140+50+15));
							jsp.revalidate();	
							

							ci = new Item[20];
							System.out.println("  ci[0] = null - pierwsze dodanie");
							
							int y = 50;
							for(int j = 0; j < 20; j++) {
								System.out.println("Dodanawanie pierwsze " + i + " z " + maxy);
								ci[j] = new Item(im[j]);
								ci[j].setLocation(0, y);
								panel.add(ci[j]);
								y += 140;
							
							}
			
						}
						catch(IOException io) { System.out.println("io " + io); }
					}
				}
			});
			add(butt);
		
		setVisible(true);
    }
    
    private void showItems(int item) {
		maxy = item+20;
		
		im = new Image[20];
		try(DirectoryStream<Path> dir = Files.newDirectoryStream(Paths.get(path), "*.{jpg, png, gif}")) {
			int i = 0, j = 0;
			for(Path p : dir) {
				if(i >= item && i < item+20) {
					System.out.println(i);
					im[j] = Toolkit.getDefaultToolkit().getImage(p.toString());
					j++;
				}
				else if(i > item+20) break;
				i++;
			}
		}
		catch(Exception exc) {System.out.println(exc); }
		
		System.out.println("Nowe maxy -  " + maxy);
		for(int y = (item*140)+50, j = 0; j < 20; j++) {
			System.out.println("Dodano " + j + " z " + maxy);
			ci[j].setImage(im[j]);
			ci[j].setLocation(0, y);
			y += 140;
		}
		//panel.repaint();
	} 
    
    public static void main(String args[]) {
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				new MainWindow();
			}
		});
	}
}

Temat można zamknąć.

1 użytkowników online, w tym zalogowanych: 0, gości: 1