Eric Bergman-Terrell's Blog

Java Programming Tip: SWT Photo Frame Program
October 31, 2016

SWT Photo Frame App

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);
    }
}
Keywords: Java, SWT, Java 8, Eclipse, Standard Widget Toolkit, Windows, Photo Frame

Reader Comments

Comment on this Blog Post

Recent Posts

TitleDate
Java Programming Tip: SWT Photo Frame ProgramOctober 31, 2016
Vault 3 (Desktop) Version 1.63 ReleasedSeptember 9, 2016
"Compliance with Court Orders Act of 2016"April 9, 2016
Disable "Visual Voicemail" on Android / T-MobileJanuary 17, 2016
IPv6 HumorDecember 10, 2015
Java Programming Tip: Specify the JVM time zoneDecember 7, 2015
Node.js / Express Programming Tip: Detect and Fix Memory LeaksOctober 27, 2015