I have a Windows computer that I use as a dedicated photo frame. Unlike the other photo frames I've tried, which can only handle a relatively small number of photo files, the Windows computer can handle all my 20,000+ photos.
I was using the built-in Windows photo screen saver, but I've noticed that it fails, sometimes immediately, sometimes after multiple days. Is it crashing when it hits a corrupt image? Or leaking memory or resources? I'm not sure.
I wrote the following minimalist photo frame program using Java 8 and SWT. SWT is the "Standard Widget Toolkit" that the Eclipse development system uses for its UI. It requires Java 8, and I'm using SWT version 4.4.2. The photo frame program will not scale the images (I scale them to the device's resolution before I copy them to the machine).
I checked the program for memory leaks using JVisualVM, and it seems to be solid. I should know for sure in a few days of running it on the low-end Windows machine. That little machine doesn't have a lot of memory or resources to leak.
Full Source Code:
import org.eclipse.swt.SWT; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.KeyListener; import org.eclipse.swt.graphics.*; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Canvas; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.Random; import java.util.stream.Collectors; import static java.lang.System.exit; public class Main { private Runnable runnable; private Image image; private Main(Display display, List<Path> allImagePaths, Canvas canvas, int secondsPerImage) { final Random random = new Random(); canvas.addPaintListener(e -> { final Rectangle canvasBounds = canvas.getBounds(); final Rectangle imageBounds = image.getBounds(); final Point margin = new Point((canvasBounds.width - imageBounds.width) / 2, (canvasBounds.height - imageBounds.height) / 2); e.gc.drawImage(image, margin.x, margin.y); }); runnable = () -> { // Hide the cursor by moving it off of the screen. display.setCursorLocation(display.getClientArea().width, display.getClientArea().height); try { if (image != null && !image.isDisposed()) { image.dispose(); image = null; } image = new Image(display, allImagePaths.get(random.nextInt(allImagePaths.size())).toFile().getAbsolutePath()); canvas.redraw(); } catch (Exception ex) { System.out.println(ex); // If this attempt failed, try the next image immediately. display.timerExec(0, runnable); } display.timerExec(secondsPerImage * 1000, runnable); }; } private void run() { runnable.run(); } public static void main(String[] args) { if (args.length < 3) { System.out.println("SlideShow {image folder} {seconds per image} {file type 1} ... {file type N}\r\n"); System.out.println("For example:\r\n"); System.out.println("SlideShow \"D:\\photos\" 10 \".jpg\" \".jpeg\" \".png\"\r\n"); exit(0); } String imagesFolder = args[0]; int secondsPerImage = Integer.parseInt(args[1]); List<String> fileTypes = new ArrayList<>(); for (int i = 2; i < args.length; i++) { fileTypes.add(args[i].toUpperCase()); } System.out.println(String.format("Searching for files in %s\r\n", args[0])); System.out.println("File types:\r\n"); fileTypes.forEach(System.out::println); System.out.println(""); List<Path> allImagePaths = new ArrayList<>(); try { allImagePaths = Files.walk(Paths.get(imagesFolder)) .filter(path -> path.toFile().isFile()) .filter(path -> isPictureFile(path, fileTypes)) .collect(Collectors.toList()); } catch (Exception ex) { System.out.println(ex); } System.out.println(String.format("Loaded %,d files\r\n", allImagePaths.size())); Display display = Display.getDefault(); System.out.println(String.format("Image size should be %d x %d\r\n", display.getBounds().width, display.getBounds().height)); System.out.println("Press Esc to stop\r\n"); if (allImagePaths.size() > 0) { try { Thread.sleep(1000 * 10); } catch (InterruptedException e) { e.printStackTrace(); } // Make sure that no other windows cover up the app's window. Shell shell = new Shell(display, SWT.NO_TRIM | SWT.ON_TOP); shell.setBounds(display.getBounds()); GridLayout gridLayout = new GridLayout(1, false); gridLayout.marginHeight = gridLayout.marginWidth = 0; shell.setLayout(gridLayout); Canvas canvas = new Canvas(shell, SWT.NO_TRIM); GridData gridData = new GridData(SWT.FILL); gridData.grabExcessHorizontalSpace = true; gridData.grabExcessVerticalSpace = true; gridData.horizontalAlignment = SWT.FILL; gridData.verticalAlignment = SWT.FILL; canvas.setLayoutData(gridData); canvas.setBackground(display.getSystemColor(SWT.COLOR_BLACK)); // Allow the user to stop the program by typing Esc. canvas.addKeyListener(new KeyListener() { @Override public void keyPressed(KeyEvent e) { if (e.keyCode == SWT.ESC) { display.dispose(); } } @Override public void keyReleased(KeyEvent e) { } }); shell.open(); new Main(display, allImagePaths, canvas, secondsPerImage).run(); while (!shell.isDisposed()) { if (!display.readAndDispatch()) { display.sleep(); } } display.dispose(); } else { exit(0); } } private static boolean isPictureFile(Path path, List<String> fileTypes) { final String fileName = path.toFile().getName().toUpperCase(); return fileTypes.stream().anyMatch(fileName::endsWith); } }
Title | Date |
.NET Public-Key (Asymmetric) Cryptography Demo | July 20, 2025 |
Raspberry Pi 3B+ Photo Frame | June 17, 2025 |
EBTCalc (Android) Version 1.53 is now available | May 19, 2024 |
Vault 3 Security Enhancements | October 24, 2023 |
Vault 3 is now available for Apple OSX M2 Mac Computers! | September 18, 2023 |
Vault (for Desktop) Version 0.77 Released | March 26, 2023 |
EBTCalc (Android) Version 1.44 is now available | October 12, 2021 |