/* * PreviewPanel.java * * Created on October 1, 2004, 5:55 PM */ package com.tog.desktopbeautifier1.client; import com.tog.desktopbeautifier.shared.Image; import com.tog.desktopbeautifier.shared.ImageSpecification; import com.tog.desktopbeautifier.shared.UnsupportedImageFormatException; import java.awt.Dimension; import java.awt.EventQueue; import java.awt.Graphics2D; import java.awt.GraphicsConfiguration; import java.awt.RenderingHints; import java.awt.image.BufferedImage; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import javax.swing.ImageIcon; import javax.swing.JLabel; /** * Represents the desktop preview panel. * * @author Gili Tzabari */ public class PreviewPanel extends JLabel { /** * Lock on instance state. */ private Object lock = new Object(); /** * Worker thread. */ private ExecutorService worker = null; private int desktopImageType = -1; private Map previewQuality = new HashMap(); /** * Creates a new instance of the PreviewPanel. */ public PreviewPanel() { // Always render preview image using low-quality previewQuality.put(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED); previewQuality.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); previewQuality.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED); worker = Executors.newFixedThreadPool(1); } /** * Cancel all pending tasks and attempts to interrupt the currently executing * task. This method blocks until the worker thread becomes idle. * * @throws InterruptedException If the operation was interrupted */ public void cancel() throws InterruptedException { synchronized (lock) { worker.shutdownNow(); worker.awaitTermination(0, TimeUnit.MILLISECONDS); worker = Executors.newFixedThreadPool(1); } } /** * Previews the specified image. * * @param image The image to view. If it is null, the preview panel displays * an empty image. */ public void preview(final Image image) throws { cancel(); if (image!=null) { worker.submit(new Runnable() { public void run() { displayText("Loading..."); final Dimension[] previewSize = new Dimension[1]; try { EventQueue.invokeAndWait(new Runnable() { public void run() { previewSize[0] = getSize(); } }); } catch (InvocationTargetException e) { displayText("An error has occured. Please try again... "); e.printStackTrace(); return; } catch (InterruptedException e) { // Rendering aborted return; } if (desktopImageType==-1) { // We use lazy initialization here because if we do this in the // constructor, anyone that tries using us as a bean gets a // UnsatisfiedLinkError. Desktop desktop = Win32Desktop.getInstance(); GraphicsConfiguration[] displayConfig = desktop.getGraphicsConfigurations(); desktopImageType = displayConfig[0].createCompatibleImage(1, 1).getType(); } ImageSpecification previewSpecification = new ImageSpecification( previewSize[0].width, previewSize[0].height, desktopImageType); final BufferedImage previewImage; try { previewImage = getPreviewImage(image, previewSpecification, BackgroundStyle.getSuggestedStyle( new Dimension(image.getWidth(), image.getHeight()), previewSpecification.getSize())); } catch (InterruptedException e) { return; } EventQueue.invokeLater(new Runnable() { public void run() { setText(null); setIcon(new ImageIcon(previewImage)); } }); } }); } else { // Display empty image EventQueue.invokeLater(new Runnable() { public void run() { setText(null); setIcon(null); } }); } } /** * Imports the desktop background into the preview panel. */ public void importDesktop() { Desktop desktop = Win32Desktop.getInstance(); try { File background = desktop.getBackground(); Image desktopImage = new Image(background); preview(desktopImage); } catch (UnsupportedImageFormatException e) { displayText("An error has occured. Please try again... "); e.printStackTrace(); return; } catch (FileNotFoundException e) { // do nothing } catch (IOException e) { displayText("An error has occured. Please try again... "); e.printStackTrace(); return; } } /** * Destroys the PreviewPanel. */ public void dispose() { synchronized (lock) { if (worker==null) return; worker.shutdownNow(); worker.awaitTermination(0, TimeUnit.MILLISECONDS); worker = null; } System.out.println("PreviewPanel shut down"); } /** * Displays text in the preview panel. */ protected void displayText(final String text) { EventQueue.invokeLater(new Runnable() { public void run() { setIcon(null); setText(text); } }); } /** * We will be rendering the image described by 'source' into the desktop * described by 'target'. The preview panel is described by 'previewSize'. * The assumption is that the preview panel is a scaled-down representation * of the desktop. * * NOTE: BackgroundStyle.CENTER and BackgroundStyle.TILED are interpreted * differently than most operating systems. Specifically, the two modes will * never allow images to render bigger than the visible area of 'target'. * Conventionally, if images are larger than the visible area and CENTER or * TILED mode is used, they are allowed to span beyond the visible region. * We prevent this from ever occuring. * * @param source Image to render * @param target Specification of image as it needs to be rendered * @param previewSize Size of preview panel * @param style Image display style * @see BackgroundStyle * @throws InterruptedException if the thread has been interrupted */ protected BufferedImage getPreviewImage(Image source, ImageSpecification target, BackgroundStyle style) throws InterruptedException { // Maintaining the original aspect-ratio of the image, retrieve a // scaled-down version that is smaller than or equal to the target size Image scaledImage; if (source.getWidth() > target.getWidth() || source.getHeight() > target.getHeight()) { double sourceAspectRatio = (double) source.getWidth() / source.getHeight(); double targetAspectRatio = (double) target.getWidth() / target.getHeight(); double scaleRatio; // Resulting image will touch the target borders either horizontally or // vertically or both. if (sourceAspectRatio > targetAspectRatio) { // Image too wide scaleRatio = (double) target.getWidth() / source.getWidth(); } else { // Image too tall scaleRatio = (double) target.getHeight() / source.getHeight(); } scaledImage = source.getScaledImage((int) (source.getWidth() * scaleRatio), (int) (source.getHeight() * scaleRatio), target.getColorDepth()); } else scaledImage = source.getScaledImage(source.getWidth(), source.getHeight(), target.getColorDepth()); if (scaledImage.getWidth()==target.getWidth() && scaledImage.getHeight()==target.getHeight()) return scaledImage.toBufferedImage(); switch (style) { case STRETCH: { BufferedImage result = new BufferedImage(target.getWidth(), target.getHeight(), target.getType()); Graphics2D g2d = result.createGraphics(); g2d.addRenderingHints(previewQuality); if (Thread.interrupted()) { // Rendering aborted throw new InterruptedException(); } g2d.drawImage(scaledImage.toBufferedImage(), 0, 0, result.getWidth(), result.getHeight(), null); g2d.dispose(); return result; } case CENTER: { // Center the image BufferedImage result = new BufferedImage(target.getWidth(), target.getHeight(), scaledImage.getType()); Graphics2D g2d = result.createGraphics(); g2d.addRenderingHints(previewQuality); if (Thread.interrupted()) { // Rendering aborted throw new InterruptedException(); } g2d.drawImage(scaledImage.toBufferedImage(), (target.getWidth() - scaledImage.getWidth()) / 2, (target.getHeight() - scaledImage.getHeight()) / 2, scaledImage.getWidth(), scaledImage.getHeight(), null); g2d.dispose(); return result; } case TILED: { // Tile the image int columns = target.getWidth() / source.getWidth(); if (target.getWidth() % source.getWidth() != 0) ++columns; int rows = target.getHeight() / source.getHeight(); if (target.getHeight() % scaledImage.getHeight() != 0) ++rows; BufferedImage result = new BufferedImage(target.getWidth(), target.getHeight(), scaledImage.getType()); Graphics2D g2d = result.createGraphics(); g2d.addRenderingHints(previewQuality); try { for (int y = 0; y < rows; ++y) for (int x = 0; x < columns; ++x) { if (Thread.interrupted()) { // Rendering aborted throw new InterruptedException(); } g2d.drawImage(scaledImage.toBufferedImage(), null, x * scaledImage.getWidth(), y * scaledImage.getHeight()); } } finally { g2d.dispose(); } return result; } default: throw new IllegalStateException("Unexpected style"); } } }