Expert Java Programming Part III

JGuru

Wise Old Owl
Expert Java Programming Part III

In this tutorial we will look at writing Java Programs at the expert level!!
I got some exciting Java programs to get your Java adrenaline pumped up!!
Let's get started!!

1) Write a simple program to show a Collage Demo in Java ?

Code:
/**
 * Created with IntelliJ IDEA.
 * User: JGuru
 * Date: 7/18/16
 * Time: 12:29 PM
 * To change this template use File | Settings | File Templates.
 */
import java.awt.*;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.dnd.*;
import java.awt.event.*;
import java.io.*;
import java.util.Iterator;
import java.util.List;
import java.awt.*;
import java.awt.dnd.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.util.LinkedList;
import java.util.List;
import javax.swing.*;
import javax.swing.border.*;
import java.awt.image.BufferedImage;
import java.util.concurrent.atomic.AtomicBoolean;
import java.awt.geom.Area;
import java.net.URL;
import javax.imageio.ImageIO;
import java.lang.reflect.Field;
import java.awt.image.*;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.HashMap;

public class CollageDemo extends JFrame {

    private static String[] photoNames;
    private static BufferedImage[] photoImages;

    private ImageViewer viewer;
    private PhotoItemsPanel pane;
    private GridLayout grid;
    private JScrollPane scroller;
    private CollageBoard collageBoard;
    private static String path = "*forum.digit.in/images/Nature/";

    public CollageDemo() throws HeadlessException {
        super("Photo Collage");

        setGlassPane(new GhostGlassPane());

        buildContent();

        getContentPane().setBackground(Color.WHITE);

        pack();
        setLocationRelativeTo(null);
        Toolkit.getDefaultToolkit().setDynamicLayout(true);

        setIconImage(new ImageIcon(getClass().getResource("*forum.digit.in/images/app-icon-single-48x48.png")).getImage());
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    public void buildContent() {
        add(buildHeader(), BorderLayout.NORTH);
        add(buildContentPanel(), BorderLayout.CENTER);
        add(buildButtonsPanel(), BorderLayout.SOUTH);
    }

    private Component buildButtonsPanel() {
        JButton button;

        JPanel panel = new JPanel(new FlowLayout(FlowLayout.TRAILING));
        panel.setBackground(Color.WHITE);

        panel.add(button = new JButton("Clear Collage",
                new ImageIcon(getClass().getResource("*forum.digit.in/images/clear-button.png"))));
        button.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                collageBoard.clear();
            }
        });

        if (SwingUtilities.getRoot(this) instanceof JFrame) {
            panel.add(button = new JButton("Exit",
                    new ImageIcon(getClass().getResource("*forum.digit.in/images/exit-button.png"))));
            button.addActionListener(new ActionListener() {
                // pure laziness
                public void actionPerformed(ActionEvent e) {
                    System.exit(0);
                }
            });
        }

        JPanel wrapper = new JPanel(new BorderLayout());
        wrapper.add(new JSeparator(), BorderLayout.NORTH);
        wrapper.add(panel);

        return wrapper;
    }

    private Component buildHeader() {
        ImageIcon icon = new ImageIcon(getClass().getResource("*forum.digit.in/images/app-icon.png"));
        HeaderPanel header = new HeaderPanel(icon,
                "Photo Collage",
                "Drag and drop pictures to the viewer panel to identify your favorite.",
                "Drop pictures onto the collage panel to create a wonderful... collage.");
        return header;
    }

    private Component buildContentPanel() {
        JPanel content = new JPanel(new BorderLayout());
        content.setOpaque(false);
        content.add(buildTabbedPane(), BorderLayout.CENTER);
        content.add(buildItemsList(), BorderLayout.WEST);

        return content;
    }

    private Component buildTabbedPane() {
        JTabbedPane tabbed = new JTabbedPane();
        tabbed.addTab("Viewer", buildViewer());
        tabbed.addTab("Collage", buildCollage());

        tabbed.setBorder(new CompoundBorder(BorderFactory.createEmptyBorder(6, 6, 6, 6),
                tabbed.getBorder()));

        return tabbed;
    }

    private Component buildCollage() {
        collageBoard = new CollageBoard();
        collageBoard.setBorder(BorderFactory.createEmptyBorder(0, 2, 3, 3));
        return collageBoard;
    }

    private Component buildItemsList() {
        pane = new PhotoItemsPanel(grid = new GridLayout((photoNames.length
                + (photoNames.length % 2 == 0 ? 0 : 1)) / 2, 2));
        pane.setOpaque(false);

        if (SwingUtilities.getRoot(this) instanceof JFrame) {
            pane.setDropTarget(new DropTarget(pane, new DropFilePicture()));
        }

        for (int i = 0; i < photoNames.length; i++) {
            pane.add(new PhotoItem(photoNames[i], photoImages[i]));
        }

        scroller = new JScrollPane(pane);
        scroller.setOpaque(false);
        scroller.getViewport().setOpaque(false);
        scroller.setBorder(new CompoundBorder(BorderFactory.createEmptyBorder(6, 6, 6, 6),
                scroller.getBorder()));
        Insets insets = scroller.getBorder().getBorderInsets(scroller);
        scroller.setPreferredSize(new Dimension(PhotoItem.getItemWidth() * 2
                + scroller.getVerticalScrollBar().getPreferredSize().width
                + insets.left + insets.right,
                PhotoItem.getItemHeight() * 3
                        + scroller.getHorizontalScrollBar().getPreferredSize().height));

        return scroller;
    }

    private Component buildViewer() {
        viewer = new ImageViewer(photoImages[1]);
        viewer.setOpaque(false);
        JScrollPane viewScroller = new JScrollPane(viewer);
        viewScroller.setOpaque(false);
        viewScroller.getViewport().setOpaque(false);
        viewScroller.setBorder(BorderFactory.createEmptyBorder(0, 2, 3, 3));
        return viewScroller;
    }

    public static void main(String[] args) {
        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException e) {
        }

        loadPhotos();

        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                CollageDemo frame = new CollageDemo();
                frame.setVisible(true);
            }
        });
    }

    private static void loadPhotos() {
        int i = 0;
        //filter out the Images
        photoNames = new File(path).list(new java.io.FilenameFilter() {

            @Override
            public boolean accept(File dir, String name) {
                if (name.endsWith("jpg")) {
                    return true;
                }
                return false;
            }
        });
        photoImages = new BufferedImage[photoNames.length];
        // Remove the file extension
        for (i = 0; i < photoNames.length; i++) {
            photoNames[i] = photoNames[i].substring(0, photoNames[i].lastIndexOf("."));
            try {
                photoImages[i] = Java2dHelper.loadCompatibleImage(CollageDemo.class.getResource(path + photoNames[i] + ".jpg"));
            } catch (IOException ioe) {
            }
        }
    }

    private class DropFilePicture implements DropTargetListener {

        public void dragEnter(DropTargetDragEvent evt) {
        }

        public void dragOver(DropTargetDragEvent evt) {
        }

        public void dragExit(DropTargetEvent evt) {
        }

        public void dragScroll(DropTargetDragEvent evt) {
        }

        public void dropActionChanged(DropTargetDragEvent evt) {
        }

        public void drop(final DropTargetDropEvent evt) {
            DataFlavor[] flavors = evt.getCurrentDataFlavors();
            if (flavors == null) {
                return;
            }

            for (int i = flavors.length - 1; i >= 0; i--) {
                if (flavors[i].isFlavorJavaFileListType()) {
                    evt.acceptDrop(DnDConstants.ACTION_COPY);
                    Transferable transferable = evt.getTransferable();
                    final Iterator iterator;
                    try {
                        iterator = ((List) transferable.getTransferData(flavors[i])).iterator();

                        setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));

                        new Thread(new Runnable() {
                            public void run() {
                                File imageFile = (File) iterator.next();
                                final BufferedImage[] image = new BufferedImage[1];

                                try {
                                    image[0] = Java2dHelper.loadCompatibleImage(imageFile.toURI().toURL());
                                } catch (Exception e) {
                                    setCursor(Cursor.getDefaultCursor());
                                    return;
                                }

                                final String[] name = new String[]{imageFile.getName()};
                                name[0] = name[0].substring(0, name[0].lastIndexOf("."));
                                if (name[0].length() > 13) {
                                    name[0] = name[0].substring(0, 10) + "..." + name[0].substring(name[0].length() - 3);
                                }

                                if (pane.getComponentCount() % 2 == 0) {
                                    grid.setRows(grid.getRows() + 1);
                                }

                                SwingUtilities.invokeLater(new Runnable() {
                                    public void run() {
                                        pane.add(new PhotoItem(name[0], image[0]));

                                        pane.revalidate();
                                        scroller.getViewport().scrollRectToVisible(new Rectangle(0, pane.getHeight() - 1, 1, 1));

                                        setCursor(Cursor.getDefaultCursor());
                                        evt.dropComplete(true);
                                    }
                                });
                            }
                        }).start();
                    } catch (UnsupportedFlavorException | IOException e1) {
                    }
                }
            }
        }
    }
}

class CollageBoard extends JComponent implements DropTargetListener {
    private List<CollagePart> parts = new LinkedList<CollagePart>();
    private RenderingHints hints;
    private float clearRatio = -1;

    public CollageBoard() {
        createRenderingHints();
        setDropTarget(new DropTarget(this, this));
    }

    private void createRenderingHints() {
        hints = new RenderingHints(RenderingHints.KEY_INTERPOLATION,
                RenderingHints.VALUE_INTERPOLATION_BICUBIC);
        hints.put(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);
        hints.put(RenderingHints.KEY_ALPHA_INTERPOLATION,
                RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
        hints.put(RenderingHints.KEY_COLOR_RENDERING,
                RenderingHints.VALUE_COLOR_RENDER_QUALITY);
        hints.put(RenderingHints.KEY_RENDERING,
                RenderingHints.VALUE_RENDER_QUALITY);
    }

    public void addImage(BufferedImage image, int x, int y, double rotation) {
        parts.add(new CollagePart(image, x, y, rotation));
    }

    public void clear() {
        //parts.clear();
        Timer clearTimer = new Timer(1000 / 60, new ClearAnimation());
        clearTimer.start();
    }

    private class ClearAnimation implements ActionListener {
        private long DELAY_ANIMATION = 400;
        private long start;
        private boolean isInitialized;

        private ClearAnimation() {
            isInitialized = false;
        }

        public void actionPerformed(ActionEvent e) {
            if (!isInitialized) {
                start = System.currentTimeMillis();
                isInitialized = true;
            }

            long elapsed = System.currentTimeMillis() - start;
            if (elapsed > DELAY_ANIMATION) {
                ((Timer) e.getSource()).stop();
                clearRatio = -1;
                parts.clear();
            } else {
                float time = (float) elapsed / (float) DELAY_ANIMATION;
                clearRatio = time;
            }

            repaint();
        }
    }

    @Override
    protected void paintComponent(Graphics g) {
        if (isVisible()) {
            Insets insets = getInsets();

            Graphics2D g2 = (Graphics2D) g;
            g2.setRenderingHints(hints);

            g2.setColor(getBackground());
            g2.fillRect(insets.left, insets.top,
                    getWidth() - insets.left - insets.right, getHeight() - insets.top - insets.bottom);

            for (CollagePart part: parts) {
                drawPart(g2, part);
            }
        }
    }

    private void drawPart(Graphics2D g2, CollagePart part) {
        if (part.getImage() != null) {
            int x = part.getX() - part.getImage().getWidth() / 2;
            int y = part.getY() - part.getImage().getHeight() / 2;

            g2.translate(x, y);
            g2.rotate(part.getRotation());

            Composite composite = g2.getComposite();
            if (clearRatio != -1) {
                g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f - clearRatio));
            }
            g2.drawImage(part.getImage(), null, 0, 0);

            g2.setComposite(composite);
            g2.scale(1.0, 1.0);
            g2.rotate(-part.getRotation());
            g2.translate(-x, -y);
        }
    }

    private class CollagePart {
        private BufferedImage image;
        private int x;
        private int y;
        private double rotation;

        public CollagePart(BufferedImage image, int x, int y, double rotation) {
            this.image = image;
            this.x = x;
            this.y = y;
            this.rotation = rotation;
        }

        public BufferedImage getImage() {
            return image;
        }

        public int getX() {
            return x;
        }

        public int getY() {
            return y;
        }

        public double getRotation() {
            return rotation;
        }
    }

    public void dragEnter(DropTargetDragEvent dtde) {
    }

    public void dragOver(DropTargetDragEvent dtde) {
    }

    public void dropActionChanged(DropTargetDragEvent dtde) {
    }

    public void dragExit(DropTargetEvent dte) {
    }

    public void drop(DropTargetDropEvent dtde) {
        DataFlavor[] flavors = dtde.getCurrentDataFlavors();
        if (flavors == null) {
            return;
        }

        for (int i = flavors.length - 1; i >= 0; i--) {
            if (flavors[i].equals(CollageItemTransferHandler.COLLAGE_ITEM_FLAVOR)) {
                dtde.acceptDrop(DnDConstants.ACTION_COPY);
                Transferable transferable = dtde.getTransferable();

                CollageItem newImage = null;
                try {
                    newImage = (CollageItem) transferable.getTransferData(CollageItemTransferHandler.COLLAGE_ITEM_FLAVOR);
                } catch (Exception e) {
                }

                if (newImage == null) {
                    return;
                }

                GhostGlassPane glassPane = (GhostGlassPane) SwingUtilities.getRootPane(this).getGlassPane();
                Point p = dtde.getLocation();

                double phi = Math.PI / 6.0;
                double d = 1.0 - Math.random() * 2.0;
                double rotation = d * phi / 2.0;

                addImage(newImage.getThumb(), p.x, p.y, rotation);

                // XXX: to change when animated
                glassPane.setImage(null);
                glassPane.setVisible(false);
                DragAndDropLock.setLocked(false);

                dtde.dropComplete(true);
                break;
            }
        }
    }
}

class CollageItem {
    private BufferedImage itemPicture;
    private BufferedImage image;

    public CollageItem(BufferedImage itemPicture, BufferedImage image) {
        this.itemPicture = itemPicture;
        this.image = image;
    }

    public BufferedImage getImage() {
        return image;
    }

    public BufferedImage getThumb() {
        return itemPicture;
    }
}

class CollageItemTransferHandler implements Transferable {
    private CollageItem item;
    public static DataFlavor COLLAGE_ITEM_FLAVOR;
    static {
        try {
            COLLAGE_ITEM_FLAVOR = new DataFlavor("image/x-collage-item;class=" + CollageItem.class.getCanonicalName());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    public CollageItemTransferHandler(BufferedImage itemPicture, BufferedImage image) {
        this.item = new CollageItem(itemPicture, image);
    }

    public DataFlavor[] getTransferDataFlavors() {
        return new DataFlavor[] { COLLAGE_ITEM_FLAVOR };
    }

    public boolean isDataFlavorSupported(DataFlavor flavor) {
        return flavor.isMimeTypeEqual(COLLAGE_ITEM_FLAVOR.getMimeType());
    }

    public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException,
            IOException {
        if (flavor.equals(COLLAGE_ITEM_FLAVOR)) {
            return item;
        } else {
            return new UnsupportedFlavorException(flavor);
        }
    }
}

class DragAndDropLock {
    private static AtomicBoolean locked = new AtomicBoolean(false);
    private static AtomicBoolean startedDnD = new AtomicBoolean(false);

    public static boolean isLocked() {
        return locked.get();
    }

    public static void setLocked(boolean isLocked) {
        locked.set(isLocked);
    }

    public static boolean isDragAndDropStarted() {
        return startedDnD.get();
    }

    public static void setDragAndDropStarted(boolean isLocked) {
        startedDnD.set(isLocked);
    }
}

class GhostGlassPane extends JPanel {
    private final int ANIMATION_DELAY = 500;

    private BufferedImage dragged = null;
    private Point location = new Point(0, 0);
    private Point oldLocation = new Point(0, 0);

    private int width;
    private int height;
    private Rectangle visibleRect = null;

    private float zoom = 1.0f;
    private float alpha = 0.7f;

    public GhostGlassPane() {
        setOpaque(false);
    }

    public void setImage(BufferedImage dragged) {
        setImage(dragged, dragged == null ? 0 : dragged.getWidth());
    }

    public void setImage(BufferedImage dragged, int width) {
        if (dragged != null) {
            float ratio = (float) dragged.getWidth() / (float) dragged.getHeight();
            this.width = width;
            height = (int) (width / ratio);
        }

        this.dragged = dragged;
    }

    public void setPoint(Point location) {
        this.oldLocation = this.location;
        this.location = location;
    }

    public Rectangle getRepaintRect() {
        int x = (int) (location.getX() - (width * zoom / 2));
        int y = (int) (location.getY() - (height * zoom / 2));

        int x2 = (int) (oldLocation.getX() - (width * zoom / 2));
        int y2 = (int) (oldLocation.getY() - (height * zoom / 2));

        int width = (int) (this.width * zoom);
        int height = (int) (this.height * zoom);

        return new Rectangle(x, y, width, height).union(new Rectangle(x2, y2, width, height));
    }

    @Override
    protected void paintComponent(Graphics g) {
        if (dragged == null || !isVisible()) {
            return;
        }

        Graphics2D g2 = (Graphics2D) g.create();
        g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);

        int x = (int) (location.getX() - (width * zoom / 2));
        int y = (int) (location.getY() - (height * zoom / 2));

        if (visibleRect != null) {
            g2.setClip(visibleRect);
        }

        if (visibleRect != null) {
            Area clip = new Area(visibleRect);
            g2.setClip(clip);
        }

        g2.drawImage(dragged, x, y, (int) (width * zoom), (int) (height * zoom), null);
    }

    public void startAnimation(Rectangle visibleRect) {
        this.visibleRect = visibleRect;
        new Timer(1000 / 30, new FadeOutAnimation()).start();
    }

    private class FadeOutAnimation implements ActionListener {
        private long start;

        FadeOutAnimation() {
            this.start = System.currentTimeMillis();
            oldLocation = location;
        }

        public void actionPerformed(ActionEvent e) {
            long elapsed = System.currentTimeMillis() - start;
            if (elapsed > ANIMATION_DELAY) {
                ((Timer) e.getSource()).stop();
                setVisible(false);
                zoom = 1.0f;
                alpha = 0.6f;
                visibleRect = null;
                dragged = null;
                DragAndDropLock.setLocked(false);
            } else {
                alpha = 0.6f - (0.6f * (float) elapsed / (float) ANIMATION_DELAY);
                zoom = 1.0f + 3.0f * ((float) elapsed / (float) ANIMATION_DELAY);
            }
            repaint(getRepaintRect());
        }
    }
}

class HeaderPanel extends JPanel {

    private ImageIcon icon;

    public HeaderPanel(ImageIcon icon,
                       String title,
                       String help1,
                       String help2) {
        super(new BorderLayout());

        this.icon = icon;

        JPanel titlesPanel = new JPanel(new GridLayout(3, 1));
        titlesPanel.setOpaque(false);
        titlesPanel.setBorder(new EmptyBorder(12, 0, 12, 0));

        JLabel headerTitle = new JLabel(title);
        Font police = headerTitle.getFont().deriveFont(Font.BOLD);
        headerTitle.setFont(police);
        headerTitle.setBorder(new EmptyBorder(0, 12, 0, 0));
        titlesPanel.add(headerTitle);

        JLabel message;

        titlesPanel.add(message = new JLabel(help1));
        police = headerTitle.getFont().deriveFont(Font.PLAIN);
        message.setFont(police);
        message.setBorder(new EmptyBorder(0, 24, 0, 0));

        titlesPanel.add(message = new JLabel(help2));
        police = headerTitle.getFont().deriveFont(Font.PLAIN);
        message.setFont(police);
        message.setBorder(new EmptyBorder(0, 24, 0, 0));

        message = new JLabel(this.icon);
        message.setBorder(new EmptyBorder(0, 0, 0, 12));

        add(BorderLayout.WEST, titlesPanel);
        add(BorderLayout.EAST, message);
        add(BorderLayout.SOUTH, new JSeparator(JSeparator.HORIZONTAL));

        setPreferredSize(new Dimension(500, this.icon.getIconHeight() + 24));
    }

    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        if (!isOpaque()) {
            return;
        }

        Color control = UIManager.getColor("control");

        int width = getWidth();
        int height = getHeight();

        Graphics2D g2 = (Graphics2D) g;
        Paint storedPaint = g2.getPaint();
        g2.setPaint(new GradientPaint(this.icon.getIconWidth(), 0, Color.WHITE, width, 0, control));
        g2.fillRect(0, 0, width, height);
        g2.setPaint(storedPaint);
    }
}

class ImageViewer extends JComponent implements DropTargetListener {
    private final int ANIMATION_DELAY = 500;

    private BufferedImage image = null;
    private BufferedImage transitionImage = null;

    private float transitionLevel = 0.0f;

    public ImageViewer(BufferedImage image) {
        this.image = image;
        setDropTarget(new DropTarget(this, this));
    }

    @Override
    public Dimension getPreferredSize() {
        return image!= null ? new Dimension(image.getWidth(), image.getHeight()) :
                super.getPreferredSize();
    }

    @Override
    protected void paintComponent(Graphics g) {
        if (isVisible() && image != null) {
            int x = (getWidth() - image.getWidth()) / 2;
            int y = (getHeight() - image.getHeight()) / 2;

            g.drawImage(image, x, y, null);
            if (transitionImage != null) {
                Graphics2D g2 = (Graphics2D) g;
                g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, transitionLevel));

                x = (getWidth() - transitionImage.getWidth()) / 2;
                y = (getHeight() - transitionImage.getHeight()) / 2;

                g2.drawImage(transitionImage, x, y, null);
            }
        }
    }

    public void setImage(BufferedImage image) {
        this.transitionImage = image;
        this.transitionLevel = 0.0f;
        new Timer(1000 / 30, new FadeInAnimation()).start();
    }

    public BufferedImage getImage() {
        return image;
    }

    private class FadeInAnimation implements ActionListener {
        private long start;

        FadeInAnimation() {
            start = System.currentTimeMillis();
        }

        public void actionPerformed(ActionEvent e) {
            long elapsed = System.currentTimeMillis() - start;
            if (elapsed > ANIMATION_DELAY) {
                ((Timer) e.getSource()).stop();
                transitionLevel = 1.0f;
                image = transitionImage;
                transitionImage = null;
                revalidate();
            } else {
                transitionLevel = (float) elapsed / (float) ANIMATION_DELAY;
            }
            repaint();
        }
    }

    public void dragEnter(DropTargetDragEvent dtde) {
    }

    public void dragOver(DropTargetDragEvent dtde) {
    }

    public void dropActionChanged(DropTargetDragEvent dtde) {
    }

    public void dragExit(DropTargetEvent dte) {
    }

    public void drop(DropTargetDropEvent dtde) {
        DataFlavor[] flavors = dtde.getCurrentDataFlavors();
        if (flavors == null) {
            return;
        }

        for (int i = flavors.length - 1; i >= 0; i--) {
            if (flavors[i].equals(CollageItemTransferHandler.COLLAGE_ITEM_FLAVOR)) {
                dtde.acceptDrop(DnDConstants.ACTION_COPY);
                Transferable transferable = dtde.getTransferable();

                CollageItem newImage = null;
                try {
                    newImage = (CollageItem) transferable.getTransferData(CollageItemTransferHandler.COLLAGE_ITEM_FLAVOR);
                } catch (Exception e) {
                }

                if (newImage == null) {
                    return;
                }

                GhostGlassPane glassPane = (GhostGlassPane) SwingUtilities.getRootPane(this).getGlassPane();

                setImage(newImage.getImage());
                glassPane.setImage(newImage.getImage(), PhotoItem.getItemWidth());
                glassPane.startAnimation(SwingUtilities.convertRectangle(this,
                        this.getVisibleRect(),
                        glassPane));

                dtde.dropComplete(true);
                break;
            }
        }
    }
}

class Java2dHelper {

    public static BufferedImage loadCompatibleImage(URL resource) throws IOException {
        BufferedImage image = ImageIO.read(resource);
        GraphicsConfiguration configuration = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
        BufferedImage compatibleImage = configuration.createCompatibleImage(image.getWidth(), image.getHeight());
        Graphics g = compatibleImage.getGraphics();
        g.drawImage(image, 0, 0, null);
        g.dispose();
        return compatibleImage;
    }

    public static BufferedImage createThumbnail(BufferedImage image, int requestedThumbSize) {
        float ratio = (float) image.getWidth() / (float) image.getHeight();
        int width = image.getWidth();
        BufferedImage thumb = image;

        do {
            width /= 2;
            if (width < requestedThumbSize) {
                width = requestedThumbSize;
            }

            BufferedImage temp = new BufferedImage(width, (int) (width / ratio), BufferedImage.TYPE_INT_ARGB);
            Graphics2D g2 = temp.createGraphics();
            g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
                    RenderingHints.VALUE_INTERPOLATION_BILINEAR);
            g2.drawImage(thumb, 0, 0, temp.getWidth(), temp.getHeight(), null);
            g2.dispose();

            thumb = temp;
        } while (width != requestedThumbSize);

        return thumb;
    }
}

class PhotoItem extends JComponent implements DragGestureListener, DragSourceMotionListener, DragSourceListener {
    private static final Insets INNER_MARGIN = new Insets(6, 6, 6, 6);
    private static final Insets OUTER_MARGIN = new Insets(10, 10, 10, 10);

    private static final int ITEM_WIDTH = 127;
    private static final int ITEM_HEIGHT = 140;
    private static final int CLIP_HEIGHT =  (int) (ITEM_HEIGHT * 0.7);

    private static final int SHADOW_SIZE = 10;
    private static BufferedImage shadowBuffer;

    private BufferedImage image;
    private BufferedImage buffer;

    private String name;
    private double ratio;

    private Font font;
    private RenderingHints hints;

    public PhotoItem(String name, BufferedImage image) {
        this.name = name;
        this.image = image;
        this.ratio = (double) image.getWidth() / (double) image.getHeight();
        this.font = new Font("Tahoma", Font.BOLD, 12);

        createRenderingHints();
        DragSource dragSource = DragSource.getDefaultDragSource();
        dragSource.createDefaultDragGestureRecognizer(this, DnDConstants.ACTION_COPY, this);
        dragSource.addDragSourceMotionListener(this);
    }

    private void createRenderingHints() {
        hints = new RenderingHints(RenderingHints.KEY_INTERPOLATION,
                RenderingHints.VALUE_INTERPOLATION_BICUBIC);
        Object value = RenderingHints.VALUE_TEXT_ANTIALIAS_ON;
        try {
            Field declaredField = RenderingHints.class.getDeclaredField("VALUE_TEXT_ANTIALIAS_LCD_HRGB");
            value = declaredField.get(null);
        } catch (Exception e) {
        }
        hints.put(RenderingHints.KEY_TEXT_ANTIALIASING, value);
    }

    public static int getItemWidth() {
        return ITEM_WIDTH + INNER_MARGIN.left + INNER_MARGIN.right + OUTER_MARGIN.left + OUTER_MARGIN.right;
    }

    public static int getItemHeight() {
        return ITEM_HEIGHT + INNER_MARGIN.top + INNER_MARGIN.bottom + OUTER_MARGIN.top + OUTER_MARGIN.bottom;
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(getItemWidth(), getItemHeight());
    }

    private void renderOffscreen() {
        buffer = new BufferedImage(getItemWidth(), getItemHeight(), BufferedImage.TYPE_INT_ARGB);

        int left = OUTER_MARGIN.left;
        int top = OUTER_MARGIN.top;
        int width = ITEM_WIDTH + INNER_MARGIN.left + INNER_MARGIN.right;
        int height = ITEM_HEIGHT + INNER_MARGIN.top + INNER_MARGIN.bottom;

        Graphics2D g2 = (Graphics2D) buffer.createGraphics();
        g2.setRenderingHints(hints);

        g2.drawImage(getPhotoBackground(),
                OUTER_MARGIN.left - 3 - SHADOW_SIZE / 2, OUTER_MARGIN.top - 3,
                getPhotoBackground().getWidth() + 6, getPhotoBackground().getHeight() + 6, null);

        g2.fillRect(left, top, width, height);

        left += INNER_MARGIN.left;
        top += INNER_MARGIN.top;
        width = ITEM_WIDTH;
        height = (int) (width / ratio);

        g2.setClip(left, top, width, CLIP_HEIGHT);
        if (ratio < 1.0) {
            top -= (int) ((height - CLIP_HEIGHT) / 2.0);
        }

        if (System.getenv("nomipmap") == null) {
            g2.drawImage(Java2dHelper.createThumbnail(image, width), left, top, null);
        } else {
            g2.drawImage(image, left, top, width, height, null);
        }

        g2.dispose();
    }

    public BufferedImage getItemPicture() {
        if (buffer == null) {
            renderOffscreen();
        }

        return buffer;
    }

    @Override
    protected void paintComponent(Graphics g) {
        if (isVisible()) {
            Graphics2D g2 = (Graphics2D) g;
            g2.setRenderingHints(hints);

            g2.setColor(Color.WHITE);
            g2.fillRect(0, 0, getWidth(), getHeight());
            g2.drawImage(getItemPicture(), 0, 0, this);

            g2.setColor(Color.DARK_GRAY);
            g2.setFont(font);
            FontMetrics metrics = g2.getFontMetrics();

            g2.drawString(name,
                    OUTER_MARGIN.left + INNER_MARGIN.left,
                    getItemHeight() - OUTER_MARGIN.bottom - INNER_MARGIN.bottom - metrics.getDescent());
        }
    }

    private static BufferedImage getPhotoBackground() {
        if (shadowBuffer == null) {
            ShadowFactory factory = new ShadowFactory(SHADOW_SIZE, 0.17f, Color.BLACK);
            BufferedImage image = new BufferedImage(ITEM_WIDTH + INNER_MARGIN.left + INNER_MARGIN.right,
                    ITEM_HEIGHT + INNER_MARGIN.top + INNER_MARGIN.bottom,
                    BufferedImage.TYPE_INT_ARGB);
            Graphics2D g2 = image.createGraphics();
            g2.setColor(Color.WHITE);
            g2.fillRect(0, 0, image.getWidth(), image.getHeight());
            g2.dispose();

            shadowBuffer = factory.createShadow(image);
        }
        return shadowBuffer;
    }

    public void dragGestureRecognized(DragGestureEvent dge) {
        if (DragAndDropLock.isLocked()) {
            DragAndDropLock.setDragAndDropStarted(false);
            return;
        }
        DragAndDropLock.setLocked(true);
        DragAndDropLock.setDragAndDropStarted(true);

        dge.startDrag(Cursor.getDefaultCursor(),
                new CollageItemTransferHandler(getItemPicture(), image),
                this);

        GhostGlassPane glassPane = (GhostGlassPane) SwingUtilities.getRootPane(this).getGlassPane();
        glassPane.setVisible(true);

        Point p = (Point) dge.getDragOrigin().clone();
        SwingUtilities.convertPointToScreen(p, this);
        SwingUtilities.convertPointFromScreen(p, glassPane);

        glassPane.setPoint(p);
        glassPane.setImage(getItemPicture(),
                getItemPicture().getWidth());
        glassPane.repaint();
    }

    public void dragMouseMoved(DragSourceDragEvent dsde) {
        if (!DragAndDropLock.isDragAndDropStarted()) {
            return;
        }

        GhostGlassPane glassPane = (GhostGlassPane) SwingUtilities.getRootPane(this).getGlassPane();

        Point p = (Point) dsde.getLocation().clone();
        SwingUtilities.convertPointFromScreen(p, glassPane);
        glassPane.setPoint(p);

        glassPane.repaint(glassPane.getRepaintRect());
    }

    public void dragEnter(DragSourceDragEvent dsde) {
    }

    public void dragOver(DragSourceDragEvent dsde) {
    }

    public void dropActionChanged(DragSourceDragEvent dsde) {
    }

    public void dragExit(DragSourceEvent dse) {
    }

    public void dragDropEnd(DragSourceDropEvent dsde) {
        if (!DragAndDropLock.isDragAndDropStarted()) {
            return;
        }
        DragAndDropLock.setDragAndDropStarted(false);

        GhostGlassPane glassPane = (GhostGlassPane) SwingUtilities.getRootPane(this).getGlassPane();

        Point p = (Point) dsde.getLocation().clone();
        SwingUtilities.convertPointFromScreen(p, glassPane);

        if (!dsde.getDropSuccess()) {
            Point end = (Point) getLocation().clone();
            SwingUtilities.convertPointToScreen(end, this.getParent());
            SwingUtilities.convertPointFromScreen(end, glassPane);


            end.x += getWidth() / 2;
            end.y += getHeight() / 2;

            Timer backTimer = new Timer(1000 / 60, new TravelBackToOrigin(glassPane, p, end));
            backTimer.start();
        } else {
            glassPane.setPoint(p);
            glassPane.repaint(glassPane.getRepaintRect());
        }
    }

    // XXX: should use acceleration instead of time
    // XXX: travel path should be a curve
    private class TravelBackToOrigin implements ActionListener {
        private boolean isInitialized;
        private long start;

        private Point startPoint;
        private Point endPoint;
        private GhostGlassPane glassPane;

        private static final double INITIAL_SPEED = 500.0;
        private static final double INITIAL_ACCELERATION = 6000.0;

        private TravelBackToOrigin(GhostGlassPane glassPane, Point start, Point end) {
            this.glassPane = glassPane;
            this.startPoint = start;
            this.endPoint = end;
            isInitialized = false;
        }

        public void actionPerformed(ActionEvent e) {
            if (!isInitialized) {
                isInitialized = true;
                start = System.currentTimeMillis();
            }

            long elapsed = System.currentTimeMillis() - start;
            double time = (double) elapsed / 1000.0;

            double a = (endPoint.y - startPoint.y) / (double) (endPoint.x - startPoint.x);
            double b = endPoint.y - a * endPoint.x;

            int travelX = (int) (INITIAL_ACCELERATION * time * time / 2.0 + INITIAL_SPEED * time);
            if (startPoint.x > endPoint.x) {
                travelX = -travelX;
            }

            int travelY = (int) ((startPoint.x + travelX) * a + b);
            int distanceX = (int) Math.abs(startPoint.x - endPoint.x);

            if (Math.abs(travelX) >= distanceX) {
                ((Timer) e.getSource()).stop();

                glassPane.setPoint(endPoint);
                glassPane.repaint(glassPane.getRepaintRect());

                SwingUtilities.invokeLater(new Runnable() {
                    public void run() {
                        glassPane.setImage(null);
                        glassPane.setVisible(false);
                    }
                });
                DragAndDropLock.setLocked(false);

                return;
            }

            glassPane.setPoint(new Point(startPoint.x + travelX,
                    travelY));

            glassPane.repaint(glassPane.getRepaintRect());
        }
    }
}

class PhotoItemsPanel extends JPanel implements Scrollable {
    public PhotoItemsPanel(LayoutManager layout) {
        super(layout);
    }

    public Dimension getPreferredScrollableViewportSize() {
        return null;
    }

    public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
        return PhotoItem.getItemHeight();
    }

    public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
        return PhotoItem.getItemHeight();
    }

    public boolean getScrollableTracksViewportWidth() {
        return false;
    }

    public boolean getScrollableTracksViewportHeight() {
        return false;
    }
}

/**
 * <p>A shadow factory generates a drop shadow for any given picture, respecting
 * the transparency channel if present. The resulting picture contains the
 * shadow only and to create a drop shadow effect you will need to stack the
 * original picture and the shadow generated by the factory. If you are using
 * Swing you can get this done very easily with the layout
 * {@link org.jdesktop.swingx.StackLayout}.</p>
 * <h2>Shadow Properties</h2>
 * <p>A shadow is defined by three properties:
 * <ul>
 *   <li><i>size</i>: The size, in pixels, of the shadow. This property also
 *   defines the fuzzyness.</li>
 *   <li><i>opacity</i>: The opacity, between 0.0 and 1.0, of the shadow.</li>
 *   <li><i>color</i>: The color of the shadow. Shadows are not meant to be
 *   black only.</li>
 * </ul>
 * You can set these properties using the provided mutaters or the appropriate
 * constructor. Here are two ways of creating a green shadow of size 10 and
 * with an opacity of 50%:
 * <pre>
 * ShadowFactory factory = new ShadowFactory(10, 0.5f, Color.GREEN);
 * // ..
 * factory = new ShadowFactory();
 * factory.setSize(10);
 * factory.setOpacity(0.5f);
 * factory.setColor(Color.GREEN);
 * </pre>
 * The default constructor provides the following default values:
 * <ul>
 *   <li><i>size</i>: 5 pixels</li>
 *   <li><i>opacity</i>: 50%</li>
 *   <li><i>color</i>: Black</li>
 * </ul></p>
 * <h2>Shadow Quality</h2>
 * <p>The factory provides two shadow generation algorithms: <i>fast quality blur</i>
 * and <i>high quality blur</i>. You can select your preferred algorithm by
 * setting the appropriate rendering hint:
 * <pre>
 * ShadowFactory factory = new ShadowFactory();
 * factory.setRenderingHint(ShadowFactory.KEY_BLUR_QUALITY,
 *                          ShadowFactory.VALUE_BLUR_QUALITY_HIGH);
 * </pre>
 * The default rendering algorihtm is <code>VALUE_BLUR_QUALITY_FAST</code>.</p>
 * <p>The current implementation should provide the same quality with both
 * algorithms but performances are guaranteed to be better (about 30 times
 * faster) with the <i>fast quality blur</i>.</p>
 * <h2>Generating a Shadow</h2>
 * <p>A shadow is generated as a <code>BufferedImage</code> from another
 * <code>BufferedImage</code>. Once the factory is set up, you must call
 * {@link #createShadow} to actually generate the shadow:
 * <pre>
 * ShadowFactory factory = new ShadowFactory();
 * // factory setup
 * BufferedImage shadow = factory.createShadow(bufferedImage);
 * </pre>
 * The resulting image is of type <code>BufferedImage.TYPE_INT_ARGB</code>.
 * Both dimensions of this image are larger than original image's:
 * <ul>
 *   <li>new width = original width + 2 * shadow size</li>
 *   <li>new height = original height + 2 * shadow size</li>
 * </ul>
 * This must be taken into account when you need to create a drop shadow effect.</p>
 * <h2>Properties Changes</h2>
 * <p>This factory allows to register property change listeners with
 * {@link #addPropertyChangeListener}. Listening to properties changes is very
 * useful when you emebed the factory in a graphical component and give the API
 * user the ability to access the factory. By listening to properties changes,
 * you can easily repaint the component when needed.</p>
 * <h2>Threading Issues</h2>
 * <p><code>ShadowFactory</code> is not guaranteed to be thread-safe.</p>
 *
 * @author Romain Guy <romain.guy@mac.com>
 * @author Sébastien Petrucci <sebastien_petrucci@yahoo.fr>
 */

class ShadowFactory {
    /**
     * <p>Key for the blur quality rendering hint.</p>
     */
    public static final String KEY_BLUR_QUALITY = "blur_quality";

    /**
     * <p>Selects the fast rendering algorithm. This is the default rendering
     * hint for <code>KEY_BLUR_QUALITY</code>.</p>
     */
    public static final String VALUE_BLUR_QUALITY_FAST = "fast";

    /**
     * <p>Selects the high quality rendering algorithm. With current implementation,
     * This algorithm does not guarantee a better rendering quality and should
     * not be used.</p>
     */
    public static final String VALUE_BLUR_QUALITY_HIGH = "high";

    /**
     * <p>Identifies a change to the size used to render the shadow.</p>
     * <p>When the property change event is fired, the old value and the new
     * value are provided as <code>Integer</code> instances.</p>
     */
    public static final String SIZE_CHANGED_PROPERTY = "shadow_size";

    /**
     * <p>Identifies a change to the opacity used to render the shadow.</p>
     * <p>When the property change event is fired, the old value and the new
     * value are provided as <code>Float</code> instances.</p>
     */
    public static final String OPACITY_CHANGED_PROPERTY = "shadow_opacity";

    /**
     * <p>Identifies a change to the color used to render the shadow.</p>
     */
    public static final String COLOR_CHANGED_PROPERTY = "shadow_color";

    // size of the shadow in pixels (defines the fuzziness)
    private int size = 5;

    // opacity of the shadow
    private float opacity = 0.5f;

    // color of the shadow
    private Color color = Color.BLACK;

    // rendering hints map
    private HashMap<Object, Object> hints;

    // notifies listeners of properties changes
    private PropertyChangeSupport changeSupport;

    /**
     * <p>Creates a default good looking shadow generator.
     * The default shadow factory provides the following default values:
     * <ul>
     *   <li><i>size</i>: 5 pixels</li>
     *   <li><i>opacity</i>: 50%</li>
     *   <li><i>color</i>: Black</li>
     *   <li><i>rendering quality</i>: VALUE_BLUR_QUALITY_FAST</li>
     * </ul></p>
     * <p>These properties provide a regular, good looking shadow.</p>
     */
    public ShadowFactory() {
        this(5, 0.5f, Color.BLACK);
    }

    /**
     * <p>A shadow factory needs three properties to generate shadows.
     * These properties are:</p>
     * <ul>
     *   <li><i>size</i>: The size, in pixels, of the shadow. This property also
     *   defines the fuzzyness.</li>
     *   <li><i>opacity</i>: The opacity, between 0.0 and 1.0, of the shadow.</li>
     *   <li><i>color</i>: The color of the shadow. Shadows are not meant to be
     *   black only.</li>
     * </ul></p>
     * <p>Besides these properties you can set rendering hints to control the
     * rendering process. The default rendering hints let the factory use the
     * fastest shadow generation algorithm.</p>
     *  [MENTION=9956]PARAM[/MENTION] size The size of the shadow in pixels. Defines the fuzziness.
     *  [MENTION=9956]PARAM[/MENTION] opacity The opacity of the shadow.
     *  [MENTION=9956]PARAM[/MENTION] color The color of the shadow.
     *  [MENTION=288550]see[/MENTION] #setRenderingHint(Object, Object)
     */
    public ShadowFactory(final int size, final float opacity, final Color color) {
        hints = new HashMap<Object, Object>();
        hints.put(KEY_BLUR_QUALITY, VALUE_BLUR_QUALITY_FAST);

        changeSupport = new PropertyChangeSupport(this);

        setSize(size);
        setOpacity(opacity);
        setColor(color);
    }

    /**
     * <p>Add a PropertyChangeListener to the listener list. The listener is
     * registered for all properties. The same listener object may be added
     * more than once, and will be called as many times as it is added. If
     * <code>listener</code> is null, no exception is thrown and no action
     * is taken.</p>
     *  [MENTION=9956]PARAM[/MENTION] listener the PropertyChangeListener to be added
     */
    public void addPropertyChangeListener(PropertyChangeListener listener) {
        changeSupport.addPropertyChangeListener(listener);
    }

    /**
     * <p>Remove a PropertyChangeListener from the listener list. This removes
     * a PropertyChangeListener that was registered for all properties. If
     * <code>listener</code> was added more than once to the same event source,
     * it will be notified one less time after being removed. If
     * <code>listener</code> is null, or was never added, no exception is thrown
     * and no action is taken.</p>
     *  [MENTION=9956]PARAM[/MENTION] listener
     */
    public void removePropertyChangeListener(PropertyChangeListener listener) {
        changeSupport.removePropertyChangeListener(listener);
    }

    /**
     * <p>Maps the specified rendering hint <code>key</code> to the specified
     * <code>value</code> in this <code>SahdowFactory</code> object.</p>
     *  [MENTION=9956]PARAM[/MENTION] key The rendering hint key
     *  [MENTION=9956]PARAM[/MENTION] value The rendering hint value
     */
    public void setRenderingHint(final Object key, final Object value) {
        hints.put(key, value);
    }

    /**
     * <p>Gets the color used by the factory to generate shadows.</p>
     * @return this factory's shadow color
     */
    public Color getColor() {
        return color;
    }

    /**
     * <p>Sets the color used by the factory to generate shadows.</p>
     * <p>Consecutive calls to {@link #createShadow} will all use this color
     * until it is set again.</p>
     * <p>If the color provided is null, the previous color will be retained.</p>
     *  [MENTION=9956]PARAM[/MENTION] shadowColor the generated shadows color
     */
    public void setColor(final Color shadowColor) {
        if (shadowColor != null) {
            Color oldColor = this.color;
            this.color = shadowColor;
            changeSupport.firePropertyChange(COLOR_CHANGED_PROPERTY,
                    oldColor,
                    this.color);
        }
    }

    /**
     * <p>Gets the opacity used by the factory to generate shadows.</p>
     * <p>The opacity is comprised between 0.0f and 1.0f; 0.0f being fully
     * transparent and 1.0f fully opaque.</p>
     * @return this factory's shadow opacity
     */
    public float getOpacity() {
        return opacity;
    }

    /**
     * <p>Sets the opacity used by the factory to generate shadows.</p>
     * <p>Consecutive calls to {@link #createShadow} will all use this color
     * until it is set again.</p>
     * <p>The opacity is comprised between 0.0f and 1.0f; 0.0f being fully
     * transparent and 1.0f fully opaque. If you provide a value out of these
     * boundaries, it will be restrained to the closest boundary.</p>
     *  [MENTION=9956]PARAM[/MENTION] shadowOpacity the generated shadows opacity
     */
    public void setOpacity(final float shadowOpacity) {
        float oldOpacity = this.opacity;

        if (shadowOpacity < 0.0) {
            this.opacity = 0.0f;
        } else if (shadowOpacity > 1.0f) {
            this.opacity = 1.0f;
        } else {
            this.opacity = shadowOpacity;
        }

        changeSupport.firePropertyChange(OPACITY_CHANGED_PROPERTY,
                new Float(oldOpacity),
                new Float(this.opacity));
    }

    /**
     * <p>Gets the size in pixel used by the factory to generate shadows.</p>
     * @return this factory's shadow size
     */
    public int getSize() {
        return size;
    }

    /**
     * <p>Sets the size, in pixels, used by the factory to generate shadows.</p>
     * <p>The size defines the blur radius applied to the shadow to create the
     * fuzziness.</p>
     * <p>There is virtually no limit to the size but it has an impact on shadow
     * generation performances. The greater this value, the longer it will take
     * to generate the shadow. Remember the generated shadow image dimensions
     * are computed as follow:
     * <ul>
     *   <li>new width = original width + 2 * shadow size</li>
     *   <li>new height = original height + 2 * shadow size</li>
     * </ul>
     * The size cannot be negative. If you provide a negative value, the size
     * will be 0 instead.</p>
     *  [MENTION=9956]PARAM[/MENTION] shadowSize the generated shadows size in pixels (fuzziness)
     */
    public void setSize(final int shadowSize) {
        int oldSize = this.size;

        if (shadowSize < 0) {
            this.size = 0;
        } else {
            this.size = shadowSize;
        }

        changeSupport.firePropertyChange(SIZE_CHANGED_PROPERTY,
                new Integer(oldSize),
                new Integer(this.size));
    }

    /**
     * <p>Generates the shadow for a given picture and the current properties
     * of the factory.</p>
     * <p>The generated shadow image dimensions are computed as follow:
     *  <ul>
     *  <li>new width = original width + 2 * shadow size</li>
     *  <li>new height = original height + 2 * shadow size</li>
     * </ul></p>
     * <p>The time taken by a call to this method depends on the size of the
     * shadow, the larger the longer it takes, and on the selected rendering
     * algorithm.</p>
     *  [MENTION=9956]PARAM[/MENTION] image the picture from which the shadow must be cast
     * @return the picture containing the shadow of <code>image</code>
     */
    public BufferedImage createShadow(final BufferedImage image) {
        if (hints.get(KEY_BLUR_QUALITY) == VALUE_BLUR_QUALITY_HIGH) {
            // the high quality algorithm is a 3-pass algorithm
            // it goes through all the pixels of the original picture at least
            // three times to generate the shadow
            // it is easy to understand but very slow
            BufferedImage subject = prepareImage(image);
            BufferedImage shadow = new BufferedImage(subject.getWidth(),
                    subject.getHeight(),
                    BufferedImage.TYPE_INT_ARGB);
            BufferedImage shadowMask = createShadowMask(subject);
            getLinearBlurOp(size).filter(shadowMask, shadow);
            return shadow;
        }

        // call the fast rendering algorithm
        return createShadowFast(image);
    }

    // prepares the picture for the high quality rendering algorithm
    private BufferedImage prepareImage(final BufferedImage image) {
        BufferedImage subject = new BufferedImage(image.getWidth() + size * 2,
                image.getHeight() + size * 2,
                BufferedImage.TYPE_INT_ARGB);

        Graphics2D g2 = subject.createGraphics();
        g2.drawImage(image, null, size, size);
        g2.dispose();

        return subject;
    }

    // fast rendering algorithm
    // basically applies duplicates the picture and applies a size*size kernel
    // in only one pass.
    // the kernel is simulated by an horizontal and a vertical pass
    // implemented by Sébastien Petrucci
    private BufferedImage createShadowFast(final BufferedImage src) {
        int shadowSize = this.size;

        int srcWidth = src.getWidth();
        int srcHeight = src.getHeight();

        int dstWidth = srcWidth + size;
        int dstHeight = srcHeight + size;

        int left = (shadowSize - 1) >> 1;
        int right = shadowSize - left;

        int yStop = dstHeight - right;

        BufferedImage dst = new BufferedImage(dstWidth, dstHeight,
                BufferedImage.TYPE_INT_ARGB);

        int shadowRgb = color.getRGB() & 0x00FFFFFF;

        int[] aHistory = new int[shadowSize];
        int historyIdx;

        int aSum;

        ColorModel srcColorModel = src.getColorModel();
        WritableRaster srcRaster = src.getRaster();
        int[] dstBuffer = ((DataBufferInt) dst.getRaster().getDataBuffer()).getData();

        int lastPixelOffset = right * dstWidth;
        float hSumDivider = 1.0f / size;
        float vSumDivider = opacity / size;

        // horizontal pass : extract the alpha mask from the source picture and
        // blur it into the destination picture
        for (int srcY = 0, dstOffset = left * dstWidth; srcY < srcHeight; srcY++) {

            // first pixels are empty
            for (historyIdx = 0; historyIdx < shadowSize; ) {
                aHistory[historyIdx++] = 0;
            }

            aSum = 0;
            historyIdx = 0;

            // compute the blur average with pixels from the source image
            for (int srcX = 0; srcX < srcWidth; srcX++) {

                int a = (int) (aSum * hSumDivider); // calculate alpha value
                dstBuffer[dstOffset++] = a << 24;   // store the alpha value only
                // the shadow color will be added in the next pass

                aSum -= aHistory[historyIdx]; // substract the oldest pixel from the sum

                // extract the new pixel ...
                a = srcColorModel.getAlpha(srcRaster.getDataElements(srcX, srcY, null));
                aHistory[historyIdx] = a;   // ... and store its value into history
                aSum += a;                  // ... and add its value to the sum

                if (++historyIdx >= shadowSize) {
                    historyIdx -= shadowSize;
                }
            }

            // blur the end of the row - no new pixels to grab
            for (int i = 0; i < shadowSize; i++) {

                int a = (int) (aSum * hSumDivider);
                dstBuffer[dstOffset++] = a << 24;

                // substract the oldest pixel from the sum ... and nothing new to add !
                aSum -= aHistory[historyIdx];

                if (++historyIdx >= shadowSize) {
                    historyIdx -= shadowSize;
                }
            }
        }

        // vertical pass
        for (int x = 0, bufferOffset = 0; x < dstWidth; x++, bufferOffset = x) {

            aSum = 0;

            // first pixels are empty
            for (historyIdx = 0; historyIdx < left;) {
                aHistory[historyIdx++] = 0;
            }

            // and then they come from the dstBuffer
            for (int y = 0; y < right; y++, bufferOffset += dstWidth) {
                int a = dstBuffer[bufferOffset] >>> 24;         // extract alpha
                aHistory[historyIdx++] = a;                     // store into history
                aSum += a;                                      // and add to sum
            }

            bufferOffset = x;
            historyIdx = 0;

            // compute the blur average with pixels from the previous pass
            for (int y = 0; y < yStop; y++, bufferOffset += dstWidth) {

                int a = (int) (aSum * vSumDivider);             // calculate alpha value
                dstBuffer[bufferOffset] = a << 24 | shadowRgb;  // store alpha value + shadow color

                aSum -= aHistory[historyIdx];   // substract the oldest pixel from the sum

                a = dstBuffer[bufferOffset + lastPixelOffset] >>> 24;   // extract the new pixel ...
                aHistory[historyIdx] = a;                               // ... and store its value into history
                aSum += a;                                              // ... and add its value to the sum

                if (++historyIdx >= shadowSize) {
                    historyIdx -= shadowSize;
                }
            }

            // blur the end of the column - no pixels to grab anymore
            for (int y = yStop; y < dstHeight; y++, bufferOffset += dstWidth) {

                int a = (int) (aSum * vSumDivider);
                dstBuffer[bufferOffset] = a << 24 | shadowRgb;

                aSum -= aHistory[historyIdx];   // substract the oldest pixel from the sum

                if (++historyIdx >= shadowSize) {
                    historyIdx -= shadowSize;
                }
            }
        }

        return dst;
    }

    // creates the shadow mask for the original picture
    // it colorize all the pixels with the shadow color according to their
    // original transparency
    private BufferedImage createShadowMask(final BufferedImage image) {
        BufferedImage mask = new BufferedImage(image.getWidth(),
                image.getHeight(),
                BufferedImage.TYPE_INT_ARGB);

        Graphics2D g2d = mask.createGraphics();
        g2d.drawImage(image, 0, 0, null);
        g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_IN,
                opacity));
        g2d.setColor(color);
        g2d.fillRect(0, 0, image.getWidth(), image.getHeight());
        g2d.dispose();

        return mask;
    }

    // creates a blur convolve operation by generating a kernel of
    // dimensions (size, size).
    private ConvolveOp getLinearBlurOp(final int size) {
        float[] data = new float[size * size];
        float value = 1.0f / (float) (size * size);
        for (int i = 0; i < data.length; i++) {
            data[i] = value;
        }
        return new ConvolveOp(new Kernel(size, size, data));
    }
}

*s3.postimg.org/5ppnya4xr/Collage_Demo.png

*s3.postimg.org/wk8kyo873/Collage_Demo2.png



2) Write a CrossWord Applet in action!!

A simple crossword applet in action!!
Code:
//-------------------------------------------------------
//
// Written by Carl Haynes
//
// Submitted to Sun Microsystems for their
// java.applet.Applet Contest 8/31/95
//
//--------------------------------------------------------
import java.awt.*;

public class crossword extends java.applet.Applet {

    final static int kAppWidth = 500;
    final static int kAppHeight = 550;

    final static int kBlockWidth = 30;
    final static int kBlockHeight = 30;

    final static int kBlocksWide = 15;
    final static int kBlocksHigh = 15;
    final static int kButtonWidth = 100;
    final static int kButtonHeight = 25;

    final static int kUp = 0;
    final static int kDown = 1;

    final static int kAcross = 0;

    final static int kPadding = 20;
    final static int kQuestionAreaHeight = 40;

    int gDirection = kAcross;
    int gCurX = 0;
    int gCurY = 0;

    int clipLeft = -1;
    int clipTop = -1;
    int clipWidth = -1;
    int clipHeight = -1;

    int gBlockMinY = 0;
    int gBlockMaxY = 0;
    int gBlockMinX = 0;
    int gBlockMaxX = 0;

    int gOldBlockMinY = 0;
    int gOldBlockMaxY = 0;
    int gOldBlockMinX = 0;
    int gOldBlockMaxX = 0;

    String letters[] = {"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
        "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"};

    /*---------------------------------------------------------------*/
    /*---------------------------------------------------------------*/
    final static int layout[][] = {
        {1, 2, 3, -1, -1, 4, 5, 6, 7, -1, -1, 8, 9, 10, -1},
        {11, 00, 00, 12, -1, 13, 00, 00, 00, -1, 14, 00, 00, 00, 15},
        {16, 00, 00, 00, -1, 17, 00, 00, 00, -1, 18, 00, 00, 00, 00},
        {-1, 19, 00, 00, 20, 00, -1, 21, 00, 22, 00, -1, 23, 00, 00},
        {-1, 24, 00, 00, 00, 00, -1, -1, -1, 25, 00, 26, -1, -1, -1},
        {-1, -1, -1, -1, 27, 00, 28, 29, -1, 30, 00, 00, 31, 32, -1},
        {33, 34, 35, 36, 00, -1, 37, 00, 38, 00, -1, 39, 00, 00, 40},
        {41, 00, 00, 00, -1, -1, 42, 00, 00, -1, -1, 43, 00, 00, 00},
        {44, 00, 00, 00, -1, 45, 00, 00, 00, -1, 46, 00, 00, 00, 00},
        {-1, 47, 00, 00, 48, 00, -1, 49, 00, 50, 00, -1, -1, -1, -1},
        {-1, -1, -1, 51, 00, 00, -1, -1, -1, 52, 00, 53, 54, 55, -1},
        {56, 57, 58, -1, 59, 00, 60, 61, -1, 62, 00, 00, 00, 00, -1},
        {63, 00, 00, 64, 00, -1, 65, 00, 66, 00, -1, 67, 00, 00, 68},
        {69, 00, 00, 00, 00, -1, 70, 00, 00, 00, -1, 71, 00, 00, 00},
        {-1, 72, 00, 00, -1, -1, 73, 00, 00, 00, -1, -1, 74, 00, 00}
    };

    final static String answers[][] = {
        {"G", "A", "D", " ", " ", "C", "R", "A", "B", " ", " ", "B", "A", "D", " "},
        {"A", "B", "E", "D", " ", "A", "U", "R", "A", " ", "B", "A", "B", "E", "L"},
        {"G", "A", "M", "E", " ", "G", "E", "A", "R", " ", "A", "R", "E", "N", "A"},
        {" ", "C", "O", "V", "E", "N", " ", "B", "E", "E", "N", " ", "L", "E", "G"},
        {" ", "A", "S", "I", "D", "E", " ", " ", " ", "G", "A", "B", " ", " ", " "},
        {" ", " ", " ", " ", "D", "Y", "A", "D", " ", "A", "L", "L", "A", "H", " "},
        {"C", "A", "C", "A", "O", " ", "B", "E", "A", "D", " ", "A", "D", "A", "M"},
        {"A", "M", "O", "K", " ", " ", "B", "I", "G", " ", " ", "D", "I", "K", "E"},
        {"B", "I", "D", "E", " ", "D", "A", "C", "E", " ", "B", "E", "T", "E", "L"},
        {" ", "D", "E", "L", "A", "Y", " ", "E", "S", "A", "U", " ", " ", " ", " "},
        {" ", " ", " ", "A", "R", "K", " ", " ", " ", "B", "R", "E", "A", "D", " "},
        {"J", "A", "G", " ", "D", "E", "A", "F", " ", "A", "P", "P", "L", "E", " "},
        {"A", "B", "O", "V", "O", " ", "B", "L", "O", "C", " ", "E", "L", "L", "S"},
        {"B", "U", "Y", "E", "R", " ", "B", "E", "A", "U", " ", "E", "A", "V", "E"},
        {" ", "T", "A", "T", " ", " ", "E", "A", "R", "S", " ", " ", "Y", "E", "A"}
    };

    final static String gQuestionsAcross[] = {
        "", //  0
        "Goad for driving cattle", // 1
        "", // 2
        "", // 3
        "Crustacean", // 4
        "", // 5
        "", // 6
        "", // 7
        "Not good", // 8
        "", // 9
        "", // 10
        "In bed", // 11
        "", // 12
        "Distinctive quality", // 13
        "Confused mixture of sounds", // 14
        "", // 15
        "Amusement", // 16
        "Equipment", // 17
        "Area used for sports", // 18
        "Assembly of witches", // 19
        "", // 20
        "Part of verb to be", // 21
        "", // 22
        "Lower limb", // 23
        "To one side", // 24
        "Chatter", // 25
        "", // 26
        "Group of two", // 27
        "", // 28
        "", // 29
        "Supreme Being", // 30
        "", // 31
        "", // 32
        "Source of cocoa", // 33
        "", // 34
        "", // 35
        "", // 36
        "Glass ornament", // 37
        "", // 38
        "First man", // 39
        "", // 40
        "Diventare pazzo", // 41
        "Large", // 42
        "Embankment", // 43
        "Remain", // 44
        "Fresh-water fish", // 45
        "East Indian pepper plant", // 46
        "Postpone", // 47
        "", // 48
        "Son of Isaac and Rebekah", // 49
        "", // 50
        "Vessel built by Noah", // 51
        "Baked dough", // 52
        "", // 53
        "", // 54
        "", // 55
        "Sharp projection", // 56
        "", // 57
        "", // 58
        "Deprived of the sense of hearing", // 59
        "", // 60
        "", // 61
        "Edible fruit", // 62
        "From the beginning", // 63
        "", // 64
        "Cartel", // 65
        "", // 66
        "Old cloth measures", // 67
        "", // 68
        "Purchaser", // 69
        "Sweetheart", // 70
        "Overhanging lower edge of a roof", // 71
        "Make lace", // 72
        "Hearing organs", // 73
        "Affirmative reply" // 74
    };

    final static String gQuestionsDown[] = {
        "", //  0
        "Bavaglio", // 1
        "Manila hemp plant", // 2
        "The common people", // 3
        "U.S. film actor", // 4
        "Regret", // 5
        "Bedouin", // 6
        "Uncover", // 7
        "Ingot", // 8
        "Second son of Adam and Eve", // 9
        "Sandy tract", // 10
        "", // 11
        "Hindu mother goddess", // 12
        "", // 13
        "Trite", // 14
        "Fall behind", // 15
        "", // 16
        "", // 17
        "", // 18
        "", // 19
        "Root of the taro", // 20
        "", // 21
        "Mild oath", // 22
        "", // 23
        "", // 24
        "", // 25
        "Part of an ice skate", // 26
        "", // 27
        "Title of respect for God", // 28
        "Free of ice", // 29
        "", // 30
        "Entrance", // 31
        "Codlike fish", // 32
        "Taxicab", // 33
        "Among", // 34
        "Message symbols", // 35
        "Pack leader", // 36
        "", // 37
        "Matures", // 38
        "", // 39
        "Honey", // 40
        "", // 41
        "", // 42
        "", // 43
        "", // 44
        "Female homosexual", // 45
        "Relieve gas", // 46
        "", // 47
        "Zeal", // 48
        "", // 49
        "Calculating device", // 50
        "", // 51
        "", // 52
        "Fencing sword", // 53
        "Alleviate", // 54
        "Research deeply", // 55
        "Punch", // 56
        "Adjoin", // 57
        "Spanish painter", // 58
        "", // 59
        "French clergyman", // 60
        "Bloodsucking insect", // 61
        "", // 62
        "", // 63
        "Veterinarian", // 64
        "", // 65
        "Lever for rowing", // 66
        "", // 67
        "Ocean" // 68
    };

    /*---------------------------------------------------------------*/
    String gGuesses[][] = new String[kBlocksWide][kBlocksHigh];

    boolean gUpdateActiveAreaFlag = false;
    boolean gChangedActiveAreaFlag = false;

    Font buttonFont = null;
    Font tileFont = null;

    public void init() {
        int viewWidth;
        int left, top;

        resize((kBlocksWide * kBlockWidth) + (kPadding * 2), (kBlocksHigh * kBlockHeight) + (kPadding * 3) + kQuestionAreaHeight);

        NewGame();

        buttonFont = new java.awt.Font("Courier", Font.PLAIN, 12);
        tileFont = new java.awt.Font("Helvetica", Font.PLAIN, 36);

    }

    /*---------------------------------------------------------------*/
    public void NewGame() {
        for (int j = 0; j < kBlocksHigh; j++) {
            for (int i = 0; i < kBlocksWide; i++) {
                gGuesses[i][j] = "";
            }
        }

        gOldBlockMinY = 0;
        gOldBlockMaxY = 0;
        gOldBlockMinX = 0;
        gOldBlockMaxX = 0;

        gDirection = kAcross;
        gCurX = 0;
        gCurY = 0;
        SetActiveBlock(gCurX, gCurY, gDirection);

    }

    /*----------------------------------------------*/
    public void paint(Graphics g) {
        int left = 0;
        int right = kAppWidth - 1;
        int top = 0;
        int bottom = kAppHeight - 1;
        int tempLeft = 0;
        int tempRight = 0;
        int tempTop = 0;
        int viewWidth;
        int viewHeight;
        int buttonWidth = 0;
        int buttonHeight = 0;
        int buttonLeft = 0;
        int buttonTop = 0;
        int tileLeft;
        int tileTop;

        Font f = new java.awt.Font("Helvetica", 0, 12);
        g.setFont(f);

        Font numFont = new java.awt.Font("Helvetica", 0, 10);

        Font answerFont = new java.awt.Font("Helvetica", 0, 18);
        Font questionFont = new java.awt.Font("Courier", 0, 24);
        Font questionFont18 = new java.awt.Font("Courier", 0, 18);

        FontMetrics answerFontMetrics = g.getFontMetrics(answerFont);
        FontMetrics questionFontMetrics = g.getFontMetrics(questionFont);
        FontMetrics questionFont18Metrics = g.getFontMetrics(questionFont18);

        g.setColor(Color.lightGray);
        g.draw3DRect(0, 0, size().width - 1, size().height - 1, true);

        viewWidth = kBlocksWide * kBlockWidth;
        viewHeight = kBlocksHigh * kBlockHeight;

        top = kPadding;
        left = (size().width / 2) - (viewWidth / 2);
        g.setColor(Color.white);
        g.fill3DRect(left, top, viewWidth, kQuestionAreaHeight, false);

        g.setFont(f);
        String s = new String(String.valueOf(layout[gBlockMinY][gBlockMinX]));
        s = s.concat(" - ");
        if (gDirection == kAcross) {
            s = s.concat("across");
        } else {
            s = s.concat("down");
        }
        g.drawString(s, left + 5, top + 12);
        g.setFont(questionFont);
        if (gDirection == kAcross) {
            Font userFont = questionFont;

            if (questionFontMetrics.stringWidth(gQuestionsAcross[layout[gBlockMinY][gBlockMinX]]) > viewWidth - 4) {
                userFont = questionFont18;
                g.setFont(questionFont18);
            }

            g.drawString(gQuestionsAcross[layout[gBlockMinY][gBlockMinX]],
                    (size().width / 2) - (g.getFontMetrics().stringWidth(gQuestionsAcross[layout[gBlockMinY][gBlockMinX]]) / 2),
                    (top + kQuestionAreaHeight) - 8);

        } else {
            Font userFont = questionFont;

            if (questionFontMetrics.stringWidth(gQuestionsDown[layout[gBlockMinY][gBlockMinX]]) > viewWidth - 4) {
                userFont = questionFont18;
                g.setFont(questionFont18);
            }

            g.drawString(gQuestionsDown[layout[gBlockMinY][gBlockMinX]],
                    (size().width / 2) - (g.getFontMetrics().stringWidth(gQuestionsDown[layout[gBlockMinY][gBlockMinX]]) / 2),
                    (top + kQuestionAreaHeight) - 8);
        }

        left = (size().width / 2) - (viewWidth / 2);
        top = (kPadding * 2) + kQuestionAreaHeight;

        for (int j = 0; j < kBlocksHigh; j++) {
            for (int i = 0; i < kBlocksWide; i++) {
                tempLeft = left + (i * kBlockWidth);
                tempTop = top + (j * kBlockHeight);

                if (InActiveBlock(i, j)) {
                    if (i == gCurX && j == gCurY) {
                        g.setColor(Color.cyan);
                    } else {
                        g.setColor(Color.yellow);
                    }
                    g.fillRect(tempLeft, tempTop, kBlockWidth, kBlockHeight);
                } else {
                    g.setColor(Color.white);
                    g.fillRect(tempLeft, tempTop, kBlockWidth, kBlockHeight);
                }

                g.setColor(Color.black);
                g.drawRect(tempLeft, tempTop, kBlockWidth, kBlockHeight);

                if (layout[j][i] == -1) {
                    g.setColor(Color.black);
                    g.fillRect(tempLeft, tempTop, kBlockWidth, kBlockHeight);
                } else if (layout[j][i] != 0) {
                    String numStr = String.valueOf(layout[j][i]);

                    g.setFont(numFont);
                    g.drawString(numStr, tempLeft + 4, tempTop + 10);

                }

                // -- put in text if needed
                if (layout[j][i] != -1) {
                    if (gGuesses[i][j].length() != 0) {
                        int sWidth = 0;

                        if (gGuesses[i][j].equalsIgnoreCase(answers[j][i]) == false) {
                            g.setColor(Color.red);
                        } else {
                            g.setColor(Color.black);
                        }

                        sWidth = answerFontMetrics.stringWidth(gGuesses[i][j]);
                        g.setFont(answerFont);
                        g.drawString(gGuesses[i][j], tempLeft + ((kBlockWidth / 2) - (sWidth / 2)), (tempTop + kBlockHeight) - 6);
                    }

                }

            }
        }
    }

    /*----------------------------------------------*/
    void PaintWord(Graphics g, int minX, int maxX, int minY, int maxY) {
        int viewWidth = kBlocksWide * kBlockWidth;
        int viewHeight = kBlocksHigh * kBlockHeight;
        int left = (size().width / 2) - (viewWidth / 2);
        int top = (kPadding * 2) + kQuestionAreaHeight;
        int tempLeft = 0;
        int tempRight = 0;
        int tempTop = 0;

        left += (minX * kBlockWidth);
        top += (minY * kBlockHeight);
        /*
         g.clipRect(	left, top,
         (kBlockWidth * (maxX - minX)) + kBlockWidth,
         (kBlockHeight * (maxY - minY)) + kBlockHeight);
         */

        Font f = new java.awt.Font("Helvetica", 0, 12);
        g.setFont(f);

        Font numFont = new java.awt.Font("Helvetica", 0, 10);

        Font answerFont = new java.awt.Font("Helvetica", 0, 18);
        FontMetrics answerFontMetrics = g.getFontMetrics(answerFont);

        viewWidth = kBlocksWide * kBlockWidth;
        viewHeight = kBlocksHigh * kBlockHeight;

        left = (size().width / 2) - (viewWidth / 2);
        top = (kPadding * 2) + kQuestionAreaHeight;

        for (int j = minY; j <= maxY; j++) {
            for (int i = minX; i <= maxX; i++) {
                tempLeft = left + (i * kBlockWidth);
                tempTop = top + (j * kBlockHeight);

                if (InActiveBlock(i, j)) {
                    if (i == gCurX && j == gCurY) {
                        g.setColor(Color.cyan);
                    } else {
                        g.setColor(Color.yellow);
                    }
                    g.fillRect(tempLeft, tempTop, kBlockWidth, kBlockHeight);
                } else {
                    g.setColor(Color.white);
                    g.fillRect(tempLeft, tempTop, kBlockWidth, kBlockHeight);
                }

                g.setColor(Color.black);
                g.drawRect(tempLeft, tempTop, kBlockWidth, kBlockHeight);

                if (layout[j][i] == -1) {
                    g.setColor(Color.black);
                    g.fillRect(tempLeft, tempTop, kBlockWidth, kBlockHeight);
                } else if (layout[j][i] != 0) {
                    String numStr = String.valueOf(layout[j][i]);

                    g.setFont(numFont);
                    g.drawString(numStr, tempLeft + 4, tempTop + 10);

                }

                // -- put in text if needed
                if (layout[j][i] != -1) {
                    if (gGuesses[i][j].length() != 0) {
                        int sWidth = 0;

                        if (gGuesses[i][j].equalsIgnoreCase(answers[j][i]) == false) {
                            g.setColor(Color.red);
                        } else {
                            g.setColor(Color.black);
                        }

                        sWidth = answerFontMetrics.stringWidth(gGuesses[i][j]);
                        g.setFont(answerFont);
                        g.drawString(gGuesses[i][j], tempLeft + ((kBlockWidth / 2) - (sWidth / 2)), (tempTop + kBlockHeight) - 6);
                    }

                }

            }
        }
    }

    /*----------------------------------------------*/
    void PaintQuestionArea(Graphics g) {
        Font f = new java.awt.Font("Helvetica", 0, 12);
        Font questionFont = new java.awt.Font("Courier", 0, 24);
        Font questionFont18 = new java.awt.Font("Courier", 0, 18);
        FontMetrics questionFontMetrics = g.getFontMetrics(questionFont);

        int viewWidth = kBlocksWide * kBlockWidth;
        int viewHeight = kBlocksHigh * kBlockHeight;

        int top = kPadding;
        int left = (size().width / 2) - (viewWidth / 2);
        //g.clipRect(left, top, viewWidth, kQuestionAreaHeight);

        g.setColor(Color.white);
        g.fill3DRect(left, top, viewWidth, kQuestionAreaHeight, false);

        g.setFont(f);
        String s = new String(String.valueOf(layout[gBlockMinY][gBlockMinX]));
        s = s.concat(" - ");
        if (gDirection == kAcross) {
            s = s.concat("across");
        } else {
            s = s.concat("down");
        }
        g.drawString(s, left + 5, top + 12);
        g.setFont(questionFont);
        int fontSize = 24;
        if (gDirection == kAcross) {
            Font userFont = questionFont;

            if (questionFontMetrics.stringWidth(gQuestionsAcross[layout[gBlockMinY][gBlockMinX]]) > viewWidth - 4) {
                userFont = questionFont18;
                g.setFont(questionFont18);
            }

            g.drawString(gQuestionsAcross[layout[gBlockMinY][gBlockMinX]],
                    (size().width / 2) - (g.getFontMetrics().stringWidth(gQuestionsAcross[layout[gBlockMinY][gBlockMinX]]) / 2),
                    (top + kQuestionAreaHeight) - 8);

        } else {
            Font userFont = questionFont;

            if (questionFontMetrics.stringWidth(gQuestionsDown[layout[gBlockMinY][gBlockMinX]]) > viewWidth - 4) {
                userFont = questionFont18;
                g.setFont(questionFont18);
            }

            g.drawString(gQuestionsDown[layout[gBlockMinY][gBlockMinX]],
                    (size().width / 2) - (g.getFontMetrics().stringWidth(gQuestionsDown[layout[gBlockMinY][gBlockMinX]]) / 2),
                    (top + kQuestionAreaHeight) - 8);
        }

    }

    /*----------------------------------------------*/
    private boolean InActiveBlock(int x, int y) {
        if (x < gBlockMinX) {
            return (false);
        }
        if (x > gBlockMaxX) {
            return (false);
        }
        if (y < gBlockMinY) {
            return (false);
        }
        if (y > gBlockMaxY) {
            return (false);
        }

        return (true);
    }

    /*----------------------------------------------*/
    private void SetActiveBlock(int x, int y, int direction) {
        int tempx;
        int tempy;

        gOldBlockMinY = gBlockMinY;
        gOldBlockMaxY = gBlockMaxY;
        gOldBlockMinX = gBlockMinX;
        gOldBlockMaxX = gBlockMaxX;

        if (direction == kAcross) {
            gBlockMinY = y;
            gBlockMaxY = y;
            tempx = x;
            while (tempx > 0 && layout[y][tempx] != -1) {
                tempx--;
            }
            if (tempx > 0) {
                gBlockMinX = tempx + 1;
            } else {
                if (layout[y][0] == -1) {
                    gBlockMinX = 1;
                } else {
                    gBlockMinX = 0;
                }
            }

            tempx = x;
            while (tempx < kBlocksWide && layout[y][tempx] != -1) {
                tempx++;
            }
            gBlockMaxX = tempx - 1;
        } else {
            gBlockMinX = x;
            gBlockMaxX = x;
            tempy = y;
            while (tempy > 0 && layout[tempy][x] != -1) {
                tempy--;
            }
            if (tempy > 0) {
                gBlockMinY = tempy + 1;
            } else {
                if (layout[0][x] == -1) {
                    gBlockMinY = 1;
                } else {
                    gBlockMinY = 0;
                }
            }

            tempy = y;
            while (tempy < kBlocksHigh && layout[tempy][x] != -1) {
                tempy++;
            }
            gBlockMaxY = tempy - 1;
        }
    }

    /*----------------------------------------------*/
    public void update(Graphics g) {
        if (clipLeft != -1 && clipTop != -1) {
            //g.clipRect(clipLeft, clipTop, clipWidth, clipHeight);
            clipLeft = clipTop = clipWidth = clipHeight = -1;
        }

        if (gChangedActiveAreaFlag == false && gUpdateActiveAreaFlag == false) {
            paint(g);
            return;
        }

        if (gChangedActiveAreaFlag == true) {
            PaintQuestionArea(g);
            PaintWord(g, gOldBlockMinX, gOldBlockMaxX, gOldBlockMinY, gOldBlockMaxY);
            PaintWord(g, gBlockMinX, gBlockMaxX, gBlockMinY, gBlockMaxY);
            gChangedActiveAreaFlag = false;
            return;
        }

        //-----------------------------------------------
        if (gUpdateActiveAreaFlag == true) {
            gUpdateActiveAreaFlag = false;
            PaintWord(g, gBlockMinX, gBlockMaxX, gBlockMinY, gBlockMaxY);
            return;
        }

    }

    /*----------------------------------------------*/
    private void beep() {
        play(getCodeBase(), "nope.au");
    }

    /*----------------------------------------------*/
    public boolean mouseDown(java.awt.Event evt, int x, int y) {
        int viewWidth = kBlocksWide * kBlockWidth;
        int left = (size().width / 2) - (viewWidth / 2);
        int top = (kPadding * 2) + kQuestionAreaHeight;

        requestFocus();

        if (x < left) {
            return false;
        }
        if (y < top) {
            return false;
        }

        int j = y - top;
        j /= kBlockHeight;

        int i = x - left;
        i /= kBlockWidth;

        if (i >= 0 && i < kBlocksWide && j >= 0 && j < kBlocksHigh) {
            if (layout[j][i] != -1) {
                gCurX = i;
                gCurY = j;
                if (InActiveBlock(i, j)) {
                    gUpdateActiveAreaFlag = true;
                    repaint();
                } else {
                    SetActiveBlock(i, j, gDirection);
                    gChangedActiveAreaFlag = true;
                    repaint();
                }
                return true;
            }
        }
        return true;
    }

    /*----------------------------------------------*/
    public boolean mouseUp(java.awt.Event evt, int x, int y) {
        requestFocus();
        return true;
    }

    /*----------------------------------------------*/
    public boolean mouseDrag(java.awt.Event evt, int x, int y) {
        requestFocus();
        return true;
    }


    /*----------------------------------------------*/
    public boolean mouseExit(java.awt.Event evt) {
        return true;
    }

    /*----------------------------------------------*/
    public boolean mouseEnter(java.awt.Event evt) {
        requestFocus();
        return true;
    }

    /*----------------------------------------------*/
    public boolean mouseMove(java.awt.Event evt, int x, int y) {
        requestFocus();
        return true;
    }

    /*----------------------------------------------*/
    public boolean keyDown(java.awt.Event evt, int key) {

        if ((key >= 'A' && key <= 'Z') || (key >= 'a' && key <= 'z')) {
            char charArray[] = new char[1];

            charArray[0] = (char) key;

            gGuesses[gCurX][gCurY] = new String(charArray);
            gGuesses[gCurX][gCurY] = gGuesses[gCurX][gCurY].toUpperCase();

            if (gDirection == kAcross) {
                if (gCurX < kBlocksWide - 1 && layout[gCurY][gCurX + 1] != -1) {
                    gCurX++;
                }
            } else {
                if (gCurY < kBlocksHigh - 1 && layout[gCurY + 1][gCurX] != -1) {
                    gCurY++;
                }
            }
            gUpdateActiveAreaFlag = true;
            repaint();
            return true;
        }

        switch ((char) key) {
            case ' ':
                ChangeDirection();
                gChangedActiveAreaFlag = true;
                repaint();
                break;
            case 0x08:
                if (gGuesses[gCurX][gCurY] != "") {
                gGuesses[gCurX][gCurY] = "";
                gUpdateActiveAreaFlag = true;
                repaint();
            } else {
                if (gDirection == kAcross) {
                    if (gCurX != 0 && layout[gCurY][gCurX - 1] != -1) {
                        gCurX--;
                        gGuesses[gCurX][gCurY] = "";
                        gUpdateActiveAreaFlag = true;
                        repaint();
                    }
                } else {
                    if (gCurY != 0 && layout[gCurY - 1][gCurX] != -1) {
                        gCurY--;
                        gGuesses[gCurX][gCurY] = "";
                        gUpdateActiveAreaFlag = true;
                        repaint();
                    }
                }
            }
                break;
            default:
                beep();
                break;
        }
        return true;
    }


    /*----------------------------------------------*/
    int random(int max) {
        return (int) Math.floor(Math.random() * max);
    }

    /*----------------------------------------------*/
    boolean PtInRect(int x, int y, int left, int top, int right, int bottom) {
        if (x < left) {
            return (false);
        }

        if (x > right) {
            return (false);
        }

        if (y < top) {
            return (false);
        }

        if (y > bottom) {
            return (false);
        }

        return (true);
    }

    /*----------------------------------------------*/
    void ChangeDirection() {
        if (gDirection == kDown) {
            gDirection = kUp;
        } else {
            gDirection = kDown;
        }
        SetActiveBlock(gCurX, gCurY, gDirection);
    }
}

*s4.postimg.org/dmkj3vam1/crossword.png


3) Write a overlapping curves demo with a little animation?

Many applications, especially in corporate environments, ask the user to enter a login (and very often, a password as well)
when it starts. Unfortunately most of these applications display a boring welcome screen which gives you only desire, to
close the application and go grab some food. The J2SE platform contains a wonderful API to help us design much more appealing
welcome screen. I'm talking about Java2D. Here is an example:

The effect is made with two panels, GradientPanel and CurvesPanel. The first one, as you might guess, is responsible for drawing
the greenish gradient in the background. It is not just a simple gradient, it is made of three different gradients. The second
panel, CurvesPanel, extends the first one and add the curves. To draw one curve you can call this method:

Code:
private void drawCurve(Graphics2D g2, 
    float y1, float y1_offset,
    float y2, float y2_offset,
    float cx1, float cx1_offset,
    float cy1, float cy1_offset,
    float cx2, float cx2_offset,
    float cy2, float cy2_offset,
    float thickness,
    float speed,
    boolean invert);

I know, it is really scary. See, everytime paintComponent() is invoked, the animation goes one step forward. All these parameters
let you create a cubic curve (with 4 control points). The parameters ending with _offset are used as animation coefficients.
It's getting late so if you want to understand it, take a look at the code. Just know that the time function is a simple sine.
The last three parameters should speak for themselves (invert simply inverts the gradient used to draw the curve).

You need to call this method once for each curve you want to draw.
And since the curves use alpha transparency, you can overlap them to create really cool effects!!
Albeit visually interesting this code is not to be used in production. The drawCurve() is highly unoptimized.
If you take a look at the source code, you'll it makes a lot of geometrical operations. I've also set all
Java2D rendering hints to their highest values.

Curves demo in Java!!

Code:
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.CubicCurve2D;
import java.awt.geom.GeneralPath;


public class CurvesDemo extends JFrame implements ActionListener {

    private CurvesPanel contentPane;

    public CurvesDemo() {
        contentPane = new CurvesPanel();
        getContentPane().add(BorderLayout.CENTER, contentPane);

        JPanel componentsPane = new JPanel(new BorderLayout());
        componentsPane.setOpaque(false);

        JPanel panel = new JPanel();
        panel.add(new JLabel("User Name : "));
        panel.add(new JTextField(15));
        panel.setOpaque(false);
        componentsPane.add(BorderLayout.NORTH, panel);

        panel = new JPanel();
        panel.add(new JLabel("Password:   "));
        panel.add(new JPasswordField(15));
        panel.setOpaque(false);
        componentsPane.add(BorderLayout.CENTER, panel);
        contentPane.add(BorderLayout.NORTH, Box.createVerticalStrut(375));
        contentPane.add(BorderLayout.CENTER, componentsPane);

        Timer animation = new Timer(50, this);
        animation.start();
        // Set the Frame's properties
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setSize(new Dimension(550, 450));
        setResizable(false);
        setVisible(true);
        setLocationRelativeTo(null);
    }

    public void actionPerformed(ActionEvent e) {
        contentPane.repaint();
    }

    public String getTitle() {
        return "CurvesDemo";
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                new CurvesDemo();
            }
        });
    }
}


 class CurvesPanel extends GradientPanel
 {
    private RenderingHints hints;
    private int counter = 0;

    public CurvesPanel() {
        hints = new RenderingHints(RenderingHints.KEY_ALPHA_INTERPOLATION,
                RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
        hints.put(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);
        hints.put(RenderingHints.KEY_COLOR_RENDERING,
                RenderingHints.VALUE_COLOR_RENDER_QUALITY);
        hints.put(RenderingHints.KEY_INTERPOLATION,
                RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        hints.put(RenderingHints.KEY_RENDERING,
                RenderingHints.VALUE_RENDER_QUALITY);
    }

    public void paintComponent(Graphics g) {
        counter++;

        Graphics2D g2 = (Graphics2D) g;
        g2.setRenderingHints(hints);
        super.paintComponent(g2);

        float width = getWidth();
        float height = getHeight();

        g2.translate(0, -30);
        drawCurve(g2,
                20.0f, -10.0f, 20.0f, -10.0f,
                width / 2.0f - 40.0f, 10.0f,
                0.0f, -5.0f,
                width / 2.0f + 40, 1.0f,
                0.0f, 5.0f,
                50.0f, 5.0f, false);
        g2.translate(0, 30);

        g2.translate(0, height - 60);
        drawCurve(g2,
                30.0f, -15.0f, 50.0f, 15.0f,
                width / 2.0f - 40.0f, 1.0f,
                15.0f, -25.0f,
                width / 2.0f, 1.0f / 2.0f,
                0.0f, 25.0f,
                15.0f, 6.0f, false);
        g2.translate(0, -height + 60);

        drawCurve(g2,
                height - 35.0f, -5.0f, height - 50.0f, 10.0f,
                width / 2.0f - 40.0f, 1.0f,
                height - 35.0f, -25.0f,
                width / 2.0f, 1.0f / 2.0f,
                height - 20.0f, 25.0f,
                25.0f, 4.0f, true);
    }

    private void drawCurve(Graphics2D g2,
                           float y1, float y1_offset,
                           float y2, float y2_offset,
                           float cx1, float cx1_offset,
                           float cy1, float cy1_offset,
                           float cx2, float cx2_offset,
                           float cy2, float cy2_offset,
                           float thickness,
                           float speed,
                           boolean invert) {

        float width = getWidth();
        float height = getHeight();

        double offset = Math.sin(counter / (speed * Math.PI));
        float start_x = 0.0f;
        float start_y = y1 + (float) (offset * y1_offset);
        float end_x = width;
        float end_y = y2 + (float) (offset * y2_offset);
        float ctrl1_x = (float) offset * cx1_offset + cx1;
        float ctrl1_y = cy1 + (float) (offset * cy1_offset);
        float ctrl2_x = (float) (offset * cx2_offset) + cx2;
        float ctrl2_y = (float) (offset * cy2_offset) + cy2;

        CubicCurve2D curve = new CubicCurve2D.Double(start_x, start_y, ctrl1_x, ctrl1_y, ctrl2_x, ctrl2_y, end_x, end_y);

        GeneralPath path = new GeneralPath(curve);
        path.lineTo(width, height);
        path.lineTo(0, height);
        path.closePath();

        Area thickCurve = new Area((Shape) path.clone());
        AffineTransform translation = AffineTransform.getTranslateInstance(0, thickness);
        path.transform(translation);
        thickCurve.subtract(new Area(path));

        Color start = new Color(255, 255, 255, 200);
        Color end = new Color(255, 255, 255, 0);

        Rectangle bounds = thickCurve.getBounds();
        GradientPaint painter = new GradientPaint(0, curve.getBounds().y,
                invert ? end : start,
                0, bounds.y + bounds.height,
                invert ? start : end);
        Paint oldPainter = g2.getPaint();
        g2.setPaint(painter);

        g2.fill(thickCurve);

        g2.setPaint(oldPainter);
    }
}

class GradientPanel extends JPanel {
    public void paintComponent(Graphics g) {
        int width = getWidth();
        int height = getHeight();

        Color gradientStart = new Color(128, 9, 219);
        Color gradientEnd = new Color(138, 27, 211);

        Graphics2D g2 = (Graphics2D) g;
        GradientPaint painter = new GradientPaint(0, 0, gradientStart,
                0, height, gradientEnd);
        Paint oldPainter = g2.getPaint();
        g2.setPaint(painter);
        g2.fill(g2.getClip());

        gradientStart = new Color(6, 122, 255, 200);
        gradientEnd = new Color(70, 200, 255, 255);

        painter = new GradientPaint(0, 0, gradientEnd,
                0, height / 2, gradientStart);
        g2.setPaint(painter);
        g2.fill(g2.getClip());

        painter = new GradientPaint(0, height / 2, gradientStart,
                0, height, gradientEnd);
        g2.setPaint(painter);
        g2.fill(g2.getClip());

        g2.setPaint(oldPainter);
    }
}

*s3.postimg.org/jqiffua8f/Curves_Demo.png


5) Write a Gas applet ?



Code:
// Gas.java (C) 2001 by Paul Falstad, www.falstad.com
import java.awt.*;
import java.applet.Applet;
import java.util.Vector;
import java.util.Random;
import java.lang.Math;
import java.awt.event.*;
import java.text.DecimalFormat;
import java.text.NumberFormat;

class GasCanvas extends Canvas {

    Gas pg;

    GasCanvas(Gas p) {
        pg = p;
    }

    public Dimension getPreferredSize() {
        return new Dimension(300, 400);
    }

    public void update(Graphics g) {
        pg.updateGas(g);
    }

    public void paint(Graphics g) {
        pg.updateGas(g);
    }
};

class HistogramCanvas extends Canvas {

    Gas pg;

    HistogramCanvas(Gas p) {
        pg = p;
    }

    public Dimension getPreferredSize() {
        return new Dimension(125, 50);
    }

    public void update(Graphics g) {
        pg.updateHistogram(g);
    }
};

class GasLayout implements LayoutManager {

    public GasLayout() {
    }

    public void addLayoutComponent(String name, Component c) {
    }

    public void removeLayoutComponent(Component c) {
    }

    public Dimension preferredLayoutSize(Container target) {
        return new Dimension(500, 500);
    }

    public Dimension minimumLayoutSize(Container target) {
        return new Dimension(100, 100);
    }

    public void layoutContainer(Container target) {
        int cw = target.size().width * 2 / 3;
        target.getComponent(0).move(0, 0);
        target.getComponent(0).resize(cw, target.size().height - 100);
        target.getComponent(1).move(0, target.size().height - 100);
        target.getComponent(1).resize(cw, 100);
        int i;
        int h = 0;
        for (i = 2; i < target.getComponentCount(); i++) {
            Component m = target.getComponent(i);
            if (m.isVisible()) {
                Dimension d = m.getPreferredSize();
                if (m instanceof Scrollbar) {
                    d.width = target.size().width - cw;
                }
                int c = 0;
                if (m instanceof Label) {
                    h += d.height / 3;
                    c = (target.size().width - cw - d.width) / 2;
                }
                m.move(cw + c, h);
                m.resize(d.width, d.height);
                h += d.height;
            }
        }
    }
};

public class Gas extends Applet
        implements ComponentListener, ActionListener, AdjustmentListener,
        ItemListener {

    Thread engine = null;
    int molCount;

    Dimension winSize;
    Image dbimage;

    public static final int defaultPause = 10;
    int heaterSize;
    int pause;
    Random random;

    public String getAppletInfo() {
        return "Gas Molecules by Paul Falstad";
    }

    public static int gridEltWidth = 10; // was 60
    public static int gridEltHeight = 10;
    int gridWidth;
    int gridHeight;
    Molecule mols[];
    Molecule grid[][];
    Molecule bigmol;
    Button resetButton;
    Button expandButton;
    Checkbox stoppedCheck;
    Checkbox heaterCheck;
    Checkbox energyCheck;
    Scrollbar heaterTempBar;
    Scrollbar gravityBar;
    Scrollbar speedBar;
    Scrollbar molCountBar;
    Scrollbar colorBar;
    Choice setupChooser;
    Vector setupList;
    Setup setup;
    double gravity;
    double colorMult;
    int upperBound;
    double topWallPos;
    double topWallVel;
    int areaHeight;
    double heatstate;
    double heaterTemp;
    double heaterMove;
    double wallF, wallFMeasure;
    Color heaterColor;
    Color colors[];
    int heaterTop;
    int heaterLeft;
    int heaterRight;
    final int maxMolCount = 1000;
    NumberFormat showFormat;

    int getrand(int x) {
        int q = random.nextInt();
        if (q < 0) {
            q = -q;
        }
        return q % x;
    }
    GasCanvas cv;
    HistogramCanvas hist_cv;

    public void init() {
        setupList = new Vector();
        Setup s = new Setup1Random();
        while (s != null) {
            setupList.addElement(s);
            s = s.createNext();
        }
        showFormat = DecimalFormat.getInstance();
        showFormat.setMaximumFractionDigits(3);

        int ci = 0;
        heatstate = 0;
        colors = new Color[16];
        colors[ci++] = new Color(46, 120, 255);
        colors[ci++] = new Color(79, 140, 254);
        colors[ci++] = new Color(113, 142, 253);
        colors[ci++] = new Color(147, 145, 252);
        colors[ci++] = new Color(181, 105, 178);
        colors[ci++] = new Color(215, 64, 103);
        colors[ci++] = new Color(249, 23, 28);
        colors[ci++] = new Color(250, 101, 44);
        colors[ci++] = new Color(251, 139, 33);
        colors[ci++] = new Color(252, 178, 22);
        colors[ci++] = new Color(253, 216, 11);
        colors[ci++] = new Color(255, 255, 0);
        colors[ci++] = new Color(255, 255, 63);
        colors[ci++] = new Color(255, 255, 127);
        colors[ci++] = new Color(255, 255, 191);
        colors[ci++] = new Color(255, 255, 255);
        gravity = 0;
        //setLayout(new GridLayout(3, 1, 10, 10));
        setLayout(new GasLayout());
        //setLayout(new GridBagLayout());
        cv = new GasCanvas(this);
        cv.addComponentListener(this);
        add(cv);
        hist_cv = new HistogramCanvas(this);
        hist_cv.addComponentListener(this);
        add(hist_cv);

        setupChooser = new Choice();
        int i;
        for (i = 0; i != setupList.size(); i++) {
            setupChooser.add("Setup: "
                    + ((Setup) setupList.elementAt(i)).getName());
        }
        setupChooser.addItemListener(this);
        add(setupChooser);

        stoppedCheck = new Checkbox("Stopped");
        stoppedCheck.addItemListener(this);
        add(stoppedCheck);

        heaterCheck = new Checkbox("Heater");
        heaterCheck.addItemListener(this);
        add(heaterCheck);

        energyCheck = new Checkbox("Energy Distribution");
        energyCheck.addItemListener(this);
        add(energyCheck);

        add(resetButton = new Button("Reset"));
        resetButton.addActionListener(this);
        add(expandButton = new Button("Expand"));
        expandButton.addActionListener(this);
        add(new Label("Simulation Speed", Label.CENTER));
        add(speedBar = new Scrollbar(Scrollbar.HORIZONTAL, 50, 1, 0, 100));
        speedBar.addAdjustmentListener(this);
        add(new Label("Molecule Count", Label.CENTER));
        add(molCountBar = new Scrollbar(Scrollbar.HORIZONTAL, 500, 1, 1, maxMolCount));
        molCountBar.addAdjustmentListener(this);
        add(new Label("Color Scale", Label.CENTER));
        add(colorBar = new Scrollbar(Scrollbar.HORIZONTAL, 150, 1, 1, 300));
        colorBar.addAdjustmentListener(this);
        add(new Label("Heater Temperature", Label.CENTER));
        add(heaterTempBar = new Scrollbar(Scrollbar.HORIZONTAL, 35, 1, 0, 100));
        heaterTempBar.addAdjustmentListener(this);
        add(new Label("Gravity", Label.CENTER));
        add(gravityBar = new Scrollbar(Scrollbar.HORIZONTAL, 20, 1, 0, 100));
        gravityBar.addAdjustmentListener(this);
        cv.setBackground(Color.black);
        cv.setForeground(heaterColor = Color.lightGray);
        hist_cv.setBackground(Color.black);
        hist_cv.setForeground(Color.lightGray);
        random = new Random();
        pause = defaultPause;
        adjustColors();
        adjustHeaterTemp();
        enableItems();
        try {
            String param = getParameter("PAUSE");
            if (param != null) {
                pause = Integer.parseInt(param);
            }
        } catch (Exception e) {
        }
        reinit(true);
        repaint();
    }

    static final int SPEED_RANDOM = 0;
    static final int SPEED_EQUAL = 1;
    static final int SPEED_EXTREME = 2;

    void reinit(boolean newsetup) {
        if (cv.getSize().width == 0 || gravityBar == null || setupChooser == null) {
            return;
        }
        System.out.println("winsize " + winSize);
        bigmol = null;
        setup = (Setup) setupList.elementAt(setupChooser.getSelectedIndex());
        gravityBar.setValue(0);
        if (newsetup) {
            speedBar.setValue(20);
            molCountBar.setValue(500);
            colorBar.setValue(160);
            setup.select();
        }
        setup.reinit();
        adjustColors();
    }

    void expand() {
        topWallPos -= 50;
        if (topWallPos < 0) {
            topWallPos = 0;
        }
        enableItems();
    }

    void initMolecules(int speed) {
        Dimension d = winSize = cv.getSize();
        molCount = molCountBar.getValue();
        upperBound = (int) (winSize.height * (1 - setup.getVolume()) - 1);
        topWallPos = upperBound;
        areaHeight = winSize.height - upperBound;
        mols = new Molecule[maxMolCount];
        dbimage = createImage(d.width, d.height);
        gridWidth = d.width / gridEltWidth + 1;
        gridHeight = d.height / gridEltHeight + 1;
        grid = new Molecule[gridWidth][gridHeight];
        int i, j;
        for (i = 0; i != gridWidth; i++) {
            for (j = 0; j != gridHeight; j++) {
                grid[i][j] = new Molecule();
                grid[i][j].listHead = true;
            }
        }
        for (i = 0; i != maxMolCount; i++) {
            Molecule m = new Molecule();
            mols[i] = m;
            m.x = getrand(winSize.width * 10) * .1;
            m.y = getrand(areaHeight * 10) * .1 + upperBound;
            m.dx = (getrand(100) / 99.0 - .5);
            m.dy = java.lang.Math.sqrt(1 - m.dx * m.dx);
            if (getrand(10) > 4) {
                m.dy = -m.dy;
            }
            if (speed == SPEED_EXTREME) {
                double q = ((i & 2) > 0) ? 3 : .1;
                m.dx *= q;
                m.dy *= q;
            }
            if (speed == SPEED_RANDOM) {
                double q = getrand(101) / 50.;
                m.dx *= q;
                m.dy *= q;
            }
            if (Double.isNaN(m.dx) || Double.isNaN(m.dy)) {
                System.out.println("nan1");
            }
            setColor(m);
            if (i < molCount) {
                gridAdd(m);
            }
        }
        heaterTop = winSize.height - 5;
        heaterSize = winSize.width / 4;
        heaterLeft = (winSize.width - heaterSize * 3) / 2;
        heaterRight = (winSize.width + heaterSize * 3) / 2;
        enableItems();
        cv.repaint();
        hist_cv.repaint();
    }

    void setMoleculeTypes(double mult, int typeCount) {
        int i, j;
        for (i = 0; i != maxMolCount; i++) {
            Molecule m = mols[i];
            m.r *= mult;
            m.mass *= mult * mult;
            if (typeCount > 1) {
                int n = (i % typeCount);
                m.type = n;
                if (n == 2) {
                    m.r *= 3;
                    m.mass *= 9; // was 27
                } else if (n == 1) {
                    m.r *= 2;
                    m.mass *= 4; // was 8
                }
            }
            setColor(m);
        }
    }

    long secTime, lastTime;
    double t, lastSecT, totalKE, temp, totalV;

    public void updateGas(Graphics realg) {
        if (winSize == null) {
            return;
        }
        Graphics g = dbimage.getGraphics();
        g.setColor(cv.getBackground());
        g.fillRect(0, 0, winSize.width, winSize.height);
        int j;

        double dt = speedBar.getValue() / 100.;
        if (!stoppedCheck.getState()) {
            long sysTime = System.currentTimeMillis();
            if (lastTime != 0) {
                int inc = (int) (sysTime - lastTime);
                dt *= inc / 8.;
            }
            if (sysTime - secTime >= 1000) {
                if (t > 0) {
                    wallF /= t - lastSecT;
                }
                wallFMeasure = wallF;
                wallF = 0;
                secTime = sysTime;
                lastSecT = t;
            }
            lastTime = sysTime;
        } else {
            lastTime = 0;
        }

        for (short i = 0; i != molCount; i++) {
            Molecule m = mols[i];
            boolean bounce = false;
            int ix = (int) m.x;
            int iy = (int) m.y;
            j = (stoppedCheck.getState()) ? 5 : 0;
            for (; j < 5; j++) {
                m.dy += gravity * dt;
                m.x += m.dx * dt;
                m.y += m.dy * dt;
                if (Double.isNaN(m.dx) || Double.isNaN(m.dy)) {
                    System.out.println("nan2");
                }
                int r = m.r;
                if (m.x < r || m.x >= winSize.width - r) {
                    wallF += Math.abs(m.dx) * m.mass;
                    m.dx = -m.dx;
                    if (m.x < m.r) {
                        m.x = m.r;
                    }
                    if (m.x >= winSize.width - r) {
                        m.x = winSize.width - r - 1;
                    }
                    setColor(m);
                    bounce = true;
                }
                if (m.y < upperBound + r || m.y >= winSize.height - r) {
                    wallF += Math.abs(m.dy) * m.mass;
                    if (m.y < upperBound + r) {
                        m.y = upperBound + r;
                    }
                    if (m.y >= winSize.height - r) {
                        m.y = winSize.height - r - 1;
                    }
                    if (m.y == upperBound + r && m.dy < 0 && false) {
                        double wallMass = 1000;
                        double totmass = m.mass + wallMass;
                        double comdy
                                = (m.mass * m.dy + wallMass * topWallVel) / totmass;
                        double chg = (m.dy - comdy);
                        //System.out.print("< " + m.dy + " " + topWallVel + "\n");
                        m.dy -= 2 * chg;
                        topWallVel += 2 * chg * m.mass / wallMass;
                        //System.out.print("> " + m.dy + " " + topWallVel + "\n");
                    } else {
                        m.dy = -m.dy;
                    }
                    setColor(m);
                    bounce = true;
                }
                int nix = (int) m.x;
                int niy = (int) m.y;
                if (!bounce && nix >= heaterLeft && nix <= heaterRight
                        && niy >= heaterTop - 1 && heaterCheck.getState()) {
                    double v = java.lang.Math.sqrt(m.dx * m.dx + m.dy * m.dy);
                    double oldy = m.dy;
                    // calculate velocity of particle if it were at heater temp
                    double mxv = Math.sqrt(3 * heaterTemp / m.mass);
                    // mix this velocity with particle's velocity randomly
                    double mix = getrand(100) / 99.0;
                    mix = 0;
                    double newv = v * mix + mxv * (1 - mix);
                    // randomize direction
                    m.dx = getrand(101) / 50.0 - 1;
                    m.dy = -Math.sqrt(1 - m.dx * m.dx) * newv;
                    m.dx *= newv;
                    if (Double.isNaN(m.dx) || Double.isNaN(m.dy)) {
                        System.out.println("nan3");
                    }
                    wallF += (oldy - m.dy) * m.mass;
                    setColor(m);
                    bounce = true;
                    m.y = heaterTop - 2;
                    niy = (int) m.y;
                }
                Molecule m2 = (bounce) ? null : checkCollision(m);
                if (m2 != null) {
                    // handle a collision
                    // first, find exact moment they collided by solving
                    // a quadratic equation:
                    // [(x1-x2)+t(dx1-dx2)]^2 + [(y1-y2)+...]^2 = mindist^2
                    // (first deal with degenerate case where molecules are on top
                    // of each other)
                    if (m.dx == m2.dx && m.dy == m2.dy) {
                        if (m.dx == 0 && m.dy == 0) {
                            continue;
                        }
                        m.dx += .001;
                    }
                    double sdx = m.dx - m2.dx;
                    double sx = m.x - m2.x;
                    double sdy = m.dy - m2.dy;
                    double sy = m.y - m2.y;
                    int mindist = m.r + m2.r;
                    double a = sdx * sdx + sdy * sdy;
                    double b = 2 * (sx * sdx + sy * sdy);
                    double c = sx * sx + sy * sy - mindist * mindist;
                    double t = (-b - java.lang.Math.sqrt(b * b - 4 * a * c)) / a;
                    double t2 = (-b + java.lang.Math.sqrt(b * b - 4 * a * c)) / a;
                    if (java.lang.Math.abs(t) > java.lang.Math.abs(t2)) {
                        t = t2;
                    }
                    if (Double.isNaN(t)) {
                        System.out.print("nan " + m.dx + " " + m.dy + " "
                                + m2.dx + " " + m2.dy + " " + a + " " + b
                                + " " + c + " " + t + " " + t2 + "\n");
                    }

                    // backtrack m to where they collided.
                    // (t is typically negative.)
                    m.x += t * m.dx;
                    m.y += t * m.dy;

                    // ok, so now they are just touching.  find vector
                    // separating their centers and normalize it.
                    sx = m.x - m2.x;
                    sy = m.y - m2.y;
                    double sxynorm = java.lang.Math.sqrt(sx * sx + sy * sy);
                    double sxn = sx / sxynorm;
                    double syn = sy / sxynorm;

                    // find velocity of center of mass
                    double totmass = m.mass + m2.mass;
                    double comdx = (m.mass * m.dx + m2.mass * m2.dx) / totmass;
                    double comdy = (m.mass * m.dy + m2.mass * m2.dy) / totmass;
                    //System.out.print("<x " + (m.dx-comdx) + " " + (m2.dx-comdx) + "\n");
                    //System.out.print("<y " + (m.dy-comdy) + " " + (m2.dy-comdy) + "\n");

                    // subtract COM velocity from m's momentum and
                    // project result onto the vector separating them.
                    // This is the component of m's momentum which
                    // must be turned the other way.  Double the
                    // result.  This is the momentum that is
                    // transferred.
                    double pn = (m.dx - comdx) * sxn + (m.dy - comdy) * syn;
                    double px = 2 * sxn * pn;
                    double py = 2 * syn * pn;

                    // subtract this vector from m's momentum
                    m.dx -= px;
                    m.dy -= py;
                    if (Double.isNaN(m.dx)) {
                        System.out.println("nan0 " + sxynorm + " " + pn);
                    }

                    // adjust m2's momentum so that total momentum
                    // is conserved
                    double mult = m.mass / m2.mass;
                    m2.dx += px * mult;
                    m2.dy += py * mult;
                    //System.out.print(">x " + (m.dx-comdx) + " " + (m2.dx-comdx) + "\n");
                    //System.out.print(">y " + (m.dy-comdy) + " " + (m2.dy-comdy) + "\n");

                    // send m on its way
                    if (t < 0) {
                        m.x -= t * m.dx;
                        m.y -= t * m.dy;
                    }
                    if (m.x < r) {
                        m.x = r;
                    }
                    if (m.x >= winSize.width - r) {
                        m.x = winSize.width - r;
                    }
                    if (m.y < upperBound + r) {
                        m.y = upperBound + r;
                    }
                    if (m.y >= winSize.height - r) {
                        m.y = winSize.height - r - 1;
                    }
                    if (Double.isNaN(m.dx) || Double.isNaN(m.dy)) {
                        System.out.println("nan4");
                    }
                    if (Double.isNaN(m2.dx) || Double.isNaN(m2.dy)) {
                        System.out.println("nan5");
                    }
                    setColor(m);
                }
                // this line may not be reached
            }
            g.setColor(m.color);
            g.fillOval((int) m.x - m.r, (int) m.y - m.r, m.r * 2, m.r * 2);
            // XXX
            //g.fillRect((int)m.x-m.r, (int)m.y-m.r, m.r*2, m.r*2);
            gridRemove(m);
            gridAdd(m);
        }
        t += dt * 5;
        totalKE = 0;
        totalV = 0;
        for (short i = 0; i != molCount; i++) {
            Molecule m = mols[i];
            totalKE += m.mass * (m.dx * m.dx + m.dy * m.dy);
            totalV += m.r * m.r;
        }
        totalV *= Math.PI;
        totalKE *= .5;
        temp = (2. / 3) * totalKE / molCount;

        //topWallVel += volumeBar.getValue()*.01;
        if (topWallVel > .5) {
            topWallVel = .5;
        }
        topWallPos += topWallVel * 5;
        if (topWallPos < 0) {
            topWallPos = 0;
            if (topWallVel < 0) {
                topWallVel = 0;
            }
        }
        if (topWallPos > (winSize.height * 4 / 5)) {
            topWallPos = (winSize.height * 4 / 5);
            if (topWallVel > 0) {
                topWallVel = 0;
            }
        }
        upperBound = (int) topWallPos;

        int heatstateint = ((int) heatstate);
        if (heaterCheck.getState()) {
            for (j = 0; j != heaterSize; j++, heatstateint++) {
                int x = heaterLeft + j * 3;
                int y = heatstateint & 3;
                if ((heatstateint & 4) == 4) {
                    y = 4 - y;
                }
                g.setColor(heaterColor);
                g.fillRect(x, heaterTop + y, 2, 2);
            }
        }
        g.setColor(Color.lightGray);
        g.drawRect(0, upperBound,
                winSize.width - 1, winSize.height - 1 - upperBound);
        g.fillRect(winSize.width / 2 - 20, 0, 40, upperBound);
        realg.drawImage(dbimage, 0, 0, this);
        if (!stoppedCheck.getState()) {
            heatstate += heaterMove;
            cv.repaint(pause);
            hist_cv.repaint(pause);
        }
    }

    void gridAdd(Molecule m) {
        int gx = (int) (m.x / gridEltWidth);
        int gy = (int) (m.y / gridEltHeight);
        Molecule g = grid[gx][gy];
        m.next = g;
        m.prev = g.prev;
        g.prev = m;
        m.prev.next = m;
    }

    void gridRemove(Molecule m) {
        m.next.prev = m.prev;
        m.prev.next = m.next;
    }

    Molecule checkCollision(Molecule m) {
        if (bigmol != null) {
            Molecule q = checkCollision(m,
                    grid[(int) (bigmol.x / gridEltWidth)][(int) (bigmol.y / gridEltHeight)]);
            if (q != null) {
                return q;
            }
        }
        int gx = (int) (m.x / gridEltWidth);
        int gy = (int) (m.y / gridEltHeight);
        int i, j;
        // check grid squares around the molecule for collisions
        for (i = -1; i <= 1; i++) {
            for (j = -1; j <= 1; j++) {
                if (gx + i < 0 || gy + j < 0
                        || gx + i >= gridWidth || gy + j >= gridHeight) {
                    continue;
                }
                Molecule n = checkCollision(m, grid[gx + i][gy + j]);
                if (n != null) {
                    return n;
                }
            }
        }
        return null;
    }

    Molecule checkCollision(Molecule m, Molecule list) {
        Molecule l = list.next;
        int count = 0;
        for (; !l.listHead; l = l.next) {
            if (m == l) {
                continue;
            }
            count++;
            int mindist = m.r + l.r;
            double dx = m.x - l.x;
            double dy = m.y - l.y;
            //System.out.print("ck " + dx + " " + dy + "\n");
            if (dx > mindist || dy > mindist
                    || dx < -mindist || dy < -mindist) {
                continue;
            }
            double dist = java.lang.Math.sqrt(dx * dx + dy * dy);
            if (dist > mindist) {
                continue;
            }
            //System.out.print("COLL " + m + " " + l + "\n");
            return l;
        }
        return null;
    }

    void setColor(Molecule m) {
        m.vel = Math.sqrt(m.dx * m.dx + m.dy * m.dy);
        m.ke = m.mass * m.vel * m.vel;
        int col = (int) (m.ke * colorMult);
        int maxcol = colors.length - 1;
        if (col > maxcol) {
            col = maxcol;
        }
        m.color = colors[col];
    }

    int graphmax = 20;

    public void updateHistogram(Graphics realg) {
        if (winSize == null) {
            return;
        }
        Dimension d = hist_cv.size();
        Graphics g = dbimage.getGraphics();
        g.setColor(hist_cv.getBackground());
        g.fillRect(0, 0, d.width, d.height);
        g.setColor(hist_cv.getForeground());
        int i;
        int slots = d.width / 2;
        int graph[] = new int[slots];
        int gi;
        int mg = 0;
        int gicount = setup.getHistogramCount();
        boolean energy = energyCheck.getState();
        for (gi = 0; gi != gicount; gi++) {
            int ymin = d.height * gi / gicount;
            int ymax = d.height * (gi + 1) / gicount - 1;
            int yheight = ymax - ymin;
            double maxke = energy ? 70 : 15;
            for (i = 0; i != slots; i++) {
                graph[i] = 0;
            }
            double mass = 1;
            int mcount = 0;
            for (i = 0; i != molCount; i++) {
                Molecule m = mols[i];
                if (m.type != gi) {
                    continue;
                }
                mcount++;
                mass = m.mass;
                double value = (energy ? m.ke : m.vel);
                int r = (int) (value * slots / maxke);
                if (r >= slots) {
                    continue;
                }
                graph[r]++;
            }
            maxke += .5;
            int maxcol = colors.length - 1;
            for (i = 0; i != slots; i++) {
                if (graph[i] == 0) {
                    continue;
                }
                if (graph[i] > mg) {
                    mg = graph[i];
                }
                int y = ymax - (graph[i] * yheight / graphmax);
                if (y < ymin) {
                    y = ymin;
                }
                double value = i * maxke / slots;
                if (!energy) {
                    value *= mass * value;
                }
                int col = (int) (value * colorMult);
                if (col > maxcol) {
                    col = maxcol;
                }
                g.setColor(colors[col]);
                g.fillRect(i * 2, y, 2, ymax - y + 1);
            }
            int ox = -1, oy = -1;
            g.setColor(Color.lightGray);
            // not sure if maxwell energy distribution is right,
            // comment it out for now
            if (!energyCheck.getState()) {
                for (i = 0; i != slots; i++) {
                    double v = i * maxke / slots;
                    double dv = maxke / slots;
                    double distdv = .5 * mcount * (maxwellDist(v, mass) + maxwellDist(v + dv, mass)) * dv;
                    int v0 = (int) distdv;
                    int y = (ymax - (v0 * yheight / graphmax));
                    if (y < ymin) {
                        y = ymin;
                    }
                    int x = i * 2;
                    if (ox != -1 && !(y == oy && oy == ymax)) {
                        g.drawLine(ox, oy, x, y);
                    }
                    ox = x;
                    oy = y;
                }
            }
        }
        if (mg > graphmax) {
            graphmax = mg;
        }
        if (mg < graphmax / 2 && graphmax > 1) {
            graphmax /= 2;
        }

        FontMetrics fm = g.getFontMetrics();
        g.setColor(Color.white);
        int x = winSize.width * 2 / 3;
        double vadj = 4e-4;
        double v = (winSize.width - 2) * (winSize.height - upperBound - 2) * vadj;
        g.drawString("V = " + showFormat.format(v), x, fm.getAscent());
        g.drawString("n = " + molCount, x, fm.getAscent() + fm.getHeight());
        double a = 2 * (winSize.width + (winSize.height - upperBound) - 4);
        double p = 1e4 * wallFMeasure / (3 * a);
        g.drawString("P = " + showFormat.format(p),
                x, fm.getAscent() + 2 * fm.getHeight());
        g.drawString("kT = " + showFormat.format(temp), x,
                fm.getAscent() + 3 * fm.getHeight());
        g.drawString("PV/nkT = " + showFormat.format(p * v / (molCount * temp)),
                x, fm.getAscent() + 4 * fm.getHeight());
        g.drawString("P(V-nb)/nkT = "
                + showFormat.format(p * (v - totalV * vadj) / (molCount * temp)),
                x, fm.getAscent() + 5 * fm.getHeight());

        realg.drawImage(dbimage, 0, 0, this);
    }

    // 2-D Maxwell distribution of molecular speeds
    double maxwellDist(double v, double mass) {
        if (energyCheck.getState()) {
            return Math.exp(-v / temp) / temp;
        }
        return (mass / temp) * v * Math.exp(-mass * v * v / (2 * temp));
    }

    public void componentHidden(ComponentEvent e) {
    }

    public void componentMoved(ComponentEvent e) {
    }

    public void componentShown(ComponentEvent e) {
    }

    public void componentResized(ComponentEvent e) {
        reinit(false);
        cv.repaint(100);
        hist_cv.repaint(100);
    }

    public void actionPerformed(ActionEvent e) {
        System.out.println(e);
        if (e.getSource() == resetButton) {
            reinit(false);
            cv.repaint();
        }
        if (e.getSource() == expandButton) {
            expand();
            cv.repaint();
        }
    }

    public void adjustmentValueChanged(AdjustmentEvent e) {
        System.out.println(((Scrollbar) e.getSource()).getValue());
        /*if (e.getSource() == volumeBar) {
         upperBound = winSize.height * (100-volumeBar.getValue()) / 100;
         areaHeight = winSize.height - upperBound;
         }*/ // XXXXXX
        if (e.getSource() == gravityBar) {
            gravity = gravityBar.getValue() * (.001 / 20);
        }
        if (e.getSource() == heaterTempBar) {
            adjustHeaterTemp();
        }
        if (e.getSource() == molCountBar) {
            adjustMolCount();
        }
        if (e.getSource() == colorBar) {
            adjustColors();
        }
    }

    void adjustHeaterTemp() {
        heaterTemp = (heaterTempBar.getValue() * .029111971) * 30 + .01;
        heaterMove = (heaterTempBar.getValue() * .029111971) + .3;
        heaterMove /= 2;
        double value = 1.5 * heaterTemp;
        int col = (int) (value * colorMult);
        int maxcol = colors.length - 1;
        if (col > maxcol) {
            col = maxcol;
        }
        heaterColor = colors[col];
        System.out.println("htemp = " + heaterTemp);
    }

    void adjustColors() {
        int i;
        double c = colorBar.getValue() / 150.;
        colorMult = Math.exp((c - 1) * 4) * .7;
        for (i = 0; i != molCount; i++) {
            setColor(mols[i]);
        }
    }

    void enableItems() {
        heaterTempBar.setEnabled(heaterCheck.getState());
        expandButton.setEnabled(topWallPos > 0);
    }

    public void itemStateChanged(ItemEvent e) {
        enableItems();
        if (e.getItemSelectable() == stoppedCheck) {
            cv.repaint();
            return;
        }
        if (e.getItemSelectable() == setupChooser) {
            reinit(true);
        }
    }

    void adjustMolCount() {
        int oldcount = molCount;
        molCount = molCountBar.getValue();
        if (molCount == oldcount) {
            return;
        }
        if (oldcount > molCount) {
            int i;
            for (i = molCount; i != oldcount; i++) {
                gridRemove(mols[i]);
            }
        } else {
            int i;
            for (i = oldcount; i != molCount; i++) {
                gridAdd(mols[i]);
            }
        }
    }

    class Molecule {

        public double x, y, dx, dy, mass, ke, vel;
        public int r, type;
        public Color color;
        public Molecule next, prev;
        public boolean listHead;

        Molecule() {
            r = 2;
            type = 0;
            mass = 2;
            next = prev = this;
        }
    };

    abstract class Setup {

        abstract String getName();

        void select() {
        }

        void reinit() {
        }

        void deselect() {
        }

        int getHistogramCount() {
            return 1;
        }

        double getVolume() {
            return 1;
        }

        abstract Setup createNext();
    };

    class Setup1Random extends Setup {

        String getName() {
            return "1 Gas, Random Speeds";
        }

        void reinit() {
            initMolecules(SPEED_RANDOM);
            setMoleculeTypes(2, 1);
        }

        Setup createNext() {
            return new Setup1Equal();
        }
    }

    class Setup1Equal extends Setup {

        String getName() {
            return "1 Gas, Equal Speeds";
        }

        void select() {
            speedBar.setValue(3);
        }

        void reinit() {
            initMolecules(SPEED_EQUAL);
            setMoleculeTypes(2, 1);
        }

        Setup createNext() {
            return new Setup1Extreme();
        }
    }

    class Setup1Extreme extends Setup {

        String getName() {
            return "1 Gas, Extreme Speeds";
        }

        void select() {
            speedBar.setValue(3);
        }

        void reinit() {
            initMolecules(SPEED_EXTREME);
            setMoleculeTypes(2, 1);
        }

        Setup createNext() {
            return new Setup1Single();
        }
    }

    class Setup1Single extends Setup {

        String getName() {
            return "1 Gas, One Moving Molecule";
        }

        void select() {
            speedBar.setValue(10);
        }

        void reinit() {
            initMolecules(SPEED_EQUAL);
            int i, j;
            for (i = 1; i != maxMolCount; i++) {
                mols[i].dx = mols[i].dy = 0;
            }
            mols[0].dx *= Math.sqrt(molCount);
            mols[0].dy *= Math.sqrt(molCount);
            setMoleculeTypes(2, 1);
        }

        Setup createNext() {
            return new Setup1Small();
        }
    }

    class Setup1Small extends Setup {

        String getName() {
            return "1 Gas, Small";
        }

        void select() {
            colorBar.setValue(215);
            speedBar.setValue(36);
        }

        void reinit() {
            initMolecules(SPEED_RANDOM);
            setMoleculeTypes(1, 1);
        }

        Setup createNext() {
            return new Setup2Random();
        }
    }

    class Setup2Random extends Setup {

        String getName() {
            return "2 Gases, Random Speeds";
        }

        void reinit() {
            initMolecules(SPEED_RANDOM);
            setMoleculeTypes(1, 2);
        }

        int getHistogramCount() {
            return 2;
        }

        Setup createNext() {
            return new Setup2Equal();
        }
    }

    class Setup2Equal extends Setup {

        String getName() {
            return "2 Gases, Equal Speeds";
        }

        void select() {
            speedBar.setValue(3);
        }

        void reinit() {
            initMolecules(SPEED_EQUAL);
            setMoleculeTypes(1, 2);
        }

        int getHistogramCount() {
            return 2;
        }

        Setup createNext() {
            return new Setup3Random();
        }
    }

    class Setup3Random extends Setup {

        String getName() {
            return "3 Gases, Random Speeds";
        }

        void reinit() {
            initMolecules(SPEED_RANDOM);
            setMoleculeTypes(1, 3);
        }

        int getHistogramCount() {
            return 3;
        }

        Setup createNext() {
            return new Setup3Equal();
        }
    }

    class Setup3Equal extends Setup {

        String getName() {
            return "3 Gases, Equal Speeds";
        }

        void select() {
            speedBar.setValue(3);
        }

        void reinit() {
            initMolecules(SPEED_EQUAL);
            setMoleculeTypes(1, 3);
        }

        int getHistogramCount() {
            return 3;
        }

        Setup createNext() {
            return new SetupBrownian();
        }
    }

    class SetupBrownian extends Setup {

        String getName() {
            return "Brownian Motion";
        }

        void select() {
            speedBar.setValue(70);
            colorBar.setValue(210);
        }

        void reinit() {
            initMolecules(SPEED_RANDOM);
            bigmol = mols[0];
            bigmol.r = 30;
            bigmol.mass = bigmol.r * bigmol.r / 2;
            bigmol.dx = bigmol.dy = 0;
        }

        Setup createNext() {
            return new SetupExpansion();
        }
    }

    class SetupExpansion extends Setup {

        String getName() {
            return "Free Expansion";
        }

        void select() {
            molCountBar.setValue(250);
            speedBar.setValue(45);
            colorBar.setValue(210);
        }

        void reinit() {
            initMolecules(SPEED_RANDOM);
            setMoleculeTypes(1, 1);
        }

        double getVolume() {
            return .5;
        }

        Setup createNext() {
            return null;
        }
    }
}

*s3.postimg.org/uolpzy1bz/Gas.png



6) Write a Image puzzle in Java?

Ans) The Image is cut into smaller tiles by the program.
The program can shuffle the images or solve the image puzzle!!

Code:
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

/**
 * Created with IntelliJ IDEA.
 * User: Sowndar
 * Date: 9/3/15
 * Time: 1:36 PM
 * To change this template use File | Settings | File Templates.
 */
public class ImagePuzzle extends JPanel {

    private BufferedImage bufferedImage;
    private String path = "Images/";
    private String fileName = "Kimberley Rogers4.jpg";
    private int iconWidth, iconHeight;
    private int imgWidth, imgHeight;
    private int cols, rows;
    private int MAX;
    private JButton shuffle = new JButton("Shuffle");
    private JButton solve = new JButton("Solve");
    private static JPanel bottom = new JPanel(new FlowLayout(FlowLayout.CENTER));
    private Image[] cell;
    private boolean isShuffle = true;
    private int width, height;

    public ImagePuzzle() {
        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (Exception e) {

        }
        shuffle.updateUI();
        solve.updateUI();
        // Load the Image
        bufferedImage = getImage(path + fileName);
        if (bufferedImage != null) {
            imgWidth = bufferedImage.getWidth(this);
            imgHeight = bufferedImage.getHeight(this);
            iconWidth = imgWidth / 8;
            iconHeight = imgHeight / 8;
            // How many columns , rows for this Image dimension ?
            cols = imgWidth / iconWidth;
            rows = imgHeight / iconHeight;
            MAX = (rows * cols);
            cell = new Image[MAX];
            // Store the image pieces in the Image array
            makePieces();
            setPreferredSize(new Dimension(bufferedImage.getWidth() - 8, bufferedImage.getHeight()));
            bottom.add(shuffle);
            bottom.add(solve);
        }
        shuffle.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                isShuffle = true;
                repaint();
                solve.setEnabled(true);
            }
        });

        solve.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                isShuffle = false;
                repaint();
                solve.setEnabled(false);
            }
        });
    }

    public BufferedImage getImage(String fileName) {

        try {
            return ImageIO.read(new File(fileName));
        } catch (IOException ioe) {
            System.err.println("Error loading Image!!");
            System.exit(-1);
        }
        return null;
    }

    // Cut the image into several pieces and store it in a array
    public void makePieces() {
        int X = 0;
        int Y = -iconHeight;

        // Assign the image pieces to the Image array
        for (int j = 0; j < MAX; j++) {
            if (j % cols == 0) {
                // Move to the next row starting position for displaying the next cols Images
                X = 0;
                Y += iconHeight;
            }
            cell[j] = bufferedImage.getSubimage(X, Y, iconWidth, iconHeight);

            X += iconWidth;
        }
    }

    public void shuffle(Graphics2D g2d) {
        // Shuffle the Images
        for (int i = 0; i < MAX; i++) {
            // Pick a random element from the array
            int si = (int) (Math.random() * MAX);
            // Pick a random element from the array
            int di = (int) (Math.random() * MAX);
            // Now swap these two
            Image tmp = cell[si];
            cell[si] = cell[di];
            cell[di] = tmp;
        }
        int X = 0;
        int Y = -iconHeight;
        Image image = null;

        for (int i = 0; i < MAX; i++) {
            image = cell[i];
            if (i % cols == 0) {
                // Move to the next row starting position for displaying the next cols Images
                X = 0;
                Y += iconHeight;
            }
            if (image != null) {
                g2d.drawImage(image, X, Y, this);
                g2d.drawRect(X, Y, iconWidth, iconHeight);
            }
            X += iconWidth;
        }
    }

    public void solve(Graphics2D g2d) {
        int X = 0;
        int Y = -iconHeight;
        Image image = null;

        for (int j = 0; j < MAX; j++) {
            if (j % cols == 0) {
                // Move to the next row starting position for displaying the next cols Images
                X = 0;
                Y += iconHeight;
            }
            image = bufferedImage.getSubimage(X, Y, iconWidth, iconHeight);
            if (image != null) {
                g2d.drawImage(image, X, Y, this);
                g2d.drawRect(X, Y, iconWidth, iconHeight);
            }
            X += iconWidth;
        }
        g2d.setFont(new Font("Helvetica", Font.BOLD, 20));
        g2d.setColor(Color.blue);
        String message = "Puzzle solved!!";
        //Center the text message on the screen
        X = (width - g2d.getFontMetrics().stringWidth(message)) / 2;
        Y = height / 2;
        g2d.setColor(Color.blue);
        g2d.drawString(message, X - 1, Y - 1);
        g2d.drawString(message, X - 1, Y + 1);
        g2d.drawString(message, X + 1, Y - 1);
        g2d.drawString(message, X + 1, Y + 1);
        g2d.setColor(Color.cyan);
        g2d.drawString(message, X, Y);
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        width = getWidth();
        height = getHeight();
        Graphics2D g2d = (Graphics2D) g;
        // Set the rendering hints
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
        g2d.setStroke(new BasicStroke(1.5f));
        g2d.setColor(Color.black);

        if (isShuffle) {
            shuffle(g2d);
        } else {
            solve(g2d);
        }
    }

    void sleep(long ms) {
        try {
            Thread.sleep(ms);
        } catch (InterruptedException ie) {

        }
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame f = new JFrame("ImagePuzzle");
                f.add(new ImagePuzzle());
                f.add(bottom, BorderLayout.SOUTH);
                f.pack();
                f.setResizable(false);
                f.setVisible(true);
                f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
            }
        });
    }
}

*s3.postimg.org/3kisjyoi7/Image_Puzzle.png

7) Write a Particle System applet that flows like a fountain!!

Code:
/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
/*
 * Copyright Jerry Huxtable 1999
 *
 * Feel free to do anything you like with this code.
 */
import com.jhlabs.image.Gradient;
import com.jhlabs.image.ImageMath;
import java.applet.Applet;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.ColorModel;
import java.awt.image.IndexColorModel;
import java.awt.image.MemoryImageSource;
import java.io.IOException;
import java.io.ObjectInputStream;

/**
 * An applet which animates a simple particle system.
 */
public class ParticleApplet extends Applet implements Runnable, MouseListener {

    private static final long serialVersionUID = 1L;

    protected MemoryImageSource source;
    protected Image image;
    protected boolean newImage = true;
    protected ColorModel colorModel;
    protected Thread thread;
    protected byte[] pixels1, pixels2;
    protected int width, height;
    protected Image offscreen;
    protected Graphics offscreenG;
    public Action[] actions;
    protected Filter[] filters;
    protected Object[] gradients;
    private boolean running = false;
    private boolean startAnimation = false;

    public ParticleApplet() {
    }

    @Override
    public void init() {
        Dimension size = getSize();
        width = size.width;
        height = size.height;
        try {
            try (ObjectInputStream ois = new ObjectInputStream(getClass().getResourceAsStream("gradients.ser"))) {
                gradients = (Object[]) ois.readObject();
            }
        } catch (IOException | ClassNotFoundException e) {
        }
        addMouseListener(this);
        setGradient(3);
        int numFilters = 8;
        filters = new Filter[numFilters];
        filters[0] = new ShiftDownFilter();
        filters[1] = new ShiftUpFilter();
        filters[2] = new ConvolveFilter();
        filters[3] = new BlurHFilter();
        filters[4] = new BlurVFilter();
        filters[5] = new ThingFilter();
        filters[6] = new WaterFilter();
        filters[7] = new ClearFilter();
        filters[3].setEnabled(true);
        filters[4].setEnabled(true);

        int numActions = 1;
        actions = new Action[numActions];
        int numParticles = getIntParameter("numParticles", 100);
        Particles particles = new Particles(numParticles, width / 2, height / 2, width, height);
        actions[0] = particles;
        actions[0].setEnabled(true);
        particles.rate = getIntParameter("rate", 100);
        particles.speed = (getIntParameter("speed", 0) << 8) / 10;
        particles.angle = getIntParameter("angle", 0);
        particles.spread = getIntParameter("spread", 360);
        particles.gravity = getIntParameter("gravity", 0);
        particles.color = getIntParameter("color", 255);
        particles.scatter = getIntParameter("scatter", 0);
        particles.hscatter = getIntParameter("hscatter", 0);
        particles.vscatter = getIntParameter("vscatter", 0);
        particles.randomness = getIntParameter("randomness", 0);
        particles.size = getIntParameter("size", 2);
        particles.x = getIntParameter("x", width / 2);
        particles.y = getIntParameter("y", height / 2);
        particles.lifetime = getIntParameter("lifetime", 50);
        particles.speedVariation = (getIntParameter("speedVariation", 0) << 8) / 10;
        particles.decay = getIntParameter("decay", 0);
        startAnimation = getIntParameter("startAnimation", 1) != 0;
        int colormap = getIntParameter("colormap", 4);
        setGradient(colormap);
        String actions = getParameter("actions");
        if (actions != null) {
            String s = "duchvtwc";
            for (int i = 0; i < s.length(); i++) {
                filters[i].setEnabled(actions.indexOf(s.charAt(i)) != -1);
            }
        }
    }

    private int getIntParameter(String name, int defaultValue) {
        if (name != null) {
            try {
                return Integer.parseInt(getParameter(name));
            } catch (NumberFormatException e) {
            }
        }
        return defaultValue;
    }

    public void setGradient(int n) {
        n = Math.max(0, Math.min(n, gradients.length - 1));
        byte[] r = new byte[256];
        byte[] g = new byte[256];
        byte[] b = new byte[256];
        for (int i = 0; i < 256; i++) {
            int rgb = ((Gradient) gradients[n]).getColor((float) (i / 255.0));
            r[i] = (byte) ((rgb >> 16) & 0xff);
            g[i] = (byte) ((rgb >> 8) & 0xff);
            b[i] = (byte) (rgb & 0xff);
        }
        colorModel = new IndexColorModel(8, 256, r, g, b);
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(width, height);
    }

    @Override
    public void start() {
        if (startAnimation && thread == null) {
            thread = new Thread(this);
            thread.start();
        }
    }

    @Override
     [MENTION=139512]Sup[/MENTION]pressWarnings("deprecation")
    public void stop() {
        if (thread != null) {
            thread.stop();
            thread = null;
        }
    }

    @Override
    public void run() {
        try {
            running = true;
            while (running) {
                repaintImage();

                Thread.sleep(5);
            }
        } catch (InterruptedException e) {
        }
        thread = null;
    }

    @Override
    public void update(Graphics g) {
        paint(g);
    }

    @Override
    public synchronized void paint(Graphics g) {
        Dimension size = getSize();
        int w = size.width;
        int h = size.height;
        if (newImage || image == null) {
            image = makeImage(w, h);
        }
        g.drawImage(image, 0, 0, this);
        if (!running) {
            String s = "Click to Start";
            FontMetrics fm = g.getFontMetrics();
            int x = (size.width - fm.stringWidth(s)) / 2;
            int y = (size.height - fm.getAscent()) / 2;
            g.setColor(Color.white);
            g.drawString(s, x, y);
        }
        notify();
    }

    private Image makeImage(int w, int h) {
        if (pixels1 == null) {
            int i = 0;
            pixels1 = new byte[width * height];
            pixels2 = new byte[width * height];
            for (i = 0; i < actions.length; i++) {
                if (actions[i].isEnabled()) {
                    actions[i].apply(pixels1, width, height);
                }
            }
        } else {
            for (int i = 0; i < filters.length; i++) {
                if (filters[i].isEnabled()) {
                    filters[i].apply(pixels1, pixels2, width, height);
                    byte[] t = pixels1;
                    pixels1 = pixels2;
                    pixels2 = t;
                }
            }
            for (int i = 0; i < actions.length; i++) {
                if (actions[i].isEnabled()) {
                    actions[i].apply(pixels1, width, height);
                }
            }
        }
        newImage = false;
        if (image == null) {
            image = createImage(source = new MemoryImageSource(w, h, colorModel, pixels1, 0, w));
            source.setAnimated(true);
        } else {
            source.newPixels(pixels1, colorModel, 0, w);
        }
        return image;
    }

    private void repaintImage() {
        newImage = true;
        repaint();
    }

    @Override
    public void mousePressed(MouseEvent e) {
        requestFocus();
        startAnimation = true;
        if (thread == null && !running) {
            start();
        } else {
            running = false;
        }
    }

    @Override
    public void mouseReleased(MouseEvent e) {
    }

    @Override
    public void mouseClicked(MouseEvent e) {
    }

    @Override
    public void mouseEntered(MouseEvent e) {
    }

    @Override
    public void mouseExited(MouseEvent e) {
    }

}

class Thing {

    private boolean enabled;

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    public boolean isEnabled() {
        return enabled;
    }
}

class Action extends Thing {

    public void apply(byte[] pixels, int width, int height) {
    }
}

class Filter extends Action {

    public void apply(byte[] in, byte[] out, int width, int height) {
    }
}

/**
 * A particle system. We use fixed point integer maths with 8 fractional bits
 * everywhere for speed, hence the frequent shifts by 8.
 */
class Particles extends Action {

    public Particle[] particles;
    public int rate = 100;
    public int angle = 0;	// 0 degrees is north
    public int spread = 90;
    public int gravity = (1 << 8);
    public int lifetime = 65;
    public int scatter = 0;
    public int hscatter = 0;
    public int vscatter = 0;
    public int x;
    public int y;
    public int speed = 1;
    public int size;
    public int width;
    public int height;
    public int speedVariation = 0;
    public int decay = 0;
    public int randomness = (7 << 8);
    public int color;
    public int numParticles;
    private static int[] sinTable, cosTable;

    static {
        sinTable = new int[360];
        cosTable = new int[360];
        for (int i = 0; i < 360; i++) {
            double angle = 2 * Math.PI * i / 360;
            sinTable[i] = (int) (256 * Math.sin(angle));
            cosTable[i] = (int) (256 * Math.cos(angle));
        }
    }

    public Particles(int numParticles, int x, int y, int width, int height) {
        this.numParticles = numParticles;
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
        particles = new Particle[numParticles];
        for (int i = 0; i < numParticles; i++) {
            particles[i] = new Particle(this, width, height);
            newParticle(particles[i]);
        }
    }

    public double gaussian() {
        double sum = 0;
        for (int i = 0; i < 12; i++) {
            sum += Math.random();
        }
        return (sum - 6) / 3.0;
    }

    public void newParticle(Particle particle) {
        particle.color = color;
        particle.size = size;
        particle.lifetime = (int) (Math.random() * lifetime);
        particle.randomness = randomness;
        particle.x = x;
        particle.y = y;
        if (scatter != 0) {
            int a = ((int) (Math.random() * 360)) % 360;
            double distance = scatter * Math.random() / 256;
            particle.x += (int) (cosTable[a] * distance);
            particle.y += (int) (-sinTable[a] * distance);
        }
        if (hscatter != 0) {
            particle.x += (int) (hscatter * (Math.random() - 0.5));
        }
        if (vscatter != 0) {
            particle.y += (int) (vscatter * (Math.random() - 0.5));
        }
        int a = (angle + 450 - spread / 2 + (int) (Math.random() * spread)) % 360;
        int s = speed + (int) (speedVariation * gaussian());
        particle.vx = ((cosTable[a] * s) >> 8);
        particle.vy = -((sinTable[a] * s) >> 8);

        particle.x <<= 8;
        particle.y <<= 8;
    }

    @Override
    public void apply(byte[] pixels, int width, int height) {
        for (int i = 0; i < particles.length; i++) {
            Particle p = particles[i];
            if (p.lifetime < 0) {
                newParticle(p);
            }
            p.paint(pixels, width, height);
            p.move(width, height);
            p.color -= decay;
            if (p.color < 0) {
                p.color = 0;
            }
        }
    }

    @Override
    public String toString() {
        return "Particles";
    }
}

class Particle {

    protected int x, y;
    protected int vx, vy;
    public int size;
    public int color = 255;
    public int randomness = 0;
    public int lifetime = -1;
    private Particles particles;

    public Particle(Particles particles, int width, int height) {
        this.particles = particles;
    }

    public void move(int width, int height) {
        if (randomness != 0) {
            vx += (int) (Math.random() * randomness) - randomness / 2;
            vy += (int) (Math.random() * randomness) - randomness / 2;
        }
        x += vx;
        y += vy;
        vy += particles.gravity;
        lifetime--;
    }

    /*
     * How to draw circles of small sizes
     */
    public int[] circle1 = {0, 1};
    public int[] circle3 = {0, 1, -1, 3, 0, 1};
    public int[] circle5 = {-1, 3, -2, 5, -2, 5, -2, 5, -1, 3};
    public int[] circle7 = {-1, 3, -2, 5, -3, 7, -3, 7, -3, 7, -2, 5, -1, 3};
    public int[][] circles = {circle1, circle3, circle5, circle7};

    public void paint(byte[] pixels, int width, int height) {
        byte pixel = (byte) color;
        int[] c = circles[Math.min(size, circles.length)];
        int my = (y >> 8) - size;
        for (int i = 0; i < c.length; i += 2, my++) {
            if (my < 0) {
                continue;
            } else if (my >= height) {
                break;
            }
            int x1 = Math.max(0, (x >> 8) + c[i]);
            int x2 = Math.min(width - 1, x1 + c[i + 1]);
            int j = my * width + x1;
            for (int mx = x1; mx <= x2; mx++) {
                pixels[j++] = pixel;
            }
        }
    }
}

/**
 * Scroll the image down
 */
class ShiftDownFilter extends Filter {

    @Override
    public void apply(byte[] in, byte[] out, int width, int height) {
        int i = 0;
        int j = 0;
        for (int x = 0; x < width; x++) {
            out[j++] = 0;
        }
        for (int y = 1; y < height; y++) {
            for (int x = 0; x < width; x++) {
                out[j++] = in[i++];
            }
        }
    }

    @Override
    public String toString() {
        return "Move Down";
    }
}

/**
 * Scroll the image up
 */
class ShiftUpFilter extends Filter {

    @Override
    public void apply(byte[] in, byte[] out, int width, int height) {
        int i = width * height - 1;
        int j = i;
        for (int x = 0; x < width; x++) {
            out[j--] = 0;
        }
        for (int y = 1; y < height; y++) {
            for (int x = 0; x < width; x++) {
                out[j--] = in[i--];
            }
        }
    }

    @Override
    public String toString() {
        return "Move Up";
    }
}

/**
 * Zoom the image out to the left and right
 */
class ZoomInVFilter extends Filter {

    @Override
    public void apply(byte[] in, byte[] out, int width, int height) {
        int i = 0;
        int j = 0;
        int height2 = height / 2;
        for (int x = 0; x < width; x++) {
            out[j++] = 0;
        }
        j = width;
        for (int y = 1; y < height2; y++) {
            for (int x = 0; x < width; x++) {
                out[j++] = in[i++];
            }
        }
        i += 2 * width;
        for (int y = 1; y < height2; y++) {
            for (int x = 0; x < width; x++) {
                out[j++] = in[i++];
            }
        }
        for (int x = 0; x < width; x++) {
            out[j++] = 0;
        }
    }

    @Override
    public String toString() {
        return "Move In Vertical";
    }
}

/**
 * Zoom the image out sideways
 */
class ZoomInHFilter extends Filter {

    @Override
    public void apply(byte[] in, byte[] out, int width, int height) {
        int i = 0;
        int j = 0;
        int height2 = height / 2;
        for (int x = 0; x < width; x++) {
            out[j++] = 0;
        }
        j = width;
        for (int y = 1; y < height2; y++) {
            for (int x = 0; x < width; x++) {
                out[j++] = in[i++];
            }
        }
        i += 2 * width;
        for (int y = 1; y < height2; y++) {
            for (int x = 0; x < width; x++) {
                out[j++] = in[i++];
            }
        }
        for (int x = 0; x < width; x++) {
            out[j++] = 0;
        }
    }

    @Override
    public String toString() {
        return "Move In Horizontal";
    }
}

/**
 * Do a general convolution on the image (this is much slower than the blur
 * filters)
 */
class ConvolveFilter extends Filter {

    protected int[] kernel = {
        -3, 0, 0, 0, -3,
        0, 0, 0, 0, 0,
        -3, 0, 50, 0, -3,
        0, 0, 0, 0, 0,
        -3, 0, 0, 0, -3,};
    int target = 40;

    public ConvolveFilter() {
    }

    public ConvolveFilter(int[] kernel, int target) {
        this.kernel = kernel;
        this.target = target;
    }

    @Override
    public void apply(byte[] in, byte[] out, int width, int height) {
        int index = 0;
        int rows = 5;
        int cols = 5;
        int rows2 = rows / 2;
        int cols2 = cols / 2;

        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                int t = 0;

                for (int row = -rows2; row <= rows2; row++) {
                    int iy = y + row;
                    int ioffset;
                    if (0 <= iy && iy < height) {
                        ioffset = iy * width;
                    } else {
                        ioffset = y * width;
                    }
                    int moffset = cols * (row + rows2) + cols2;
                    for (int col = -cols2; col <= cols2; col++) {
                        int f = kernel[moffset + col];

                        if (f != 0) {
                            int ix = x + col;
                            if (!(0 <= ix && ix < width)) {
                                ix = x;
                            }
                            t += f * (in[ioffset + ix] & 0xff);
                        }
                    }
                }
                t /= target;
                if (t > 255) {
                    t = 255;
                }
                out[index++] = (byte) t;
            }
        }
    }

    @Override
    public String toString() {
        return "Convolve";
    }
}

/**
 * Blur horizontally
 */
class BlurHFilter extends Filter {

    @Override
    public void apply(byte[] in, byte[] out, int width, int height) {
        for (int y = 0; y < height; y++) {
            int index = y * width;
            out[index] = (byte) (((in[index] & 0xff) + (in[index + 1] & 0xff)) / 3);
            index++;
            for (int x = 1; x < width - 1; x++) {
                out[index] = (byte) (((in[index - 1] & 0xff) + (in[index] & 0xff) + (in[index + 1] & 0xff)) / 3);
                index++;
            }
            out[index] = (byte) (((in[index - 1] & 0xff) + (in[index] & 0xff)) / 3);
        }
    }

    @Override
    public String toString() {
        return "Blur Horizontally";
    }
}

/**
 * Blur vertically
 */
class BlurVFilter extends Filter {

    @Override
    public void apply(byte[] in, byte[] out, int width, int height) {
        for (int x = 0; x < width; x++) {
            int index = x;
            out[index] = (byte) (((in[index] & 0xff) + (in[index + width] & 0xff)) / 3);
            index += width;
            for (int y = 1; y < height - 1; y++) {
                out[index] = (byte) (((in[index - width] & 0xff) + (in[index] & 0xff) + (in[index + width] & 0xff)) / 3);
                index += width;
            }
            out[index] = (byte) (((in[index - width] & 0xff) + (in[index] & 0xff)) / 3);
        }
    }

    @Override
    public String toString() {
        return "Blur Vertically";
    }
}

/**
 * A sort of water-ripple type effect
 */
class WaterFilter extends Filter {

    @Override
    public void apply(byte[] in, byte[] out, int width, int height) {
        for (int y = 1; y < height - 1; y++) {
            int index = y * width;
            index++;
            for (int x = 1; x < width - 1; x++) {
                int n = (byte) (((in[index - 1] & 0xff) + (in[index + 1] & 0xff) + (in[index - width] & 0xff) + (in[index + width] & 0xff)) / 2
                        - (out[index] & 0xff));
                n -= (byte) (n >> 6);
                out[index] = (byte) ImageMath.clamp(n, 0, 255);
                index++;
            }
            index++;
        }
    }

    @Override
    public String toString() {
        return "Ripple";
    }
}

/**
 * Clears the image to black - you only see the moving particles with this one.
 */
class ClearFilter extends Filter {

    @Override
    public void apply(byte[] in, byte[] out, int width, int height) {
        int index = 0;
        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                out[index] = 0;
                index++;
            }
        }
    }

    @Override
    public String toString() {
        return "Clear";
    }
}

class ThingFilter extends ConvolveFilter {

    protected static int[] kernel = {
        10, 0, 0, 0, 10,
        0, 10, 0, 10, 0,
        0, 0, 10, 0, 0,
        0, 10, 0, 10, 0,
        10, 0, 0, 0, 10,};

    public ThingFilter() {
        super(kernel, 120);
    }

    @Override
    public String toString() {
        return "Thing";
    }
}


*s3.postimg.org/kezh5uu4v/Particle_System.png

particlesystem.html

Code:
<HTML>
<HEAD>
   <TITLE>Applet HTML Page</TITLE>
</HEAD>
<BODY>

<!--
*** GENERATED applet HTML launcher - DO NOT EDIT IN 'BUILD' FOLDER ***

If you need to modify this HTML launcher file (e.g., to add applet parameters),
copy it to where your applet class is found in the SRC folder. If you do this,
the IDE will use it when you run or debug the applet.

Tip: To exclude an HTML launcher from the JAR file, use exclusion filters in
the Packaging page in the Project Properties dialog.

For more information see the online help.
-->

<H3><HR WIDTH="100%">Applet HTML Page<HR WIDTH="100%"></H3>

<P>
<APPLET codebase="classes" code="ParticleApplet.class" width=680 height=600>

        <param name="archive" value="Particles.jar" />
        <param name="numParticles" value="500" />
        <param name="rate" value="10" />
        <param name="speed" value="50" />
        <param name="speedVariation" value="10" />
        <param name="angle" value="0" />
        <param name="spread" value="40" />
        <param name="gravity" value="20" />
        <param name="size" value="0" />
        <param name="lifetime" value="200" />
      </APPLET>

</P>

<HR WIDTH="100%"><FONT SIZE=-1><I>Generated by NetBeans IDE</I></FONT>
</BODY>
</HTML>

8) Write a 3D Photos viewer using Java3D?
Also use previous , next, zoom buttons to display the images.

Code:
/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
import java.awt.*;
import java.io.*;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.beans.*;
import java.util.*;
import javax.media.opengl.*;
import javax.vecmath.*;
import javax.media.opengl.glu.GLU;
import java.awt.event.*;
import java.awt.geom.GeneralPath;
import java.text.NumberFormat;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.border.EmptyBorder;
import java.awt.image.BufferedImage;
import java.awt.event.*;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import java.util.concurrent.ConcurrentLinkedQueue;
import javax.media.opengl.glu.GLU;
import java.net.URL;
import javax.imageio.ImageIO;
import java.awt.image.*;
import java.awt.color.ColorSpace;
import java.awt.image.*;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import javax.imageio.ImageIO;


public class Photos3dViewer extends JFrame {

    private PictureViewer viewer = new PictureViewer();

    public Photos3dViewer() {
        super("Photos3dViewer");
        add(BorderLayout.CENTER, viewer);
        Dimension scrDim = Toolkit.getDefaultToolkit().getScreenSize();
        setSize(scrDim.width - 150, scrDim.height - 50);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
        setVisible(true);
    }

    private void loadPictures() {
        Thread loader = new Thread(new PicturesLoader(), "Pictures Loader");
        loader.start();
    }

    private final class PicturesLoader implements Runnable {

        @Override
        public void run() {
            String path = "resources/";
            File file = new File(path);
            String[] imgName = file.list(new FilenameFilter() {

                String[] readFormat = ImageIO.getReaderFormatNames();

                @Override
                public boolean accept(File dir, String name) {
                    for (int i = 0; i < readFormat.length; i++) {
                        if (name.endsWith(readFormat[i])) {
                            return true;
                        }
                    }
                    return false;
                }
            });
            int MAX = imgName.length;
            for (int i = 0; i < MAX; i++) {
                String name = imgName[i];
                name = name.substring(0, name.lastIndexOf("."));
                String fullPath = path + imgName[i];

                try {
                    viewer.addPicture(name,
                            ImageIO.read(getClass().getResourceAsStream(fullPath)));
                } catch (IOException e) {
                    System.err.println("Error loading Images!!");
                }
            }
        }
    }

    public static void main(String args[]) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new Photos3dViewer().loadPictures();
            }
        });
    }
}


abstract class AbstractEquation implements Equation {

    protected java.util.List<PropertyChangeListener> listeners;

    protected AbstractEquation() {
        this.listeners = new LinkedList<PropertyChangeListener>();
    }

    public void addPropertyChangeListener(PropertyChangeListener listener) {
        if (listener != null && !listeners.contains(listener)) {
            listeners.add(listener);
        }
    }

    public void removePropertyChangeListener(PropertyChangeListener listener) {
        if (listener != null) {
            listeners.remove(listener);
        }
    }

    protected void firePropertyChange(String propertyName,
            double oldValue,
            double newValue) {
        PropertyChangeEvent changeEvent = new PropertyChangeEvent(this,
                propertyName,
                oldValue,
                newValue);
        for (PropertyChangeListener listener : listeners) {
            listener.propertyChange(changeEvent);
        }
    }
}

class AbstractSimulator extends JComponent implements PropertyChangeListener {

    protected AbstractEquation equation;
    protected double time;
    protected int timeScale;

    public AbstractSimulator(AbstractEquation equation) {
        this.equation = equation;
        this.equation.addPropertyChangeListener(this);
        this.time = 0.0f;
        this.timeScale = 1;
    }

    public void propertyChange(PropertyChangeEvent evt) {
        repaint();
    }

    public void setTime(double time) {
        this.time = time;
        repaint();
    }

    public double getTime() {
        return this.time;
    }

    public void setTimeScale(int timeScale) {
        this.timeScale = timeScale;
        repaint();
    }

    public int getTimeScale() {
        return this.timeScale;
    }
}


class AnimationEquation extends AbstractEquation {

    public static final String PROPERTY_PHASE = "phase";
    public static final String PROPERTY_SIGMA = "sigma";

    // exposed parameters
    private double sigma;
    private double phase;

    // internal
    private double lowerStitch;

    public AnimationEquation(double sigma, double phase) {
        this.sigma = sigma;
        this.phase = phase;

        lowerStitch = Math.exp(phase * sigma) / 2.0;
    }

    public double compute(double x) {
        double value;

        if (x <= 0.5) {
            value = Math.exp((x * 2 + phase) * sigma) / 2.0;
            value -= lowerStitch;
        } else {
            value = 1.0 - Math.exp(((1.0 - x) * 2 + phase) * sigma) / 2.0;
            value += lowerStitch;
        }

        if (value > 1.0) {
            value = 1.0;
        } else if (value < 0.0) {
            value = 0.0;
        }

        return value;
    }

    public double getSigma() {
        return sigma;
    }

    public void setSigma(double sigma) {
        double oldValue = this.sigma;
        this.sigma = sigma;
        lowerStitch = Math.exp(phase * sigma) / 2.0;
        firePropertyChange(PROPERTY_SIGMA, oldValue, sigma);
    }

    public double getPhase() {
        return phase;
    }

    public void setPhase(double phase) {
        double oldValue = this.phase;
        this.phase = phase;
        lowerStitch = Math.exp(phase * sigma) / 2.0;
        firePropertyChange(PROPERTY_PHASE, oldValue, phase);
    }
}


class AnimationSetupFrame extends JFrame {

    private AnimationEquation gauss;

    public AnimationSetupFrame(AnimationEquation gauss) throws HeadlessException {
        super("Twinkle - Setup");
        this.gauss = gauss;

        add(buildHeader(), BorderLayout.NORTH);
        add(buildControlPanel(), BorderLayout.CENTER);

        pack();
        setLocationRelativeTo(null);

        setDefaultCloseOperation(DISPOSE_ON_CLOSE);
    }

    private Component buildHeader() {
        ImageIcon icon = new ImageIcon(getClass().getResource("*forum.digit.in/images/simulator.png"));
        HeaderPanel header = new HeaderPanel(icon,
                "Animation Setup",
                "Fine-tune animation curve using the controls on the right.",
                "The reading of the parameter sigma is to be used in Twinkle.");
        return header;
    }

    private Component buildControlPanel() {
        return new EquationsControlPanel(gauss);
    }

    public static void main(String[] args) {
        AnimationEquation gauss = new AnimationEquation(3.6, -1.0);
        AnimationSetupFrame frame = new AnimationSetupFrame(gauss);
        frame.setVisible(true);
    }
}

class Billboard extends Renderable {

    private Renderable item;

    public Billboard(Renderable item) {
        this.item = item;
        setPosition(0.0f, 0.0f, 0.0f);
        setScale(1.0f, 1.0f, 1.0f);
        setRotation(0, 0, 0);
    }

    public void init(GL gl) {
        item.init(gl);
    }

    public Renderable getItem() {
        return item;
    }

    public void setItem(Renderable item) {
        this.item = item;
    }

    public void render(GL gl) {
        Vector3f camPos = new Vector3f();
        Vector3f camUp = new Vector3f();
        GLUtilities.getCameraVectors(gl, camPos, camUp);
        gl.glPushMatrix();
        GLUtilities.renderBillboard(gl, camPos, camUp, item);
        gl.glPopMatrix();
    }

    public Point3f getPosition() {
        return item.getPosition();
    }

    public void setPosition(Point3f position) {
        if (item == null) {
            return;
        }
        item.setPosition(position);
    }

    public void setPosition(float x, float y, float z) {
        if (item == null) {
            return;
        }
        item.setPosition(x, y, z);
    }

    public void setPosition(float[] coordinates) {
        if (item == null) {
            return;
        }
        item.setPosition(coordinates);
    }

    public Point3i getRotation() {
        return new Point3i(0, 0, 0);
    }

    public Point3f getScale() {
        return item.getScale();
    }

    public void setRotation(int x, int y, int z) {
    }

    public void setRotation(int[] coordinates) {
    }

    public void setRotation(Point3i rot) {
    }

    public void setScale(float x, float y, float z) {
        if (item == null) {
            return;
        }
        item.setScale(x, y, z);
    }

    public void setScale(float[] coordinates) {
        if (item == null) {
            return;
        }
        item.setScale(coordinates);
    }

    public void setScale(Point3f scale) {
        if (item == null) {
            return;
        }
        item.setScale(scale);
    }
}

class CompositeGLPanel extends GLJPanel implements GLEventListener {

    private static final GLU glu = new GLU();

    private boolean hasDepth;

    public CompositeGLPanel(boolean isOpaque, boolean hasDepth) {
        super(getCaps(isOpaque), null, null);
        setOpaque(isOpaque);
        this.hasDepth = hasDepth;
        addGLEventListener(this);
    }

    private static GLCapabilities getCaps(boolean opaque) {
        GLCapabilities caps = new GLCapabilities();

        if (!opaque) {
            caps.setAlphaBits(8);
        }

        return caps;
    }

    @Override
    public void paintComponent(Graphics g) {
        render2DBackground(g);
        super.paintComponent(g);
        render2DForeground(g);
    }

    public void init(GLAutoDrawable drawable) {
        GL gl = drawable.getGL();
        if (hasDepth) {
            gl.glEnable(GL.GL_DEPTH_TEST);
        }
        gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    }

    protected void render2DBackground(Graphics g) {
    }

    protected void render3DScene(GL gl, GLU glu) {
    }

    protected void render2DForeground(Graphics g) {
    }

    @Override
    public void display(GLAutoDrawable drawable) {
        GL gl = drawable.getGL();
        int clearBits = 0;
        if (hasDepth) {
            clearBits |= GL.GL_DEPTH_BUFFER_BIT;
        }
        if (!shouldPreserveColorBufferIfTranslucent()) {
            clearBits |= GL.GL_COLOR_BUFFER_BIT;
        }
        if (clearBits != 0) {
            gl.glClear(clearBits);
        }
        render3DScene(gl, glu);
    }

    @Override
    public void reshape(GLAutoDrawable drawable,
            int x, int y, int width, int height) {
        GL gl = drawable.getGL();

        gl.glViewport(0, 0, width, height);
        gl.glMatrixMode(GL.GL_PROJECTION);
        gl.glLoadIdentity();
        if (hasDepth) {
            double aspectRatio = (double) width / (double) height;
            glu.gluPerspective(45.0, aspectRatio, 1.0, 400.0);
        } else {
            gl.glOrtho(0.0, width, height, 0.0, -100.0, 100.0);
        }

        gl.glMatrixMode(GL.GL_MODELVIEW);
        gl.glLoadIdentity();
    }

    @Override
    public void displayChanged(GLAutoDrawable drawable,
            boolean modeChanged, boolean deviceChanged) {
    }
}

final class Debug {

    private static final boolean DEBUG = false;

    public static boolean isDebug() {
        return DEBUG;
    }
}

class DepthComparator implements Comparator<Renderable> {

    private Point3f camPos;
    private Matrix4f view;

    public DepthComparator(GL gl) {
        float[] matrix = new float[16];
        gl.glGetFloatv(GL.GL_MODELVIEW_MATRIX, matrix, 0);
        camPos = new Point3f(-matrix[12], -matrix[13], -matrix[14]);
        view = new Matrix4f(matrix);
        view.transpose();
    }

    public int compare(Renderable r1, Renderable r2) {
        if (r1 == null) {
            return -1;
        }
        if (r2 == null) {
            return 1;
        }
        if (r1 == r2) {
            return 0;
        }

        Point3f p1 = r1.getPosition();
        view.transform(p1);
        float distance1 = camPos.distance(p1);

        Point3f p2 = r2.getPosition();
        view.transform(p2);
        float distance2 = camPos.distance(p2);

        return (int) (distance2 - distance1);
    }
}

interface Equation {

    public double compute(double variable);
}

class EquationDisplay extends JComponent implements PropertyChangeListener {

    private static final Color COLOR_BACKGROUND = Color.WHITE;
    private static final Color COLOR_MAJOR_GRID = Color.GRAY.brighter();
    private static final Color COLOR_MINOR_GRID = new Color(220, 220, 220);
    private static final Color COLOR_AXIS = Color.BLACK;

    private static final float STROKE_AXIS = 1.2f;
    private static final float STROKE_GRID = 1.0f;

    private static final float COEFF_ZOOM = 1.1f;

    private java.util.List<DrawableEquation> equations;

    private double minX;
    private double maxX;
    private double minY;
    private double maxY;

    private double originX;
    private double originY;

    private double majorX;
    private int minorX;
    private double majorY;
    private int minorY;

    private Point dragStart;

    private NumberFormat formatter;

    public EquationDisplay(double originX, double originY,
            double minX, double maxX,
            double minY, double maxY,
            double majorX, int minorX,
            double majorY, int minorY) {
        if (minX >= maxX) {
            throw new IllegalArgumentException("minX must be < to maxX");
        }

        if (originX < minX || originX > maxX) {
            throw new IllegalArgumentException("originX must be between minX and maxX");
        }

        if (minY >= maxY) {
            throw new IllegalArgumentException("minY must be < to maxY");
        }

        if (originY < minY || originY > maxY) {
            throw new IllegalArgumentException("originY must be between minY and maxY");
        }

        if (minorX <= 0) {
            throw new IllegalArgumentException("minorX must be > 0");
        }

        if (minorY <= 0) {
            throw new IllegalArgumentException("minorY must be > 0");
        }

        if (majorX <= 0.0) {
            throw new IllegalArgumentException("majorX must be > 0.0");
        }

        if (majorY <= 0.0) {
            throw new IllegalArgumentException("majorY must be > 0.0");
        }

        this.originX = originX;
        this.originY = originY;

        this.minX = minX;
        this.maxX = maxX;
        this.minY = minY;
        this.maxY = maxY;

        this.majorX = majorX;
        this.minorX = minorX;
        this.majorY = majorY;
        this.minorY = minorY;

        this.equations = new LinkedList<DrawableEquation>();

        this.formatter = NumberFormat.getInstance();
        this.formatter.setMaximumFractionDigits(2);

        addMouseListener(new PanHandler());
        addMouseMotionListener(new PanMotionHandler());

        addMouseWheelListener(new ZoomHandler());
    }

    public void addEquation(AbstractEquation equation, Color color) {
        if (equation != null && !equations.contains(equation)) {
            equation.addPropertyChangeListener(this);
            equations.add(new DrawableEquation(equation, color));
            repaint();
        }
    }

    public void removeEquation(AbstractEquation equation) {
        if (equation != null) {
            DrawableEquation toRemove = null;
            for (DrawableEquation drawable : equations) {
                if (drawable.getEquation() == equation) {
                    toRemove = drawable;
                    break;
                }
            }

            if (toRemove != null) {
                equation.removePropertyChangeListener(this);
                equations.remove(toRemove);
                repaint();
            }
        }
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(400, 300);
    }

    public void propertyChange(PropertyChangeEvent evt) {
        repaint();
    }

    private double yPositionToPixel(double position) {
        double height = (double) getHeight();
        return height - ((position - minY) * height / (maxY - minY));
    }

    private double xPositionToPixel(double position) {
        return (position - minX) * (double) getWidth() / (maxX - minX);
    }

    private double xPixelToPosition(double pixel) {
        double axisV = xPositionToPixel(originX);
        return (pixel - axisV) * (maxX - minX) / (double) getWidth();
    }

    private double yPixelToPosition(double pixel) {
        double axisH = yPositionToPixel(originY);
        return (getHeight() - pixel - axisH) * (maxY - minY) / (double) getHeight();
    }

    @Override
    protected void paintComponent(Graphics g) {
        if (!isVisible()) {
            return;
        }

        Graphics2D g2 = (Graphics2D) g;
        setupGraphics(g2);

        drawBackground(g2);
        drawGrid(g2);
        drawAxis(g2);

        drawEquations(g2);
    }

    private void drawEquations(Graphics2D g2) {
        for (DrawableEquation drawable : equations) {
            g2.setColor(drawable.getColor());
            drawEquation(g2, drawable.getEquation());
        }
    }

    private void drawEquation(Graphics2D g2, AbstractEquation equation) {
        float x = 0.0f;
        float y = (float) yPositionToPixel(equation.compute(xPixelToPosition(0.0)));

        GeneralPath path = new GeneralPath();
        path.moveTo(x, y);

        for (x = 0.0f; x < getWidth(); x += 1.0f) {
            double position = xPixelToPosition(x);
            y = (float) yPositionToPixel(equation.compute(position));
            path.lineTo(x, y);
        }

        g2.draw(path);
    }

    private void drawGrid(Graphics2D g2) {
        Stroke stroke = g2.getStroke();

        drawVerticalGrid(g2);
        drawHorizontalGrid(g2);

        g2.setStroke(stroke);
    }

    private void drawHorizontalGrid(Graphics2D g2) {
        double minorSpacing = majorY / minorY;
        double axisV = xPositionToPixel(originX);

        Stroke gridStroke = new BasicStroke(STROKE_GRID);
        Stroke axisStroke = new BasicStroke(STROKE_AXIS);

        for (double y = originY + majorY; y < maxY + majorY; y += majorY) {
            g2.setStroke(gridStroke);
            g2.setColor(COLOR_MINOR_GRID);
            for (int i = 0; i < minorY; i++) {
                int position = (int) yPositionToPixel(y - i * minorSpacing);
                g2.drawLine(0, position, getWidth(), position);
            }

            int position = (int) yPositionToPixel(y);
            g2.setColor(COLOR_MAJOR_GRID);
            g2.drawLine(0, position, getWidth(), position);

            g2.setStroke(axisStroke);
            g2.setColor(COLOR_AXIS);
            g2.drawLine((int) axisV - 3, position, (int) axisV + 3, position);
            g2.drawString(formatter.format(y), (int) axisV + 5, position);
        }

        for (double y = originY - majorY; y > minY - majorY; y -= majorY) {
            g2.setStroke(gridStroke);
            g2.setColor(COLOR_MINOR_GRID);
            for (int i = 0; i < minorY; i++) {
                int position = (int) yPositionToPixel(y + i * minorSpacing);
                g2.drawLine(0, position, getWidth(), position);
            }

            int position = (int) yPositionToPixel(y);
            g2.setColor(COLOR_MAJOR_GRID);
            g2.drawLine(0, position, getWidth(), position);

            g2.setStroke(axisStroke);
            g2.setColor(COLOR_AXIS);
            g2.drawLine((int) axisV - 3, position, (int) axisV + 3, position);
            g2.drawString(formatter.format(y), (int) axisV + 5, position);
        }
    }

    private void drawVerticalGrid(Graphics2D g2) {
        double minorSpacing = majorX / minorX;
        double axisH = yPositionToPixel(originY);

        Stroke gridStroke = new BasicStroke(STROKE_GRID);
        Stroke axisStroke = new BasicStroke(STROKE_AXIS);

        FontMetrics metrics = g2.getFontMetrics();

        for (double x = originX + majorX; x < maxX + majorX; x += majorX) {
            g2.setStroke(gridStroke);
            g2.setColor(COLOR_MINOR_GRID);
            for (int i = 0; i < minorX; i++) {
                int position = (int) xPositionToPixel(x - i * minorSpacing);
                g2.drawLine(position, 0, position, getHeight());
            }

            int position = (int) xPositionToPixel(x);
            g2.setColor(COLOR_MAJOR_GRID);
            g2.drawLine(position, 0, position, getHeight());

            g2.setStroke(axisStroke);
            g2.setColor(COLOR_AXIS);
            g2.drawLine(position, (int) axisH - 3, position, (int) axisH + 3);
            g2.drawString(formatter.format(x), position, (int) axisH + metrics.getHeight());
        }

        for (double x = originX - majorX; x > minX - majorX; x -= majorX) {
            g2.setStroke(gridStroke);
            g2.setColor(COLOR_MINOR_GRID);
            for (int i = 0; i < minorX; i++) {
                int position = (int) xPositionToPixel(x + i * minorSpacing);
                g2.drawLine(position, 0, position, getHeight());
            }

            int position = (int) xPositionToPixel(x);
            g2.setColor(COLOR_MAJOR_GRID);
            g2.drawLine(position, 0, position, getHeight());

            g2.setStroke(axisStroke);
            g2.setColor(COLOR_AXIS);
            g2.drawLine(position, (int) axisH - 3, position, (int) axisH + 3);
            g2.drawString(formatter.format(x), position, (int) axisH + metrics.getHeight());
        }
    }

    private void drawAxis(Graphics2D g2) {
        double axisH = yPositionToPixel(originY);
        double axisV = xPositionToPixel(originX);

        g2.setColor(COLOR_AXIS);
        Stroke stroke = g2.getStroke();
        g2.setStroke(new BasicStroke(STROKE_AXIS));

        g2.drawLine(0, (int) axisH, getWidth(), (int) axisH);
        g2.drawLine((int) axisV, 0, (int) axisV, getHeight());

        FontMetrics metrics = g2.getFontMetrics();
        g2.drawString(formatter.format(0.0), (int) axisV + 5, (int) axisH + metrics.getHeight());

        g2.setStroke(stroke);
    }

    private void setupGraphics(Graphics2D g2) {
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);
    }

    private void drawBackground(Graphics2D g2) {
        g2.setColor(COLOR_BACKGROUND);
        g2.fill(g2.getClipBounds());
    }

    private class DrawableEquation {

        private AbstractEquation equation;
        private Color color;

        DrawableEquation(AbstractEquation equation, Color color) {
            this.equation = equation;
            this.color = color;
        }

        AbstractEquation getEquation() {
            return equation;
        }

        Color getColor() {
            return color;
        }
    }

    private class ZoomHandler implements MouseWheelListener {

        public void mouseWheelMoved(MouseWheelEvent e) {
            double distanceX = maxX - minX;
            double distanceY = maxY - minY;

            double cursorX = minX + distanceX / 2.0;
            double cursorY = minY + distanceY / 2.0;

            int rotation = e.getWheelRotation();
            if (rotation < 0) {
                distanceX /= COEFF_ZOOM;
                distanceY /= COEFF_ZOOM;
            } else {
                distanceX *= COEFF_ZOOM;
                distanceY *= COEFF_ZOOM;
            }

            minX = cursorX - distanceX / 2.0;
            maxX = cursorX + distanceX / 2.0;
            minY = cursorY - distanceY / 2.0;
            maxY = cursorY + distanceY / 2.0;

            repaint();
        }
    }

    private class PanHandler extends MouseAdapter {

        @Override
        public void mousePressed(MouseEvent e) {
            dragStart = e.getPoint();
        }
    }

    private class PanMotionHandler extends MouseMotionAdapter {

        @Override
        public void mouseDragged(MouseEvent e) {
            Point dragEnd = e.getPoint();

            double distance = xPixelToPosition(dragEnd.getX())
                    - xPixelToPosition(dragStart.getX());
            minX -= distance;
            maxX -= distance;

            distance = yPixelToPosition(dragEnd.getY())
                    - yPixelToPosition(dragStart.getY());
            minY -= distance;
            maxY -= distance;

            repaint();
            dragStart = dragEnd;
        }
    }
}

class EquationsControlPanel extends JPanel implements PropertyChangeListener {

    private EquationDisplay display;

    private AnimationEquation gauss;

    private JLabel sigmaLabel;
    private JLabel phaseLabel;

    private JPanel debugPanel;

    private int linesCount = 0;

    EquationsControlPanel(AnimationEquation gauss) {
        super(new BorderLayout());

        this.gauss = gauss;
        this.gauss.addPropertyChangeListener(this);

        add(buildDebugControls(), BorderLayout.EAST);
        add(buildEquationDisplay(), BorderLayout.CENTER);
    }

    private Container buildEquationDisplay() {
        JPanel panel = new JPanel(new BorderLayout());

        display = new EquationDisplay(0.0, 0.0,
                -1.1, 1.1, -1.1, 1.1,
                0.5, 5,
                0.5, 4);
        display.addEquation(gauss, new Color(0.0f, 0.7f, 0.0f, 0.7f));

        panel.add(display, BorderLayout.CENTER);

        return panel;
    }

    private Component buildDebugControls() {
        debugPanel = new JPanel(new GridBagLayout());

        JSlider slider;

        addEmptySpace(debugPanel, 6);
        addSeparator(debugPanel, "Parameters");

        slider = addDebugSlider(debugPanel, "Sigma:", 1, 500, (int) (gauss.getSigma() * 10.0));
        slider.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                final int value = ((JSlider) e.getSource()).getValue();
                gauss.setSigma(value / 10.0);
            }
        });

        slider = addDebugSlider(debugPanel, "Phase:", -250, 250, (int) (gauss.getPhase() * 100.0));
        slider.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                final int value = ((JSlider) e.getSource()).getValue();
                gauss.setPhase(value / 100.0);
            }
        });

        sigmaLabel = addDebugLabel(debugPanel, "Sigma:", Double.toString(gauss.getSigma()));
        phaseLabel = addDebugLabel(debugPanel, "Phase:", Double.toString(gauss.getPhase()));

        addEmptySpace(debugPanel, 12);

        debugPanel.add(Box.createVerticalGlue(),
                new GridBagConstraints(0, linesCount++,
                2, 1,
                1.0, 1.0,
                GridBagConstraints.LINE_START,
                GridBagConstraints.NONE,
                new Insets(0, 0, 0, 0),
                0, 0));

        JPanel wrapper = new JPanel(new BorderLayout());
        wrapper.add(new JSeparator(JSeparator.VERTICAL), BorderLayout.WEST);
        wrapper.add(debugPanel);
        return wrapper;
    }

    private void addEmptySpace(JPanel panel, int size) {
        panel.add(Box.createVerticalStrut(size),
                new GridBagConstraints(0, linesCount++,
                2, 1,
                1.0, 0.0,
                GridBagConstraints.CENTER,
                GridBagConstraints.VERTICAL,
                new Insets(6, 0, 0, 0),
                0, 0));
    }

    public void propertyChange(PropertyChangeEvent evt) {
        String name = evt.getPropertyName();
        if (AnimationEquation.PROPERTY_SIGMA.equals(name)) {
            sigmaLabel.setText(evt.getNewValue().toString());
        } else if (AnimationEquation.PROPERTY_PHASE.equals(name)) {
            phaseLabel.setText(evt.getNewValue().toString());
        }
    }

    private void addSeparator(JPanel panel, String label) {
        JPanel innerPanel = new JPanel(new GridBagLayout());
        innerPanel.add(new JLabel(label),
                new GridBagConstraints(0, 0,
                1, 1,
                0.0, 0.0,
                GridBagConstraints.LINE_START,
                GridBagConstraints.NONE,
                new Insets(0, 0, 0, 0),
                0, 0));
        innerPanel.add(new JSeparator(),
                new GridBagConstraints(1, 0,
                1, 1,
                0.9, 0.0,
                GridBagConstraints.LINE_START,
                GridBagConstraints.HORIZONTAL,
                new Insets(0, 6, 0, 6),
                0, 0));
        panel.add(innerPanel,
                new GridBagConstraints(0, linesCount++,
                2, 1,
                1.0, 0.0,
                GridBagConstraints.LINE_START,
                GridBagConstraints.HORIZONTAL,
                new Insets(6, 6, 6, 0),
                0, 0));
    }

    private JLabel addDebugLabel(JPanel panel, String label, String value) {
        JLabel labelComponent = new JLabel(label);
        panel.add(labelComponent,
                new GridBagConstraints(0, linesCount,
                1, 1,
                0.5, 0.0,
                GridBagConstraints.LINE_END,
                GridBagConstraints.NONE,
                new Insets(0, 6, 0, 0),
                0, 0));
        labelComponent = new JLabel(value);
        panel.add(labelComponent,
                new GridBagConstraints(1, linesCount++,
                1, 1,
                0.5, 0.0,
                GridBagConstraints.LINE_START,
                GridBagConstraints.NONE,
                new Insets(0, 6, 0, 0),
                0, 0));
        return labelComponent;
    }

    private JSlider addDebugSlider(JPanel panel, String label,
            int min, int max, int value) {
        JSlider slider;
        panel.add(new JLabel(label),
                new GridBagConstraints(0, linesCount++,
                2, 1,
                1.0, 0.0,
                GridBagConstraints.LINE_START,
                GridBagConstraints.NONE,
                new Insets(0, 6, 0, 0),
                0, 0));
        panel.add(slider = new JSlider(min, max, value),
                new GridBagConstraints(0, linesCount++,
                2, 1,
                0.0, 0.0,
                GridBagConstraints.LINE_START,
                GridBagConstraints.HORIZONTAL,
                new Insets(0, 6, 0, 6),
                0, 0));
        return slider;
    }
}

class GLUtilities {

    private static HashMap<Integer, Point2d[]> jitterMap = null;

    public static void drawLocalAxis(GL gl, float axisLength) {
        Vector3f x = new Vector3f(1.0f, 0.0f, 0.0f);
        Vector3f y = new Vector3f(0.0f, 1.0f, 0.0f);
        Vector3f z = new Vector3f(0.0f, 0.0f, 1.0f);

        gl.glLineWidth(2);
        gl.glBegin(GL.GL_LINES);
        gl.glColor3f(1, 0, 0);
        gl.glVertex3f(0, 0, 0);
        gl.glVertex3f(x.x * axisLength, x.y * axisLength, x.z * axisLength);

        gl.glColor3f(0, 1, 0);
        gl.glVertex3f(0, 0, 0);
        gl.glVertex3f(y.x * axisLength, y.y * axisLength, y.z * axisLength);

        gl.glColor3f(0, 0, 1);
        gl.glVertex3f(0, 0, 0);
        gl.glVertex3f(z.x * axisLength, z.y * axisLength, z.z * axisLength);
        gl.glEnd();

        gl.glPopAttrib();
    }

    public static void renderBillboard(GL gl, Vector3f camPos, Vector3f camUp, Renderable item) {
        Point3f pos = item.getPosition();

        Vector3f look = new Vector3f();
        Vector3f right = new Vector3f();
        Vector3f up = new Vector3f();

        look.sub(camPos, pos);
        look.normalize();

        right.cross(camUp, look);
        up.cross(look, right);

        gl.glMultMatrixf(new float[]{right.x, right.y, right.z, 0.0f,
            up.x, up.y, up.z, 0.0f,
            look.x, look.y, look.z, 0.0f,
            pos.x, pos.y, pos.z, 1}, 0);

        item.render(gl);
    }

    public static void getCameraVectors(GL gl, Vector3f camPos, Vector3f camUp) {
        float[] matrix = new float[16];
        gl.glGetFloatv(GL.GL_MODELVIEW_MATRIX, matrix, 0);

        camPos.set(new float[]{-matrix[12], -matrix[13], -matrix[14]});
        camUp.set(new float[]{matrix[1], matrix[5], matrix[9]});

        matrix[12] = matrix[13] = matrix[14] = 0;
        Matrix4f view = new Matrix4f(matrix);
        view.transform(camPos);
    }

    public static void renderAntiAliased(GL gl, Renderable scene, int aa) {
        if (aa <= 1) {
            scene.render(gl);
            return;
        }

        int[] viewport = new int[4];
        gl.glGetIntegerv(GL.GL_VIEWPORT, viewport, 0);
        gl.glClear(GL.GL_ACCUM_BUFFER_BIT);

        Point2d[] samples = GLUtilities.getJitterSamples(aa);

        for (int jitter = 0; jitter < samples.length; jitter++) {
            gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
            GLUtilities.setPerspective(gl,
                    45.0, (double) viewport[2] / (double) viewport[3], 1.0, 250.0,
                    samples[jitter].x, samples[jitter].y,
                    0.0, 0.0, 1.0);
            scene.render(gl);
            gl.glAccum(GL.GL_ACCUM, 1.0f / samples.length);
        }

        gl.glAccum(GL.GL_RETURN, 1.0f);
        gl.glFlush();
    }

    public static void setFrustum(GL gl,
            double left, double right, double bottom,
            double top, double near, double far, double pixdx,
            double pixdy, double eyedx, double eyedy, double focus) {
        int[] viewport = new int[4];
        gl.glGetIntegerv(GL.GL_VIEWPORT, viewport, 0);

        double xwsize = right - left;
        double ywsize = top - bottom;

        double dx = -(pixdx * xwsize / (double) viewport[2] + eyedx * near / focus);
        double dy = -(pixdy * ywsize / (double) viewport[3] + eyedy * near / focus);

        gl.glMatrixMode(GL.GL_PROJECTION);
        gl.glLoadIdentity();
        gl.glFrustum(left + dx, right + dx, bottom + dy, top + dy, near, far);
        gl.glMatrixMode(GL.GL_MODELVIEW);
        gl.glLoadIdentity();
        gl.glTranslatef((float) -eyedx, (float) -eyedy, 0.0f);
    }

    public static void setPerspective(GL gl,
            double fovy, double aspect,
            double near, double far, double pixdx, double pixdy,
            double eyedx, double eyedy, double focus) {
        double fov2 = ((fovy * Math.PI) / 180.0) / 2.0;

        double top = near / (Math.cos(fov2) / Math.sin(fov2));
        double bottom = -top;

        double right = top * aspect;
        double left = -right;

        setFrustum(gl,
                left, right, bottom, top, near, far,
                pixdx, pixdy, eyedx, eyedy, focus);
    }

    public static Point2d[] getJitterSamples(int sampleAmount) {
        if (jitterMap == null) {
            initJitterMap();
        }

        // fail-safe when sampleAmount does not exist
        if (!jitterMap.containsKey(sampleAmount)) {
            int difference = Integer.MAX_VALUE;
            int newSampleAmount = 0;

            for (int value : jitterMap.keySet()) {
                if (Math.abs(value - sampleAmount) < difference) {
                    difference = Math.abs(value - sampleAmount);
                    newSampleAmount = value;
                }
            }
            sampleAmount = newSampleAmount;
        }

        return jitterMap.get(sampleAmount);
    }

    // these values come from the example jitter.h in the Red Book
    private static void initJitterMap() {
        jitterMap = new HashMap<Integer, Point2d[]>(7);
        jitterMap.put(2, new Point2d[]{new Point2d(0.246490, 0.249999),
            new Point2d(-0.246490, -0.249999)});
        jitterMap.put(3, new Point2d[]{new Point2d(-0.373411, -0.250550),
            new Point2d(0.256263, 0.368119),
            new Point2d(0.117148, -0.117570)});
        jitterMap.put(4, new Point2d[]{new Point2d(-0.208147, 0.353730),
            new Point2d(0.203849, -0.353780),
            new Point2d(-0.292626, -0.149945),
            new Point2d(0.296924, 0.149994)});
        jitterMap.put(8, new Point2d[]{new Point2d(-0.334818, 0.435331),
            new Point2d(0.286438, -0.393495),
            new Point2d(0.459462, 0.141540),
            new Point2d(-0.414498, -0.192829),
            new Point2d(-0.183790, 0.082102),
            new Point2d(-0.079263, -0.317383),
            new Point2d(0.102254, 0.299133),
            new Point2d(0.164216, -0.054399)});
        jitterMap.put(15, new Point2d[]{new Point2d(0.285561, 0.188437),
            new Point2d(0.360176, -0.065688),
            new Point2d(-0.111751, 0.275019),
            new Point2d(-0.055918, -0.215197),
            new Point2d(-0.080231, -0.470965),
            new Point2d(0.138721, 0.409168),
            new Point2d(0.384120, 0.458500),
            new Point2d(-0.454968, 0.134088),
            new Point2d(0.179271, -0.331196),
            new Point2d(-0.307049, -0.364927),
            new Point2d(0.105354, -0.010099),
            new Point2d(-0.154180, 0.021794),
            new Point2d(-0.370135, -0.116425),
            new Point2d(0.451636, -0.300013),
            new Point2d(-0.370610, 0.387504)});
        jitterMap.put(24, new Point2d[]{new Point2d(0.030245, 0.136384),
            new Point2d(0.018865, -0.348867),
            new Point2d(-0.350114, -0.472309),
            new Point2d(0.222181, 0.149524),
            new Point2d(-0.393670, -0.266873),
            new Point2d(0.404568, 0.230436),
            new Point2d(0.098381, 0.465337),
            new Point2d(0.462671, 0.442116),
            new Point2d(0.400373, -0.212720),
            new Point2d(-0.409988, 0.263345),
            new Point2d(-0.115878, -0.001981),
            new Point2d(0.348425, -0.009237),
            new Point2d(-0.464016, 0.066467),
            new Point2d(-0.138674, -0.468006),
            new Point2d(0.144932, -0.022780),
            new Point2d(-0.250195, 0.150161),
            new Point2d(-0.181400, -0.264219),
            new Point2d(0.196097, -0.234139),
            new Point2d(-0.311082, -0.078815),
            new Point2d(0.268379, 0.366778),
            new Point2d(-0.040601, 0.327109),
            new Point2d(-0.234392, 0.354659),
            new Point2d(-0.003102, -0.154402),
            new Point2d(0.297997, -0.417965)});
        jitterMap.put(66, new Point2d[]{new Point2d(0.266377, -0.218171),
            new Point2d(-0.170919, -0.429368),
            new Point2d(0.047356, -0.387135),
            new Point2d(-0.430063, 0.363413),
            new Point2d(-0.221638, -0.313768),
            new Point2d(0.124758, -0.197109),
            new Point2d(-0.400021, 0.482195),
            new Point2d(0.247882, 0.152010),
            new Point2d(-0.286709, -0.470214),
            new Point2d(-0.426790, 0.004977),
            new Point2d(-0.361249, -0.104549),
            new Point2d(-0.040643, 0.123453),
            new Point2d(-0.189296, 0.438963),
            new Point2d(-0.453521, -0.299889),
            new Point2d(0.408216, -0.457699),
            new Point2d(0.328973, -0.101914),
            new Point2d(-0.055540, -0.477952),
            new Point2d(0.194421, 0.453510),
            new Point2d(0.404051, 0.224974),
            new Point2d(0.310136, 0.419700),
            new Point2d(-0.021743, 0.403898),
            new Point2d(-0.466210, 0.248839),
            new Point2d(0.341369, 0.081490),
            new Point2d(0.124156, -0.016859),
            new Point2d(-0.461321, -0.176661),
            new Point2d(0.013210, 0.234401),
            new Point2d(0.174258, -0.311854),
            new Point2d(0.294061, 0.263364),
            new Point2d(-0.114836, 0.328189),
            new Point2d(0.041206, -0.106205),
            new Point2d(0.079227, 0.345021),
            new Point2d(-0.109319, -0.242380),
            new Point2d(0.425005, -0.332397),
            new Point2d(0.009146, 0.015098),
            new Point2d(-0.339084, -0.355707),
            new Point2d(-0.224596, -0.189548),
            new Point2d(0.083475, 0.117028),
            new Point2d(0.295962, -0.334699),
            new Point2d(0.452998, 0.025397),
            new Point2d(0.206511, -0.104668),
            new Point2d(0.447544, -0.096004),
            new Point2d(-0.108006, -0.002471),
            new Point2d(-0.380810, 0.130036),
            new Point2d(-0.242440, 0.186934),
            new Point2d(-0.200363, 0.070863),
            new Point2d(-0.344844, -0.230814),
            new Point2d(0.408660, 0.345826),
            new Point2d(-0.233016, 0.305203),
            new Point2d(0.158475, -0.430762),
            new Point2d(0.486972, 0.139163),
            new Point2d(-0.301610, 0.009319),
            new Point2d(0.282245, -0.458671),
            new Point2d(0.482046, 0.443890),
            new Point2d(-0.121527, 0.210223),
            new Point2d(-0.477606, -0.424878),
            new Point2d(-0.083941, -0.121440),
            new Point2d(-0.345773, 0.253779),
            new Point2d(0.234646, 0.034549),
            new Point2d(0.394102, -0.210901),
            new Point2d(-0.312571, 0.397656),
            new Point2d(0.200906, 0.333293),
            new Point2d(0.018703, -0.261792),
            new Point2d(-0.209349, -0.065383),
            new Point2d(0.076248, 0.478538),
            new Point2d(-0.073036, -0.355064),
            new Point2d(0.145087, 0.221726)});
    }
}

class HeaderPanel extends JPanel {

    private ImageIcon icon;

    public HeaderPanel(ImageIcon icon,
            String title,
            String help1,
            String help2) {
        super(new BorderLayout());

        this.icon = icon;

        JPanel titlesPanel = new JPanel(new GridLayout(3, 1));
        titlesPanel.setOpaque(false);
        titlesPanel.setBorder(new EmptyBorder(12, 0, 12, 0));

        JLabel headerTitle = new JLabel(title);
        Font police = headerTitle.getFont().deriveFont(Font.BOLD);
        headerTitle.setFont(police);
        headerTitle.setBorder(new EmptyBorder(0, 12, 0, 0));
        titlesPanel.add(headerTitle);

        JLabel message;

        titlesPanel.add(message = new JLabel(help1));
        police = headerTitle.getFont().deriveFont(Font.PLAIN);
        message.setFont(police);
        message.setBorder(new EmptyBorder(0, 24, 0, 0));

        titlesPanel.add(message = new JLabel(help2));
        police = headerTitle.getFont().deriveFont(Font.PLAIN);
        message.setFont(police);
        message.setBorder(new EmptyBorder(0, 24, 0, 0));

        message = new JLabel(this.icon);
        message.setBorder(new EmptyBorder(0, 0, 0, 12));

        add(BorderLayout.WEST, titlesPanel);
        add(BorderLayout.EAST, message);
        add(BorderLayout.SOUTH, new JSeparator(JSeparator.HORIZONTAL));

        setPreferredSize(new Dimension(500, this.icon.getIconHeight() + 24));
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        if (!isOpaque()) {
            return;
        }

        Color control = UIManager.getColor("control");
        int width = getWidth();
        int height = getHeight();

        Graphics2D g2 = (Graphics2D) g;
        Paint storedPaint = g2.getPaint();
        g2.setPaint(new GradientPaint(this.icon.getIconWidth(), 0, Color.white, width, 0, control));
        g2.fillRect(0, 0, width, height);
        g2.setPaint(storedPaint);
    }
}

class Picture {

    private String name;
    private BufferedImage image;

    private BufferedImage thumb = null;
    private int requestedThumbSize = 64;

    public Picture(String name, BufferedImage image) {
        this.name = name;
        this.image = image;
    }

    public BufferedImage getThumbnail(int thumbWidth) {
        if (thumbWidth >= image.getWidth()) {
            return image;
        }

        if (thumbWidth != requestedThumbSize || thumb == null) {
            requestedThumbSize = thumbWidth;
            generateThumbnail();
        }

        return thumb;
    }

    private void generateThumbnail() {
        float ratio = getRatio();
        int width = image.getWidth();
        thumb = image;

        do {
            width /= 2;
            if (width < requestedThumbSize) {
                width = requestedThumbSize;
            }

            BufferedImage temp = new BufferedImage(width, (int) (width / ratio), BufferedImage.TYPE_INT_ARGB);
            Graphics2D g2 = temp.createGraphics();
            g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
                    RenderingHints.VALUE_INTERPOLATION_BILINEAR);
            g2.drawImage(thumb, 0, 0, temp.getWidth(), temp.getHeight(), null);
            g2.dispose();

            thumb = temp;
        } while (width != requestedThumbSize);
    }

    public BufferedImage getImage() {
        return image;
    }

    public String getName() {
        return name;
    }

    public float getRatio() {
        return (float) image.getWidth() / (float) image.getHeight();
    }
}

class PictureViewer extends CompositeGLPanel {

    public static final String KEY_ACTION_NEXT_PICTURE = "next";
    public static final String KEY_ACTION_PREVIOUS_PICTURE = "previous";
    public static final String KEY_ACTION_SHOW_PICTURE = "show";

    private static boolean envAntiAliasing = false;

    static {
        envAntiAliasing = System.getProperty("twinkle.aa") != null;
    }

    private static final float QUAD_WIDTH = 60.0f;

    private static final int THUMB_SPACING = 5;
    private static final int THUMB_WIDTH = 48;
    private static final double SELECTED_THUMB_RATIO = 0.35;
    private static final double SELECTED_THUMB_EXTRA_WIDTH = THUMB_WIDTH * SELECTED_THUMB_RATIO;

    private static final int INDEX_LEFT_PICTURE = 0;
    private static final int INDEX_SELECTED_PICTURE = 1;
    private static final int INDEX_NEXT_PICTURE = 2;
    private static final int INDEX_RIGHT_PICTURE = 3;

    private java.util.List<Picture> pictures = Collections.synchronizedList(new ArrayList<Picture>());
    private Renderable[] renderables = new Renderable[4];

    private Queue<Renderable> initQuadsQueue = new ConcurrentLinkedQueue<Renderable>();
    private Queue<Renderable> disposeQuadsQueue = new ConcurrentLinkedQueue<Renderable>();

    private float camPosX = 0.0f;
    private float camPosY = 0.0f;
    private float camPosZ = 100.0f;

    private int picturesStripHeight = 0;

    private BufferedImage textImage = null;
    private BufferedImage nextTextImage = null;
    private BufferedImage alphaMask;
    private ShadowFactory shadowFactory = new ShadowFactory(11, 1.0f, Color.BLACK);
    private Font textFont;
    private float textAlpha = 1.0f;
    private double animFactor = 0.0;
    private Color grayColor = new Color(0xE1E1E1);

    private boolean next;
    private int selectedPicture = -1;
    private int nextPicture = -1;

    private boolean pictureIsShowing = false;
    private Equation curve = new AnimationEquation(2.8, -0.98);//3.6, -1.0);
    private javax.swing.Timer animator;

    private boolean antiAliasing = envAntiAliasing;

    public PictureViewer() {
        super(false, true);
        setPreferredSize(new Dimension(640, 480));

        addMouseWheelListener(new MouseWheelDriver());

        setFocusable(true);
        registerActions();

        textFont = getFont().deriveFont(Font.BOLD, 32.0f);
        alphaMask = createGradientMask(THUMB_WIDTH);

        createButtons();
    }

    public boolean isAntiAliasing() {
        return antiAliasing;
    }

    public void setAntiAliasing(boolean antiAliasing) {
        this.antiAliasing = antiAliasing && envAntiAliasing;
        repaint();
    }

    public void addPicture(String name, BufferedImage image) {
        if (name == null) {
            name = "";
        }

        int size = -1;
        Picture picture = new Picture(name, image);

        pictures.add(picture);
        size = pictures.size();

        if (size == 1) {
            initQuadsQueue.add(createQuad(INDEX_SELECTED_PICTURE, 0));
        } else if (size - 1 == selectedPicture + 1) {
            initQuadsQueue.add(createQuad(INDEX_NEXT_PICTURE, 1));
        } else if (size - 1 == nextPicture + 1) {
            initQuadsQueue.add(createQuad(INDEX_RIGHT_PICTURE, 2));
        }

        float ratio = picture.getRatio();
        picturesStripHeight = Math.max(picturesStripHeight,
                (int) (THUMB_WIDTH + SELECTED_THUMB_EXTRA_WIDTH / ratio));

        getActionMap().get(KEY_ACTION_SHOW_PICTURE).setEnabled(selectedPicture >= 0);
        getActionMap().get(KEY_ACTION_NEXT_PICTURE).setEnabled(selectedPicture < size - 1);
        getActionMap().get(KEY_ACTION_PREVIOUS_PICTURE).setEnabled(selectedPicture > 0);

        repaint();
    }

    public void showSelectedPicture() {
        if (animator != null && animator.isRunning()) {
            return;
        }

        pictureIsShowing = !pictureIsShowing;
        ((ShowPictureAction) getActionMap().get(KEY_ACTION_SHOW_PICTURE)).toggleName();

        animator = new javax.swing.Timer(1000 / 60, new ZoomAnimation());
        animator.start();
    }

    public void nextPicture() {
        int size = -1;
        size = pictures.size() - 1;

        if (selectedPicture < size) {
            showPicture(true);
        }
    }

    public void previousPicture() {
        if (selectedPicture > 0) {
            showPicture(false);
        }
    }

    @Override
    public void init(GLAutoDrawable drawable) {
        super.init(drawable);
        GL gl = drawable.getGL();

        initQuads(gl);
    }

    private void registerActions() {
        KeyStroke stroke;
        Action action;

        InputMap inputMap = getInputMap();
        ActionMap actionMap = getActionMap();

        stroke = KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0);
        inputMap.put(stroke, KEY_ACTION_NEXT_PICTURE);
        stroke = KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0);
        inputMap.put(stroke, KEY_ACTION_NEXT_PICTURE);

        action = new NextPictureAction();
        actionMap.put(KEY_ACTION_NEXT_PICTURE, action);

        stroke = KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0);
        inputMap.put(stroke, KEY_ACTION_PREVIOUS_PICTURE);
        stroke = KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0);
        inputMap.put(stroke, KEY_ACTION_PREVIOUS_PICTURE);

        action = new PreviousPictureAction();
        actionMap.put(KEY_ACTION_PREVIOUS_PICTURE, action);

        stroke = KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0);
        inputMap.put(stroke, KEY_ACTION_SHOW_PICTURE);
        stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0);
        inputMap.put(stroke, KEY_ACTION_SHOW_PICTURE);

        action = new ShowPictureAction();
        actionMap.put(KEY_ACTION_SHOW_PICTURE, action);
    }

    private void createButtons() {
        ControlButton button;
        ControlPanel buttonsPanel = new ControlPanel();

        button = new ControlButton(getActionMap().get(KEY_ACTION_PREVIOUS_PICTURE));
        buttonsPanel.add(button);
        button = new ControlButton(getActionMap().get(KEY_ACTION_SHOW_PICTURE));
        buttonsPanel.add(button);
        button = new ControlButton(getActionMap().get(KEY_ACTION_NEXT_PICTURE));
        buttonsPanel.add(button);

        setLayout(new GridBagLayout());
        add(Box.createGlue(), new GridBagConstraints(0, 0,
                2, 1,
                0.0, 1.0,
                GridBagConstraints.LINE_START,
                GridBagConstraints.VERTICAL,
                new Insets(0, 0, 0, 0),
                0, 0));
        add(buttonsPanel, new GridBagConstraints(0, 1,
                1, 1,
                0.0, 0.0,
                GridBagConstraints.LINE_START,
                GridBagConstraints.NONE,
                new Insets(0, 13, 13, 0),
                0, 0));
        add(Box.createHorizontalGlue(), new GridBagConstraints(1, 1,
                1, 1,
                1.0, 0.0,
                GridBagConstraints.LINE_START,
                GridBagConstraints.HORIZONTAL,
                new Insets(0, 0, 0, 0),
                0, 0));
    }

    private void showPicture(final boolean next) {
        if (animator != null && animator.isRunning()) {
            return;
        }

        if (pictureIsShowing) {
            new Thread(new Runnable() {
                public void run() {
                    showSelectedPicture();
                    while (animator.isRunning()) {
                        Thread.yield();
                    }
                    showPicture(next);
                    while (animator.isRunning()) {
                        Thread.yield();
                    }
                    SwingUtilities.invokeLater(new Runnable() {
                        public void run() {
                            showSelectedPicture();
                        }
                    });
                }
            }).start();
            return;
        }

        this.next = next;

        animator = new javax.swing.Timer(1000 / 60, new SlideAnimation(next));
        animator.start();
    }

    private Renderable createQuad(int index, int pictureNumber) {
        Picture picture = null;
        picture = pictures.get(pictureNumber);

        if (picture == null || index > renderables.length) {
            return null;
        }

        float ratio = picture.getRatio();
        int height = (int) (QUAD_WIDTH / ratio);

        Renderable quad = RenderableFactory.createReflectedQuad(0.0f, 0.0f, 0.0f,
                QUAD_WIDTH, height,
                picture.getImage(), null,
                picture.getName());
        renderables[index] = quad;

        if (index == INDEX_SELECTED_PICTURE) {
            selectedPicture = pictureNumber;

            quad.setPosition(-7.0f, 0.0f, 0.0f);
            quad.setRotation(0, 30, 0);

            textImage = generateTextImage(picture);
        } else if (index == INDEX_NEXT_PICTURE) {
            nextPicture = pictureNumber;

            quad.setScale(0.5f, 0.5f, 0.5f);
            quad.setPosition(36.0f, -height / 2.0f, 30.0f);
            quad.setRotation(0, -20, 0);
        } else if (index == INDEX_RIGHT_PICTURE) {
            quad.setScale(0.5f, 0.5f, 0.5f);
            quad.setPosition(196.0f, -height / 2.0f, 30.0f);
            quad.setRotation(0, -20, 0);
        } else if (index == INDEX_LEFT_PICTURE) {
            quad.setPosition(-7.0f - QUAD_WIDTH * 2.0f, 0.0f, 0.0f);
            quad.setRotation(0, 30, 0);
        }

        return quad;
    }

    private BufferedImage generateTextImage(Picture picture) {
        FontRenderContext context = getFontMetrics(textFont).getFontRenderContext();
        GlyphVector vector = textFont.createGlyphVector(context, picture.getName());
        Rectangle bounds = vector.getPixelBounds(context, 0.0f, 0.0f);
        TextLayout layout = new TextLayout(picture.getName(), textFont, context);

        BufferedImage image = new BufferedImage((int) (bounds.getWidth()),
                (int) (layout.getAscent() + layout.getDescent() + layout.getLeading()),
                BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2 = image.createGraphics();
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);
        layout.draw(g2, 0, layout.getAscent());
        g2.dispose();

        BufferedImage shadow = shadowFactory.createShadow(image);
        BufferedImage composite = new BufferedImage(shadow.getWidth(),
                shadow.getHeight(),
                BufferedImage.TYPE_INT_ARGB);
        g2 = composite.createGraphics();
        g2.drawImage(shadow, null,
                -1 - (shadow.getWidth() - image.getWidth()) / 2,
                2 - (shadow.getHeight() - image.getHeight()) / 2);
        g2.drawImage(image, null, 0, 0);
        g2.dispose();

        shadow.flush();
        image.flush();

        return composite;
    }

    @Override
    protected void render2DBackground(Graphics g) {
        // NOTE: with antialiasing on the accum buffer creates a black backround
        if (!antiAliasing) {
            float h = getHeight() * 0.55f;

            GradientPaint paint = new GradientPaint(0.0f, h, Color.BLACK,
                    0.0f, getHeight(), new Color(0x4C4C4C));
            Graphics2D g2 = (Graphics2D) g;
            Paint oldPaint = g2.getPaint();
            g2.setPaint(paint);
            g2.fillRect(0, 0, getWidth(), getHeight());
            g2.setPaint(oldPaint);
        }
    }

    @Override
    protected void render2DForeground(Graphics g) {
        Graphics2D g2 = (Graphics2D) g;
        setupForegroundGraphics(g2);

        paintPicturesStrip(g2);
        paintInfo(g2);
    }

    private BufferedImage createGradientMask(int width) {
        BufferedImage gradient = new BufferedImage(width, 2,
                BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = gradient.createGraphics();
        GradientPaint painter = new GradientPaint(0.0f, 0.0f,
                new Color(1.0f, 1.0f, 1.0f, 1.0f),
                width / 2.0f, 0.0f,
                new Color(1.0f, 1.0f, 1.0f, 0.0f));
        g.setPaint(painter);
        g.fill(new Rectangle2D.Double(0, 0, width, 2));

        g.dispose();

        return gradient;
    }

    private void paintPicturesStrip(Graphics2D g2) {
        Rectangle clip = g2.getClipBounds();

        int x = (int) (getWidth() / 2 - (selectedPicture + (next ? 1 : -1) * animFactor) * (THUMB_WIDTH + THUMB_SPACING));
        int y = picturesStripHeight / 2;
        int i = 0;

        int width = (int) (THUMB_WIDTH + SELECTED_THUMB_EXTRA_WIDTH * (1.0 - animFactor));
        int width2 = (int) (THUMB_WIDTH + SELECTED_THUMB_EXTRA_WIDTH * animFactor);

        x -= width / 2;
        x -= (width2 - THUMB_WIDTH) / 2;

        Picture[] picturesArray = new Picture[pictures.size()];
        picturesArray = pictures.toArray(picturesArray);

        for (Picture picture : picturesArray) {
            int picWidth = THUMB_WIDTH;
            if (i == selectedPicture) {
                picWidth = width;
            } else if ((next && i == nextPicture)
                    || (!next && i == selectedPicture - 1)) {
                picWidth = width2;
            }

            if (x > clip.x + clip.width) {
                return;
            }

            if (x + picWidth >= clip.x) {
                BufferedImage thumb = picture.getThumbnail(THUMB_WIDTH * 2);
                float ratio = picture.getRatio();
                int height = (int) (picWidth / ratio);
                int y1 = y - height / 2;

                g2.setColor(grayColor);
                if (i == selectedPicture) {
                    Graphics2D g2d = (Graphics2D) g2.create();
                    g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
                            1.0f - (float) animFactor));
                    g2d.drawRect(x - 1, y1 - 1, picWidth + 1, height + 1);
                } else if ((next && i == nextPicture)
                        || (!next && i == selectedPicture - 1)) {
                    Graphics2D g2d = (Graphics2D) g2.create();
                    g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
                            (float) animFactor));
                    g2d.drawRect(x - 1, y1 - 1, picWidth + 1, height + 1);
                }

                g2.drawImage(thumb, x, y1, picWidth, height, null);

                if (x < picWidth || x + picWidth > getWidth() - picWidth) {
                    int x1 = 0;

                    Graphics2D g2d = (Graphics2D) g2.create();
                    g2d.setComposite(AlphaComposite.DstOut);

                    if (x + picWidth > getWidth() - picWidth) {
                        double scaleY = (double) height / (double) alphaMask.getHeight();
                        AffineTransform transform = AffineTransform.getScaleInstance(-1.0, scaleY);
                        x1 = getWidth();
                        g2d.translate(x1, y1);
                        g2d.drawImage(alphaMask, transform, null);
                    } else {
                        g2d.drawImage(alphaMask, x1, y1 - 1, picWidth, height + 1, null);
                    }
                }
            }

            x += picWidth + THUMB_SPACING;
            i++;
        }
    }

    private void paintInfo(Graphics2D g2) {
        g2.setColor(Color.WHITE);

        if (Debug.isDebug()) {
            g2.drawString("X: " + camPosX, 5, 15);
            g2.drawString("Y: " + camPosY, 5, 30);
            g2.drawString("Z: " + camPosZ, 5, 45);
        }

        if (textImage != null) {
            Composite composite = g2.getComposite();
            g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, textAlpha));
            g2.drawImage(textImage, null,
                    (int) (getWidth() - textImage.getWidth()) / 2,
                    (int) (getHeight() - textFont.getSize() * 1.7));
            g2.setComposite(composite);
        }
    }

    private void setupForegroundGraphics(Graphics2D g2) {
        g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
                RenderingHints.VALUE_INTERPOLATION_BILINEAR);
    }

    @Override
    protected void render3DScene(final GL gl, final GLU glu) {
        initScene(gl);
        initAndDisposeQuads(gl);

        Renderable scene = new Renderable() {
            @Override
            public Point3f getPosition() {
                return null;
            }

            public void render(GL gl) {
                setupCamera(gl, glu);
                renderItems(gl);
            }

            public void init(GL gl) {
            }
        };

        if (antiAliasing) {
            GLUtilities.renderAntiAliased(gl, scene, 4);
        } else {
            scene.render(gl);
        }
    }

    private void initQuads(GL gl) {
        for (Renderable item : renderables) {
            if (item != null) {
                item.init(gl);
            }
        }
    }

    private void initAndDisposeQuads(final GL gl) {
        while (!initQuadsQueue.isEmpty()) {
            Renderable quad = initQuadsQueue.poll();
            if (quad != null) {
                quad.init(gl);
            }
        }

        while (!disposeQuadsQueue.isEmpty()) {
            Renderable quad = disposeQuadsQueue.poll();
            if (quad != null) {
                quad.dispose(gl);
            }
        }
    }

    private void initScene(GL gl) {
        gl.glMatrixMode(GL.GL_MODELVIEW);
        gl.glLoadIdentity();
    }

    private void setupCamera(GL gl, GLU glu) {
        glu.gluLookAt(camPosX, camPosY, camPosZ,
                0.0f, 0.0f, 0.0f,
                0.0f, 1.0f, 0.0f);
        gl.glTranslatef(0.0f, -1.0f, 0.0f);
    }

    private void renderItems(GL gl) {
        for (Renderable renderable : renderables) {
            setAndRender(gl, renderable);
        }
    }

    private void setAndRender(GL gl, Renderable renderable) {
        if (renderable == null) {
            return;
        }

        Point3f pos = renderable.getPosition();
        Point3i rot = renderable.getRotation();
        Point3f scale = renderable.getScale();

        gl.glPushMatrix();
        gl.glScalef(scale.x, scale.y, scale.z);
        gl.glTranslatef(pos.x, pos.y, pos.z);
        gl.glRotatef(rot.x, 1.0f, 0.0f, 0.0f);
        gl.glRotatef(rot.y, 0.0f, 1.0f, 0.0f);
        gl.glRotatef(rot.z, 0.0f, 0.0f, 1.0f);

        renderable.render(gl);
        gl.glPopMatrix();
    }

    private final class ZoomAnimation implements ActionListener {

        private final int ANIM_DELAY = 400;
        private long start;

        private ZoomAnimation() {
            start = System.currentTimeMillis();
        }

        public void actionPerformed(ActionEvent e) {
            long elapsed = System.currentTimeMillis() - start;
            if (elapsed >= ANIM_DELAY) {
                javax.swing.Timer timer = (javax.swing.Timer) e.getSource();
                timer.stop();
            } else {
                double factor = (double) elapsed / (double) ANIM_DELAY;
                animateQuads(curve.compute(factor));
            }
            repaint();
        }

        private void animateQuads(double factor) {
            if (!pictureIsShowing) {
                factor = 1.0 - factor;
            }

            Renderable quad = renderables[INDEX_SELECTED_PICTURE];
            Point3f position = quad.getPosition();

            quad.setRotation(0, (int) (30.0 * (1.0 - factor)), 0);
            quad.setPosition((float) (-7.0f * (1.0 - factor)),
                    position.y,
                    (float) (30.0 * factor));

            quad = renderables[INDEX_NEXT_PICTURE];
            if (quad != null) {
                position = quad.getPosition();
                quad.setPosition(36.0f + (float) (120.0f * factor),
                        position.y,
                        position.z);
            }
        }
    }

    private final class SlideAnimation implements ActionListener {

        private final int ANIM_DELAY = 800;

        private final boolean next;
        private long start;

        private SlideAnimation(boolean next) {
            this.next = next;
            start = System.currentTimeMillis();

            if (next) {
                if (nextPicture < pictures.size()) {
                    nextTextImage = generateTextImage(pictures.get(nextPicture));
                } else {
                    nextTextImage = null;
                }
            } else {
                if (selectedPicture > 0) {
                    nextTextImage = generateTextImage(pictures.get(selectedPicture - 1));
                } else {
                    nextTextImage = null;
                }
            }
        }

        public void actionPerformed(ActionEvent e) {
            long elapsed = System.currentTimeMillis() - start;
            if (elapsed >= ANIM_DELAY) {
                javax.swing.Timer timer = (javax.swing.Timer) e.getSource();
                timer.stop();

                if (next) {
                    selectNextPicture();
                } else {
                    selectPreviousPicture();
                }

                Action action = getActionMap().get(KEY_ACTION_NEXT_PICTURE);
                action.setEnabled(selectedPicture < pictures.size() - 1);

                action = getActionMap().get(KEY_ACTION_PREVIOUS_PICTURE);
                action.setEnabled(selectedPicture > 0 && pictures.size() > 1);

                animFactor = 0.0;
            } else {
                double factor = (double) elapsed / (double) ANIM_DELAY;
                double curvedFactor = curve.compute(factor);
                animFactor = curvedFactor;

                if (next) {
                    animateQuadsNext(curvedFactor);
                } else {
                    animateQuadsPrevious(1.0 - curvedFactor);
                }

                setTextAlpha(elapsed, factor);
            }

            repaint();
        }

        private void animateQuadsNext(double factor) {
            Renderable quad = renderables[INDEX_SELECTED_PICTURE];
            Point3f position = quad.getPosition();
            quad.setPosition(-7.0f - QUAD_WIDTH * 2.0f * (float) factor, position.y, position.z);

            ReflectedQuad reflected = (ReflectedQuad) renderables[INDEX_NEXT_PICTURE];
            if (reflected != null) {
                float scale = 0.5f + 0.5f * (float) factor;

                reflected.setScale(scale, scale, scale);
                reflected.setRotation(0, (int) (-20.0 + 50.0 * factor), 0);
                reflected.setPosition((float) (36.0f - 43.0f * factor),
                        (float) (-reflected.getHeight() * (1.0f - scale)),
                        (float) (30.0 * (1.0 - factor)));
            }

            quad = renderables[INDEX_RIGHT_PICTURE];
            if (quad != null) {
                position = quad.getPosition();
                quad.setPosition(36.0f + 160.0f * (float) (1.0 - factor), position.y, position.z);
            }
        }

        private void animateQuadsPrevious(double factor) {
            ReflectedQuad reflected = (ReflectedQuad) renderables[INDEX_SELECTED_PICTURE];
            float scale = 0.5f + 0.5f * (float) factor;

            reflected.setScale(scale, scale, scale);
            reflected.setRotation(0, (int) (-20.0 + 50.0 * factor), 0);
            reflected.setPosition((float) (36.0f - 43.0f * factor),
                    (float) (-reflected.getHeight() * (1.0f - scale)),
                    (float) (30.0 * (1.0 - factor)));

            Renderable quad = renderables[INDEX_NEXT_PICTURE];
            if (quad != null) {
                Point3f position = quad.getPosition();
                quad.setPosition(36.0f + 160.0f * (float) (1.0 - factor), position.y, position.z);
            }

            quad = renderables[INDEX_LEFT_PICTURE];
            if (quad != null) {
                Point3f position = quad.getPosition();
                quad.setPosition(-7.0f - QUAD_WIDTH * 2.0f * (float) factor, position.y, position.z);
            }
        }

        private void setTextAlpha(long elapsed, double factor) {
            if (elapsed < ANIM_DELAY / 2.0) {
                textAlpha = (float) (1.0 - 2.0 * factor);
            } else {
                textAlpha = (float) ((factor - 0.5) * 2.0);
                if (textAlpha > 1.0f) {
                    textAlpha = 1.0f;
                }
            }
            if (textAlpha < 0.1f) {
                textAlpha = 0.1f;
                textImage = nextTextImage;
            }
        }

        private void selectPreviousPicture() {
            selectedPicture--;
            nextPicture--;

            if (renderables[INDEX_RIGHT_PICTURE] != null) {
                disposeQuadsQueue.add(renderables[INDEX_RIGHT_PICTURE]);
            }

            Renderable quad = renderables[INDEX_NEXT_PICTURE];
            if (quad != null) {
                renderables[INDEX_RIGHT_PICTURE] = quad;
                quad.setScale(0.5f, 0.5f, 0.5f);
                quad.setPosition(196.0f, -((ReflectedQuad) quad).getHeight() / 2.0f, 30.0f);
                quad.setRotation(0, -20, 0);
            }

            quad = renderables[INDEX_SELECTED_PICTURE];
            renderables[INDEX_NEXT_PICTURE] = quad;

            nextTextImage = generateTextImage(pictures.get(nextPicture));

            quad = renderables[INDEX_LEFT_PICTURE];
            renderables[INDEX_SELECTED_PICTURE] = quad;

            textImage = generateTextImage(pictures.get(selectedPicture));

            if (selectedPicture > 0) {
                initQuadsQueue.add(createQuad(INDEX_LEFT_PICTURE, selectedPicture - 1));
            } else {
                renderables[INDEX_LEFT_PICTURE] = null;
            }
        }

        private void selectNextPicture() {
            selectedPicture++;
            nextPicture++;

            if (renderables[INDEX_LEFT_PICTURE] != null) {
                disposeQuadsQueue.add(renderables[INDEX_LEFT_PICTURE]);
            }

            Renderable quad = renderables[INDEX_SELECTED_PICTURE];
            renderables[INDEX_LEFT_PICTURE] = quad;
            quad.setPosition(-7.0f - QUAD_WIDTH * 2.0f, 0.0f, 0.0f);
            quad.setRotation(0, 30, 0);

            quad = renderables[INDEX_NEXT_PICTURE];
            renderables[INDEX_SELECTED_PICTURE] = quad;

            textImage = generateTextImage(pictures.get(selectedPicture));

            if (nextPicture < pictures.size()) {
                quad = renderables[INDEX_RIGHT_PICTURE];
                renderables[INDEX_NEXT_PICTURE] = quad;
                nextTextImage = generateTextImage(pictures.get(nextPicture));
            } else {
                renderables[INDEX_NEXT_PICTURE] = null;
            }

            if (nextPicture < pictures.size() - 1) {
                initQuadsQueue.add(createQuad(INDEX_RIGHT_PICTURE, nextPicture + 1));
            } else {
                renderables[INDEX_RIGHT_PICTURE] = null;
            }
        }
    }

    private final class NextPictureAction extends AbstractAction {

        public NextPictureAction() {
            super("Next");
            ImageIcon nextIconActive = new ImageIcon(PictureViewer.class.getResource("*forum.digit.in/images/pictureviewer-next-button.png"));
            ImageIcon nextIconPressed = new ImageIcon(PictureViewer.class.getResource("*forum.digit.in/images/pictureviewer-next-button-pressed.png"));
            ImageIcon disabledIcon = new ImageIcon(PictureViewer.class.getResource("*forum.digit.in/images/pictureviewer-next-disabled-button.png"));

            setEnabled(false);

            putValue("disabledIcon", disabledIcon);
            putValue("pressedIcon", nextIconPressed);
            putValue(Action.LARGE_ICON_KEY, nextIconActive);
            putValue(Action.ACTION_COMMAND_KEY, "next");
            putValue(Action.SHORT_DESCRIPTION, "Show next picture");
        }

        public void actionPerformed(ActionEvent e) {
            nextPicture();
        }
    }

    private final class PreviousPictureAction extends AbstractAction {

        public PreviousPictureAction() {
            super("Previous");
            ImageIcon previousIconActive = new ImageIcon(PictureViewer.class.getResource("*forum.digit.in/images/pictureviewer-previous-button.png"));
            ImageIcon previousIconPressed = new ImageIcon(PictureViewer.class.getResource("*forum.digit.in/images/pictureviewer-previous-button-pressed.png"));
            ImageIcon disabledIcon = new ImageIcon(PictureViewer.class.getResource("*forum.digit.in/images/pictureviewer-previous-disabled-button.png"));

            setEnabled(false);

            putValue("disabledIcon", disabledIcon);
            putValue("pressedIcon", previousIconPressed);
            putValue(Action.LARGE_ICON_KEY, previousIconActive);
            putValue(Action.ACTION_COMMAND_KEY, "previous");
            putValue(Action.SHORT_DESCRIPTION, "Show previous picture");
        }

        public void actionPerformed(ActionEvent e) {
            previousPicture();
        }
    }

    private final class ShowPictureAction extends AbstractAction {

        private ImageIcon showIconActive;
        private ImageIcon showIconAll;
        private ImageIcon showIconPressed;
        private ImageIcon showIconAllPressed;

        public ShowPictureAction() {
            super(pictureIsShowing ? "Show All" : "Show Picture");

            showIconActive = new ImageIcon(PictureViewer.class.getResource("*forum.digit.in/images/pictureviewer-show-button.png"));
            showIconPressed = new ImageIcon(PictureViewer.class.getResource("*forum.digit.in/images/pictureviewer-show-button-pressed.png"));
            showIconAll = new ImageIcon(PictureViewer.class.getResource("*forum.digit.in/images/pictureviewer-show-all-button.png"));
            showIconAllPressed = new ImageIcon(PictureViewer.class.getResource("*forum.digit.in/images/pictureviewer-show-all-button-pressed.png"));

            ImageIcon disabledIcon = new ImageIcon(PictureViewer.class.getResource("*forum.digit.in/images/pictureviewer-show-disabled-button.png"));

            setEnabled(false);

            putValue("disabledIcon", disabledIcon);
            putValue("pressedIcon", pictureIsShowing ? showIconAllPressed : showIconPressed);
            putValue(Action.LARGE_ICON_KEY, pictureIsShowing ? showIconAll : showIconActive);
            putValue(Action.ACTION_COMMAND_KEY, "show");
            putValue(Action.SHORT_DESCRIPTION, "Show selected picture");
        }

        public void actionPerformed(ActionEvent e) {
            showSelectedPicture();
        }

        public void toggleName() {
            putValue(Action.NAME, pictureIsShowing ? "Show All" : "Show Picture");
            putValue(Action.LARGE_ICON_KEY, pictureIsShowing ? showIconAll : showIconActive);
            putValue("pressedIcon", pictureIsShowing ? showIconAllPressed : showIconPressed);
        }
    }

    private final class ControlButton extends JButton implements PropertyChangeListener {

        public ControlButton(Action action) {
            super(action);

            getAction().addPropertyChangeListener(this);

            setPressedIcon((Icon) getAction().getValue("pressedIcon"));
            setDisabledIcon((Icon) getAction().getValue("disabledIcon"));

            setForeground(grayColor);
            setFocusable(false);
            setFocusPainted(false);
            setBorderPainted(false);
            setContentAreaFilled(false);
            setOpaque(false);
            setMargin(new Insets(0, 0, 0, 0));
            setHideActionText(true);
        }

        @Override
        public void setToolTipText(String text) {
        }

        public void propertyChange(PropertyChangeEvent evt) {
            if ("pressedIcon".equals(evt.getPropertyName())) {
                setPressedIcon((Icon) evt.getNewValue());
            }
        }
    }

    private final class ControlPanel extends JPanel {

        private BufferedImage background;

        public ControlPanel() {
            super(new FlowLayout(FlowLayout.CENTER, 2, 2));
            setOpaque(false);
        }

        @Override
        protected void paintComponent(Graphics g) {
            if (background == null) {
                createBackground();
            }

            g.drawImage(background, 0, 0, null);
        }

        private void createBackground() {
            background = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
            Graphics2D g2 = background.createGraphics();

            g2.setColor(Color.WHITE);
            g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f));

            Insets insets = getInsets();
            RoundRectangle2D rect = new RoundRectangle2D.Double(insets.left, insets.top,
                    getWidth() - insets.right - insets.left,
                    getHeight() - insets.bottom - insets.top,
                    14, 14);
            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            g2.fill(rect);

            g2.dispose();
        }
    }

    private final class MouseWheelDriver implements MouseWheelListener {

        @Override
        public void mouseWheelMoved(MouseWheelEvent e) {
            if (e.getWheelRotation() > 0) {
                nextPicture();
            } else {
                previousPicture();
            }
        }
    }
}

class Quad extends Renderable {
    // texture

    protected BufferedImage textureImage = null;
    protected Texture texture = null;
    protected Rectangle textureCrop = null;

    // geometry
    protected float width, height;

    // alpha
    protected float alpha = 1.0f;

    public Quad(float x, float y, float z,
            float width, float height,
            BufferedImage textureImage) {
        super(x, y, z);
        setDimension(width, height);
        setTextureImage(textureImage);
    }

    public void setDimension(float width, float height) {
        this.width = width;
        this.height = height;
    }

    public float getWidth() {
        return width;
    }

    public float getHeight() {
        return height;
    }

    public float getAlpha() {
        return alpha;
    }

    public void setAlpha(float alpha) {
        this.alpha = alpha;
    }

    public void setTextureImage(BufferedImage textureImage) {
        if (textureImage == null) {
            throw new IllegalArgumentException("Quad texture cannot be null.");
        }

        this.textureImage = textureImage;
        setTextureCrop(null);
    }

    public Rectangle getTextureCrop() {
        return textureCrop;
    }

    public void setTextureCrop(Rectangle textureCrop) {
        if (textureCrop == null) {
            textureCrop = new Rectangle(0, 0,
                    textureImage.getWidth(),
                    textureImage.getHeight());
        }

        this.textureCrop = textureCrop;
    }

    public void init(GL gl) {
        texture = Texture.getInstance(gl, textureImage);
    }

    public void dispose(GL gl) {
        texture.dispose(gl);
    }

    // rendering
    public void render(GL gl) {
        float[] crop = texture.getSubImageTextureCoords(textureCrop.x,
                textureCrop.y,
                textureCrop.x + textureCrop.width,
                textureCrop.y + textureCrop.height);
        float tx1 = crop[0];
        float ty1 = crop[1];
        float tx2 = crop[2];
        float ty2 = crop[3];

        float x = -width / 2.0f;
        float y = -height / 2.0f;
        float z = 0.0f;

        if (alpha < 1.0f) {
            gl.glEnable(GL.GL_BLEND);
            gl.glBlendFunc(GL.GL_ONE, GL.GL_ONE_MINUS_SRC_ALPHA);
        }
        gl.glEnable(GL.GL_TEXTURE_2D);
        texture.bind(gl);
        gl.glTexEnvi(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, GL.GL_MODULATE);

        gl.glBegin(GL.GL_QUADS);

        // render solid/upright texture
        gl.glColor4f(alpha, alpha, alpha, alpha);
        gl.glTexCoord2f(tx2, ty1);
        gl.glVertex3f(x + width, y + height, z);
        gl.glTexCoord2f(tx1, ty1);
        gl.glVertex3f(x, y + height, z);
        gl.glTexCoord2f(tx1, ty2);
        gl.glVertex3f(x, y, z);
        gl.glTexCoord2f(tx2, ty2);
        gl.glVertex3f(x + width, y, z);

        gl.glEnd();

        gl.glDisable(GL.GL_TEXTURE_2D);
        if (alpha < 1.0f) {
            gl.glDisable(GL.GL_BLEND);
        }
    }
}

class ReflectedQuad extends Quad {
    // reflection

    protected float fadeDistance = 0.8f;
    protected float reflectionTransparency = 4.3f;

    public ReflectedQuad(float x, float y, float z,
            float width, float height,
            BufferedImage textureImage) {
        super(x, y, z, width, height, textureImage);
    }

    public float getFadeDistance() {
        return fadeDistance;
    }

    public void setFadeDistance(float fadeDistance) {
        this.fadeDistance = fadeDistance;
    }

    public float getReflectionTransparency() {
        return reflectionTransparency;
    }

    public void setReflectionTransparency(float reflectionTransparency) {
        this.reflectionTransparency = reflectionTransparency;
    }

    // rendering
    @Override
    public void render(GL gl) {
        float alpha = 1.0f;
        float[] crop = texture.getSubImageTextureCoords(textureCrop.x,
                textureCrop.y,
                textureCrop.x + textureCrop.width,
                textureCrop.y + textureCrop.height);
        float tx1 = crop[0];
        float ty1 = crop[1];
        float tx2 = crop[2];
        float ty2 = crop[3];

        float x = -width / 2.0f;
        float y = -height / 2.0f;
        float z = 0.0f;

        gl.glEnable(GL.GL_BLEND);
        gl.glBlendFunc(GL.GL_ONE, GL.GL_ONE_MINUS_SRC_ALPHA);
        gl.glEnable(GL.GL_TEXTURE_2D);
        texture.bind(gl);
        gl.glTexEnvi(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, GL.GL_MODULATE);

        gl.glBegin(GL.GL_QUADS);

        // render solid/upright texture
        gl.glColor4f(alpha, alpha, alpha, alpha);
        gl.glTexCoord2f(tx2, ty1);
        gl.glVertex3f(x + width, y + height, z);
        gl.glTexCoord2f(tx1, ty1);
        gl.glVertex3f(x, y + height, z);
        gl.glTexCoord2f(tx1, ty2);
        gl.glVertex3f(x, y, z);
        gl.glTexCoord2f(tx2, ty2);
        gl.glVertex3f(x + width, y, z);

        alpha /= reflectionTransparency;

        gl.glColor4f(alpha, alpha, alpha, alpha);
        gl.glTexCoord2f(tx2, ty2);
        gl.glVertex3f(x + width, y, z);
        gl.glTexCoord2f(tx1, ty2);
        gl.glVertex3f(x, y, z);
        gl.glColor4f(0.0f, 0.0f, 0.0f, 0.0f);
        gl.glTexCoord2f(tx1, ty2 * (1 - fadeDistance));
        gl.glVertex3f(x, y - (height * fadeDistance), z);
        gl.glTexCoord2f(tx2, ty2 * (1 - fadeDistance));
        gl.glVertex3f(x + width, y - (height * fadeDistance), z);

        gl.glEnd();

        gl.glDisable(GL.GL_TEXTURE_2D);
        gl.glDisable(GL.GL_BLEND);
    }
}

abstract class Renderable {

    protected int rotationX, rotationY, rotationZ;
    protected float scaleX, scaleY, scaleZ;
    protected float x, y, z;
    protected String name;

    public Renderable() {
        this(0.0f, 0.0f, 0.0f);
    }

    public Renderable(float x, float y, float z) {
        setPosition(x, y, z);
        setRotation(0, 0, 0);
        setScale(1.0f, 1.0f, 1.0f);
    }

    public Renderable(float[] coordinates) {
        this(coordinates[0], coordinates[1], coordinates[2]);
    }

    public Renderable(Point3f pos) {
        this(pos.x, pos.y, pos.z);
    }

    public void init(GL gl) {
    }

    public void dispose(GL gl) {
    }

    public abstract void render(GL gl);

    public void setPosition(float[] coordinates) {
        if (coordinates.length < 3) {
            throw new IllegalArgumentException("3 coordinates are required.");
        }

        setPosition(coordinates[0], coordinates[1], coordinates[2]);
    }

    public void setPosition(Point3f pos) {
        setPosition(pos.x, pos.y, pos.z);
    }

    public void setPosition(float x, float y, float z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }

    public Point3f getPosition() {
        return new Point3f(x, y, z);
    }

    public void setRotation(int x, int y, int z) {
        this.rotationX = x;
        this.rotationY = y;
        this.rotationZ = z;
    }

    public void setRotation(int[] coordinates) {
        if (coordinates.length < 3) {
            throw new IllegalArgumentException("3 coordinates are required.");
        }

        setRotation(coordinates[0], coordinates[1], coordinates[2]);
    }

    public void setRotation(Point3i rot) {
        setRotation(rot.x, rot.y, rot.z);
    }

    public Point3i getRotation() {
        return new Point3i(rotationX, rotationY, rotationZ);
    }

    public void setScale(float x, float y, float z) {
        this.scaleX = x;
        this.scaleY = y;
        this.scaleZ = z;
    }

    public void setScale(float[] coordinates) {
        if (coordinates.length < 3) {
            throw new IllegalArgumentException("3 coordinates are required.");
        }

        setScale(coordinates[0], coordinates[1], coordinates[2]);
    }

    public void setScale(Point3f scale) {
        setScale(scale.x, scale.y, scale.z);
    }

    public Point3f getScale() {
        return new Point3f(scaleX, scaleY, scaleZ);
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return name;
    }
}

class RenderableFactory {

    public static Renderable createBillboard(Renderable item) {
        return new Billboard(item);
    }

    public static Renderable createReflectedQuad(float x, float y, float z,
            float w, float h,
            URL texture, Rectangle crop) {
        try {
            BufferedImage image = ImageIO.read(texture);
            return createReflectedQuad(x, y, z, w, h, image, crop);
        } catch (IOException e) {
        }

        return null;
    }

    public static Renderable createReflectedQuad(float x, float y, float z,
            float w, float h,
            URL texture, Rectangle crop,
            String name) {
        Renderable quad = createReflectedQuad(x, y, z, w, h, texture, crop);
        ((ReflectedQuad) quad).setName(name);
        return quad;
    }

    public static Renderable createReflectedQuad(float x, float y, float z,
            float w, float h,
            BufferedImage texture, Rectangle crop) {
        ReflectedQuad quad = new ReflectedQuad(x, y, z, w, h, texture);
        quad.setTextureCrop(crop);
        return quad;
    }

    public static Renderable createReflectedQuad(float x, float y, float z,
            float w, float h,
            BufferedImage texture, Rectangle crop,
            String name) {
        Renderable quad = createReflectedQuad(x, y, z, w, h, texture, crop);
        quad.setName(name);
        return quad;
    }

    public static Renderable createQuad(float x, float y, float z,
            float w, float h,
            URL texture, Rectangle crop) {
        try {
            BufferedImage image = ImageIO.read(texture);
            return createQuad(x, y, z, w, h, image, crop);
        } catch (IOException e) {
        }

        return null;
    }

    public static Renderable createQuad(float x, float y, float z,
            float w, float h,
            URL texture, Rectangle crop,
            String name) {
        Renderable quad = createQuad(x, y, z, w, h, texture, crop);
        quad.setName(name);
        return quad;
    }

    public static Renderable createQuad(float x, float y, float z,
            float w, float h,
            BufferedImage texture, Rectangle crop) {
        Quad quad = new Quad(x, y, z, w, h, texture);
        quad.setTextureCrop(crop);
        return quad;
    }

    public static Renderable createQuad(float x, float y, float z,
            float w, float h,
            BufferedImage texture, Rectangle crop,
            String name) {
        Quad quad = new Quad(x, y, z, w, h, texture);
        quad.setTextureCrop(crop);
        quad.setName(name);
        return quad;
    }
}



/**
 * <p>
 * A shadow factory generates a drop shadow for any given picture, respecting
 * the transparency channel if present. The resulting picture contains the
 * shadow only and to create a drop shadow effect you will need to stack the
 * original picture and the shadow generated by the factory. If you are using
 * Swing you can get this done very easily with the layout
 * {@link org.jdesktop.swingx.StackLayout}.</p>
 * <h2>Shadow Properties</h2>
 * <p>
 * A shadow is defined by three properties:
 * <ul>
 * <li><i>size</i>: The size, in pixels, of the shadow. This property also
 * defines the fuzzyness.</li>
 * <li><i>opacity</i>: The opacity, between 0.0 and 1.0, of the shadow.</li>
 * <li><i>color</i>: The color of the shadow. Shadows are not meant to be black
 * only.</li>
 * </ul>
 * You can set these properties using the provided mutaters or the appropriate
 * constructor. Here are two ways of creating a green shadow of size 10 and with
 * an opacity of 50%:
 * <pre>
 * ShadowFactory factory = new ShadowFactory(10, 0.5f, Color.GREEN);
 * // ..
 * factory = new ShadowFactory();
 * factory.setSize(10);
 * factory.setOpacity(0.5f);
 * factory.setColor(Color.GREEN);
 * </pre> The default constructor provides the following default values:
 * <ul>
 * <li><i>size</i>: 5 pixels</li>
 * <li><i>opacity</i>: 50%</li>
 * <li><i>color</i>: Black</li>
 * </ul></p>
 * <h2>Shadow Quality</h2>
 * <p>
 * The factory provides two shadow generation algorithms: <i>fast quality
 * blur</i>
 * and <i>high quality blur</i>. You can select your preferred algorithm by
 * setting the appropriate rendering hint:
 * <pre>
 * ShadowFactory factory = new ShadowFactory();
 * factory.setRenderingHint(ShadowFactory.KEY_BLUR_QUALITY,
 *                          ShadowFactory.VALUE_BLUR_QUALITY_HIGH);
 * </pre> The default rendering algorihtm is
 * <code>VALUE_BLUR_QUALITY_FAST</code>.</p>
 * <p>
 * The current implementation should provide the same quality with both
 * algorithms but performances are guaranteed to be better (about 30 times
 * faster) with the <i>fast quality blur</i>.</p>
 * <h2>Generating a Shadow</h2>
 * <p>
 * A shadow is generated as a
 * <code>BufferedImage</code> from another
 * <code>BufferedImage</code>. Once the factory is set up, you must call
 * {@link #createShadow} to actually generate the shadow:
 * <pre>
 * ShadowFactory factory = new ShadowFactory();
 * // factory setup
 * BufferedImage shadow = factory.createShadow(bufferedImage);
 * </pre> The resulting image is of type
 * <code>BufferedImage.TYPE_INT_ARGB</code>. Both dimensions of this image are
 * larger than original image's:
 * <ul>
 * <li>new width = original width + 2 * shadow size</li>
 * <li>new height = original height + 2 * shadow size</li>
 * </ul>
 * This must be taken into account when you need to create a drop shadow
 * effect.</p>
 * <h2>Properties Changes</h2>
 * <p>
 * This factory allows to register property change listeners with
 * {@link #addPropertyChangeListener}. Listening to properties changes is very
 * useful when you emebed the factory in a graphical component and give the API
 * user the ability to access the factory. By listening to properties changes,
 * you can easily repaint the component when needed.</p>
 * <h2>Threading Issues</h2>
 * <p>
 * <code>ShadowFactory</code> is not guaranteed to be thread-safe.</p>
 *
 * @author JGuru
 *
 */
class ShadowFactory {

    /**
     * <p>
     * Key for the blur quality rendering hint.</p>
     */
    public static final String KEY_BLUR_QUALITY = "blur_quality";

    /**
     * <p>
     * Selects the fast rendering algorithm. This is the default rendering hint
     * for
     * <code>KEY_BLUR_QUALITY</code>.</p>
     */
    public static final String VALUE_BLUR_QUALITY_FAST = "fast";

    /**
     * <p>
     * Selects the high quality rendering algorithm. With current
     * implementation, This algorithm does not guarantee a better rendering
     * quality and should not be used.</p>
     */
    public static final String VALUE_BLUR_QUALITY_HIGH = "high";

    /**
     * <p>
     * Identifies a change to the size used to render the shadow.</p>
     * <p>
     * When the property change event is fired, the old value and the new value
     * are provided as
     * <code>Integer</code> instances.</p>
     */
    public static final String SIZE_CHANGED_PROPERTY = "shadow_size";

    /**
     * <p>
     * Identifies a change to the opacity used to render the shadow.</p>
     * <p>
     * When the property change event is fired, the old value and the new value
     * are provided as
     * <code>Float</code> instances.</p>
     */
    public static final String OPACITY_CHANGED_PROPERTY = "shadow_opacity";

    /**
     * <p>
     * Identifies a change to the color used to render the shadow.</p>
     */
    public static final String COLOR_CHANGED_PROPERTY = "shadow_color";

    // size of the shadow in pixels (defines the fuzziness)
    private int size = 5;

    // opacity of the shadow
    private float opacity = 0.5f;

    // color of the shadow
    private Color color = Color.BLACK;

    // rendering hints map
    private HashMap<Object, Object> hints;

    // notifies listeners of properties changes
    private PropertyChangeSupport changeSupport;

    /**
     * <p>
     * Creates a default good looking shadow generator. The default shadow
     * factory provides the following default values:
     * <ul>
     * <li><i>size</i>: 5 pixels</li>
     * <li><i>opacity</i>: 50%</li>
     * <li><i>color</i>: Black</li>
     * <li><i>rendering quality</i>: VALUE_BLUR_QUALITY_FAST</li>
     * </ul></p>
     * <p>
     * These properties provide a regular, good looking shadow.</p>
     */
    public ShadowFactory() {
        this(5, 0.5f, Color.BLACK);
    }

    /**
     * <p>
     * A shadow factory needs three properties to generate shadows. These
     * properties are:</p>
     * <ul>
     * <li><i>size</i>: The size, in pixels, of the shadow. This property also
     * defines the fuzzyness.</li>
     * <li><i>opacity</i>: The opacity, between 0.0 and 1.0, of the shadow.</li>
     * <li><i>color</i>: The color of the shadow. Shadows are not meant to be
     * black only.</li>
     * </ul></p>
     * <p>
     * Besides these properties you can set rendering hints to control the
     * rendering process. The default rendering hints let the factory use the
     * fastest shadow generation algorithm.</p>
     *
     *  [MENTION=9956]PARAM[/MENTION] size The size of the shadow in pixels. Defines the fuzziness.
     *  [MENTION=9956]PARAM[/MENTION] opacity The opacity of the shadow.
     *  [MENTION=9956]PARAM[/MENTION] color The color of the shadow.
     *  [MENTION=288550]see[/MENTION] #setRenderingHint(Object, Object)
     */
    public ShadowFactory(final int size, final float opacity, final Color color) {
        hints = new HashMap<Object, Object>();
        hints.put(KEY_BLUR_QUALITY, VALUE_BLUR_QUALITY_FAST);

        changeSupport = new PropertyChangeSupport(this);

        setSize(size);
        setOpacity(opacity);
        setColor(color);
    }

    /**
     * <p>
     * Add a PropertyChangeListener to the listener list. The listener is
     * registered for all properties. The same listener object may be added more
     * than once, and will be called as many times as it is added. If
     * <code>listener</code> is null, no exception is thrown and no action is
     * taken.</p>
     *
     *  [MENTION=9956]PARAM[/MENTION] listener the PropertyChangeListener to be added
     */
    public void addPropertyChangeListener(PropertyChangeListener listener) {
        changeSupport.addPropertyChangeListener(listener);
    }

    /**
     * <p>
     * Remove a PropertyChangeListener from the listener list. This removes a
     * PropertyChangeListener that was registered for all properties. If
     * <code>listener</code> was added more than once to the same event source,
     * it will be notified one less time after being removed. If
     * <code>listener</code> is null, or was never added, no exception is thrown
     * and no action is taken.</p>
     *
     *  [MENTION=9956]PARAM[/MENTION] listener
     */
    public void removePropertyChangeListener(PropertyChangeListener listener) {
        changeSupport.removePropertyChangeListener(listener);
    }

    /**
     * <p>
     * Maps the specified rendering hint
     * <code>key</code> to the specified
     * <code>value</code> in this
     * <code>SahdowFactory</code> object.</p>
     *
     *  [MENTION=9956]PARAM[/MENTION] key The rendering hint key
     *  [MENTION=9956]PARAM[/MENTION] value The rendering hint value
     */
    public void setRenderingHint(final Object key, final Object value) {
        hints.put(key, value);
    }

    /**
     * <p>
     * Gets the color used by the factory to generate shadows.</p>
     *
     * @return this factory's shadow color
     */
    public Color getColor() {
        return color;
    }

    /**
     * <p>
     * Sets the color used by the factory to generate shadows.</p>
     * <p>
     * Consecutive calls to {@link #createShadow} will all use this color until
     * it is set again.</p>
     * <p>
     * If the color provided is null, the previous color will be retained.</p>
     *
     *  [MENTION=9956]PARAM[/MENTION] shadowColor the generated shadows color
     */
    public void setColor(final Color shadowColor) {
        if (shadowColor != null) {
            Color oldColor = this.color;
            this.color = shadowColor;
            changeSupport.firePropertyChange(COLOR_CHANGED_PROPERTY,
                    oldColor,
                    this.color);
        }
    }

    /**
     * <p>
     * Gets the opacity used by the factory to generate shadows.</p>
     * <p>
     * The opacity is comprised between 0.0f and 1.0f; 0.0f being fully
     * transparent and 1.0f fully opaque.</p>
     *
     * @return this factory's shadow opacity
     */
    public float getOpacity() {
        return opacity;
    }

    /**
     * <p>
     * Sets the opacity used by the factory to generate shadows.</p>
     * <p>
     * Consecutive calls to {@link #createShadow} will all use this color until
     * it is set again.</p>
     * <p>
     * The opacity is comprised between 0.0f and 1.0f; 0.0f being fully
     * transparent and 1.0f fully opaque. If you provide a value out of these
     * boundaries, it will be restrained to the closest boundary.</p>
     *
     *  [MENTION=9956]PARAM[/MENTION] shadowOpacity the generated shadows opacity
     */
    public void setOpacity(final float shadowOpacity) {
        float oldOpacity = this.opacity;

        if (shadowOpacity < 0.0) {
            this.opacity = 0.0f;
        } else if (shadowOpacity > 1.0f) {
            this.opacity = 1.0f;
        } else {
            this.opacity = shadowOpacity;
        }

        changeSupport.firePropertyChange(OPACITY_CHANGED_PROPERTY,
                new Float(oldOpacity),
                new Float(this.opacity));
    }

    /**
     * <p>
     * Gets the size in pixel used by the factory to generate shadows.</p>
     *
     * @return this factory's shadow size
     */
    public int getSize() {
        return size;
    }

    /**
     * <p>
     * Sets the size, in pixels, used by the factory to generate shadows.</p>
     * <p>
     * The size defines the blur radius applied to the shadow to create the
     * fuzziness.</p>
     * <p>
     * There is virtually no limit to the size but it has an impact on shadow
     * generation performances. The greater this value, the longer it will take
     * to generate the shadow. Remember the generated shadow image dimensions
     * are computed as follow:
     * <ul>
     * <li>new width = original width + 2 * shadow size</li>
     * <li>new height = original height + 2 * shadow size</li>
     * </ul>
     * The size cannot be negative. If you provide a negative value, the size
     * will be 0 instead.</p>
     *
     *  [MENTION=9956]PARAM[/MENTION] shadowSize the generated shadows size in pixels (fuzziness)
     */
    public void setSize(final int shadowSize) {
        int oldSize = this.size;

        if (shadowSize < 0) {
            this.size = 0;
        } else {
            this.size = shadowSize;
        }

        changeSupport.firePropertyChange(SIZE_CHANGED_PROPERTY,
                new Integer(oldSize),
                new Integer(this.size));
    }

    /**
     * <p>
     * Generates the shadow for a given picture and the current properties of
     * the factory.</p>
     * <p>
     * The generated shadow image dimensions are computed as follow:
     * <ul>
     * <li>new width = original width + 2 * shadow size</li>
     * <li>new height = original height + 2 * shadow size</li>
     * </ul></p>
     * <p>
     * The time taken by a call to this method depends on the size of the
     * shadow, the larger the longer it takes, and on the selected rendering
     * algorithm.</p>
     *
     *  [MENTION=9956]PARAM[/MENTION] image the picture from which the shadow must be cast
     * @return the picture containing the shadow of <code>image</code>
     */
    public BufferedImage createShadow(final BufferedImage image) {
        if (hints.get(KEY_BLUR_QUALITY) == VALUE_BLUR_QUALITY_HIGH) {
            // the high quality algorithm is a 3-pass algorithm
            // it goes through all the pixels of the original picture at least
            // three times to generate the shadow
            // it is easy to understand but very slow
            BufferedImage subject = prepareImage(image);
            BufferedImage shadow = new BufferedImage(subject.getWidth(),
                    subject.getHeight(),
                    BufferedImage.TYPE_INT_ARGB);
            BufferedImage shadowMask = createShadowMask(subject);
            getLinearBlurOp(size).filter(shadowMask, shadow);
            return shadow;
        }

        // call the fast rendering algorithm
        return createShadowFast(image);
    }

    // prepares the picture for the high quality rendering algorithm
    private BufferedImage prepareImage(final BufferedImage image) {
        BufferedImage subject = new BufferedImage(image.getWidth() + size * 2,
                image.getHeight() + size * 2,
                BufferedImage.TYPE_INT_ARGB);

        Graphics2D g2 = subject.createGraphics();
        g2.drawImage(image, null, size, size);
        g2.dispose();

        return subject;
    }

    // fast rendering algorithm
    // basically applies duplicates the picture and applies a size*size kernel
    // in only one pass.
    // the kernel is simulated by an horizontal and a vertical pass
    // implemented by Sébastien Petrucci
    private BufferedImage createShadowFast(final BufferedImage src) {
        int shadowSize = this.size;

        int srcWidth = src.getWidth();
        int srcHeight = src.getHeight();

        int dstWidth = srcWidth + size;
        int dstHeight = srcHeight + size;

        int left = (shadowSize - 1) >> 1;
        int right = shadowSize - left;

        int yStop = dstHeight - right;

        BufferedImage dst = new BufferedImage(dstWidth, dstHeight,
                BufferedImage.TYPE_INT_ARGB);

        int shadowRgb = color.getRGB() & 0x00FFFFFF;

        int[] aHistory = new int[shadowSize];
        int historyIdx;

        int aSum;

        ColorModel srcColorModel = src.getColorModel();
        WritableRaster srcRaster = src.getRaster();
        int[] dstBuffer = ((DataBufferInt) dst.getRaster().getDataBuffer()).getData();

        int lastPixelOffset = right * dstWidth;
        float hSumDivider = 1.0f / size;
        float vSumDivider = opacity / size;

        // horizontal pass : extract the alpha mask from the source picture and
        // blur it into the destination picture
        for (int srcY = 0, dstOffset = left * dstWidth; srcY < srcHeight; srcY++) {

            // first pixels are empty
            for (historyIdx = 0; historyIdx < shadowSize;) {
                aHistory[historyIdx++] = 0;
            }

            aSum = 0;
            historyIdx = 0;

            // compute the blur average with pixels from the source image
            for (int srcX = 0; srcX < srcWidth; srcX++) {

                int a = (int) (aSum * hSumDivider); // calculate alpha value
                dstBuffer[dstOffset++] = a << 24;   // store the alpha value only
                // the shadow color will be added in the next pass

                aSum -= aHistory[historyIdx]; // substract the oldest pixel from the sum

                // extract the new pixel ...
                a = srcColorModel.getAlpha(srcRaster.getDataElements(srcX, srcY, null));
                aHistory[historyIdx] = a;   // ... and store its value into history
                aSum += a;                  // ... and add its value to the sum

                if (++historyIdx >= shadowSize) {
                    historyIdx -= shadowSize;
                }
            }

            // blur the end of the row - no new pixels to grab
            for (int i = 0; i < shadowSize; i++) {

                int a = (int) (aSum * hSumDivider);
                dstBuffer[dstOffset++] = a << 24;

                // substract the oldest pixel from the sum ... and nothing new to add !
                aSum -= aHistory[historyIdx];

                if (++historyIdx >= shadowSize) {
                    historyIdx -= shadowSize;
                }
            }
        }

        // vertical pass
        for (int x = 0, bufferOffset = 0; x < dstWidth; x++, bufferOffset = x) {

            aSum = 0;

            // first pixels are empty
            for (historyIdx = 0; historyIdx < left;) {
                aHistory[historyIdx++] = 0;
            }

            // and then they come from the dstBuffer
            for (int y = 0; y < right; y++, bufferOffset += dstWidth) {
                int a = dstBuffer[bufferOffset] >>> 24;         // extract alpha
                aHistory[historyIdx++] = a;                     // store into history
                aSum += a;                                      // and add to sum
            }

            bufferOffset = x;
            historyIdx = 0;

            // compute the blur average with pixels from the previous pass
            for (int y = 0; y < yStop; y++, bufferOffset += dstWidth) {

                int a = (int) (aSum * vSumDivider);             // calculate alpha value
                dstBuffer[bufferOffset] = a << 24 | shadowRgb;  // store alpha value + shadow color

                aSum -= aHistory[historyIdx];   // substract the oldest pixel from the sum

                a = dstBuffer[bufferOffset + lastPixelOffset] >>> 24;   // extract the new pixel ...
                aHistory[historyIdx] = a;                               // ... and store its value into history
                aSum += a;                                              // ... and add its value to the sum

                if (++historyIdx >= shadowSize) {
                    historyIdx -= shadowSize;
                }
            }

            // blur the end of the column - no pixels to grab anymore
            for (int y = yStop; y < dstHeight; y++, bufferOffset += dstWidth) {

                int a = (int) (aSum * vSumDivider);
                dstBuffer[bufferOffset] = a << 24 | shadowRgb;

                aSum -= aHistory[historyIdx];   // substract the oldest pixel from the sum

                if (++historyIdx >= shadowSize) {
                    historyIdx -= shadowSize;
                }
            }
        }

        return dst;
    }

    // creates the shadow mask for the original picture
    // it colorize all the pixels with the shadow color according to their
    // original transparency
    private BufferedImage createShadowMask(final BufferedImage image) {
        BufferedImage mask = new BufferedImage(image.getWidth(),
                image.getHeight(),
                BufferedImage.TYPE_INT_ARGB);

        Graphics2D g2d = mask.createGraphics();
        g2d.drawImage(image, 0, 0, null);
        g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_IN,
                opacity));
        g2d.setColor(color);
        g2d.fillRect(0, 0, image.getWidth(), image.getHeight());
        g2d.dispose();

        return mask;
    }

    // creates a blur convolve operation by generating a kernel of
    // dimensions (size, size).
    private ConvolveOp getLinearBlurOp(final int size) {
        float[] data = new float[size * size];
        float value = 1.0f / (float) (size * size);
        for (int i = 0; i < data.length; i++) {
            data[i] = value;
        }
        return new ConvolveOp(new Kernel(size, size, data));
    }
}



/**
 * REMIND: translucent images will have premultiplied comps by default...
 */
class Texture {

    /**
     * The GL target type
     */
    private int target;
    /**
     * The GL texture ID
     */
    private int texID;
    /**
     * The width of the texture
     */
    private int texWidth;
    /**
     * The height of the texture
     */
    private int texHeight;
    /**
     * The width of the image
     */
    private int imgWidth;
    /**
     * The height of the image
     */
    private int imgHeight;

    /**
     * REMIND
     */
    public float tx1, ty1, tx2, ty2;

    private static ColorModel rgbaColorModel
            = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB),
            new int[]{8, 8, 8, 8}, true, true,
            Transparency.TRANSLUCENT,
            DataBuffer.TYPE_BYTE);
    private static ColorModel rgbColorModel
            = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB),
            new int[]{8, 8, 8, 0}, false, false,
            Transparency.OPAQUE,
            DataBuffer.TYPE_BYTE);

    private Texture(int target, int texID, int texWidth, int texHeight) {
        this.target = target;
        this.texID = texID;
        this.texWidth = texWidth;
        this.texHeight = texHeight;
    }

    public static Texture getInstance(GL gl, File f) throws IOException {
        BufferedImage img = ImageIO.read(f);
        return getInstance(gl, img);
    }

    public static Texture getInstance(GL gl, BufferedImage image) {
        boolean texNonPow2 = false;
        boolean texRectangle = false;
        Texture tex = null;

        if (texNonPow2) {
        } else if (texRectangle) {
        } else {
            int pixelFormat = image.getColorModel().hasAlpha() ? GL.GL_RGBA
                    : GL.GL_RGB;
            int texWidth = getNextPowerOfTwo(image.getWidth());
            int texHeight = getNextPowerOfTwo(image.getHeight());
            tex = createTexture(gl, GL.GL_TEXTURE_2D, pixelFormat, texWidth,
                    texHeight);
            tex.updateImage(gl, image);
        }
        return tex;
    }

    private static Texture createTexture(GL gl, int target, int pixelFormat,
            int texWidth, int texHeight) {
        // REMIND
        int minFilter = GL.GL_LINEAR;
        int magFilter = GL.GL_LINEAR;

        int texID = createTextureID(gl);
        gl.glBindTexture(target, texID);
        gl.glTexImage2D(target, 0, pixelFormat, texWidth, texHeight, 0,
                pixelFormat, GL.GL_UNSIGNED_BYTE, null);

        // REMIND
        if (target == GL.GL_TEXTURE_2D) {
            gl.glTexParameteri(target, GL.GL_TEXTURE_MIN_FILTER, minFilter);
            gl.glTexParameteri(target, GL.GL_TEXTURE_MAG_FILTER, magFilter);
            gl.glTexParameteri(target, GL.GL_TEXTURE_WRAP_S,
                    GL.GL_CLAMP_TO_EDGE);
            gl.glTexParameteri(target, GL.GL_TEXTURE_WRAP_T,
                    GL.GL_CLAMP_TO_EDGE);
        }

        return new Texture(target, texID, texWidth, texHeight);
    }

    /**
     * Creates a new texture ID.
     *
     *  [MENTION=9956]PARAM[/MENTION] gl the context used to create the texture object.
     * @return a new texture ID
     */
    private static int createTextureID(GL gl) {
        int[] tmp = new int[1];
        gl.glGenTextures(1, tmp, 0);
        return tmp[0];
    }

    /**
     * Returns the nearest power of two that is larger than the given value.
     *
     *  [MENTION=9956]PARAM[/MENTION] val the value
     * @return the next power of 2
     */
    private static int getNextPowerOfTwo(int val) {
        int ret = 1;
        while (ret < val) {
            ret *= 2;
        }
        return ret;
    }

    /**
     * REMIND
     */
    private static ByteBuffer convertToByteBuffer(BufferedImage image) {
        int width = image.getWidth();
        int height = image.getHeight();

        // create a temporary image that is compatible with OpenGL
        ColorModel cm = image.getColorModel().hasAlpha() ? rgbaColorModel
                : rgbColorModel;
        boolean premult = cm.isAlphaPremultiplied();
        WritableRaster raster = cm
                .createCompatibleWritableRaster(width, height);
        BufferedImage texImage = new BufferedImage(cm, raster, premult, null);

        // copy the source image into the temporary image
        Graphics2D g = texImage.createGraphics();
        g.setComposite(AlphaComposite.Src);
        g.drawImage(image, 0, 0, null);
        g.dispose();

        // build a byte buffer from the temporary image
        byte[] data = ((DataBufferByte) texImage.getRaster().getDataBuffer())
                .getData();
        ByteBuffer imageBuffer = ByteBuffer.allocateDirect(data.length);
        imageBuffer.order(ByteOrder.nativeOrder());
        imageBuffer.put(data, 0, data.length);
        imageBuffer.rewind();

        return imageBuffer;
    }

    /**
     * Binds this texture to the specified GL context.
     *
     *  [MENTION=9956]PARAM[/MENTION] gl the GL context to bind to
     */
    public void bind(GL gl) {
        gl.glBindTexture(target, texID);
    }

    /**
     * Returns the texture coordinates corresponding to a subregion of the image
     * stored in this texture, as specified by the given bounding box. (yadda
     * yadda yadda)
     */
    public float[] getSubImageTextureCoords(int x1, int y1, int x2, int y2) {
        float[] coords = new float[4];
        // REMIND: different approach needed for GL_ARB_texture_rectangle
        coords[0] = (float) x1 / (float) texWidth;
        coords[1] = (float) y1 / (float) texHeight;
        coords[2] = (float) x2 / (float) texWidth;
        coords[3] = (float) y2 / (float) texHeight;
        return coords;
    }

    /**
     * REMIND
     */
    public void updateImage(GL gl, BufferedImage image) {
        // REMIND: we should ensure that the new image dimensions are not
        // larger than the current texture dimensions (or perhaps we
        // could clip the incoming image to the texture dimensions if the
        // image is larger?)
        imgWidth = image.getWidth();
        imgHeight = image.getHeight();
        setImageSize(imgWidth, imgHeight);

        int pixelFormat = image.getColorModel().hasAlpha() ? GL.GL_RGBA
                : GL.GL_RGB;
        ByteBuffer imageBuffer = convertToByteBuffer(image);
        bind(gl);
        gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, pixelFormat == GL.GL_RGBA ? 4
                : 1);
        gl.glTexSubImage2D(target, 0, 0, 0, imgWidth, imgHeight, pixelFormat,
                GL.GL_UNSIGNED_BYTE, imageBuffer);
    }

    /**
     * Disposes the native resources used by this texture object.
     *
     *  [MENTION=9956]PARAM[/MENTION] gl the GL context to use for disposing the texture object
     */
    public void dispose(GL gl) {
        gl.glDeleteTextures(1, new int[]{texID}, 0);
    }

    /**
     * REMIND
     */
    public int getTarget() {
        return target;
    }

    /**
     * Returns the width of the texture.
     *
     * @return the width of the texture
     */
    public int getWidth() {
        return texWidth;
    }

    /**
     * Returns the height of the texture.
     *
     * @return the height of the texture
     */
    public int getHeight() {
        return texHeight;
    }

    /**
     * Returns the width of the original image.
     *
     * @return the width of the original image
     */
    public int getImageWidth() {
        return imgWidth;
    }

    /**
     * Returns the height of the original image.
     *
     * @return the height of the original image
     */
    public int getImageHeight() {
        return imgHeight;
    }

    /**
     * Updates the actual image dimensions (usually only called from
     * <code>updateImage</code>.
     */
    private void setImageSize(int width, int height) {
        imgWidth = width;
        imgHeight = height;
        tx1 = 0.0f;
        ty1 = 0.0f;
        tx2 = (float) imgWidth / (float) texWidth;
        ty2 = (float) imgHeight / (float) texHeight;
    }
}

*s4.postimg.org/tl6ec097d/Photos3d_Viewer.png

*s4.postimg.org/hqbhv1o6x/Photos3d_Viewer_Files.png


9) Write a Rubik Cube applet in Java?

Ans) Here is the code!!

Code:
// Rubik's Cube 3D simulator
// Karl Hïrrnell, March 11 1996
// Last modified October 6

import java.awt.*;

public final class rubik extends java.applet.Applet {

    int i, j, k, n, o, p, q, lastX, lastY, dx, dy;
    int rectX[], rectY[];
    Color colList[], bgcolor;
    final double sideVec[] = {0, 0, 1, 0, 0, -1, 0, -1, 0, 1, 0, 0, 0, 1, 0, -1, 0, 0}; // Normal vectors
    final double corners[] = {-1, -1, -1, 1, -1, -1, 1, 1, -1, -1, 1, -1,
        -1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1, 1}; // Vertex co-ordinates
    double topCorners[], botCorners[];
    final int sides[] = {4, 5, 6, 7, 3, 2, 1, 0, 0, 1, 5, 4, 1, 2, 6, 5, 2, 3, 7, 6, 0, 4, 7, 3};
    final int nextSide[] = {2, 3, 4, 5, 4, 3, 2, 5, 1, 3, 0, 5, 1, 4, 0, 2, 1, 5, 0, 3, 2, 0, 4, 1};
    final int mainBlocks[] = {0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3};
    final int twistDir[] = {-1, 1, -1, 1, -1, 1, -1, 1, 1, 1, 1, 1, 1, -1, 1, -1, 1, 1, 1, 1, -1, 1, -1, 1};
    final int colDir[] = {-1, -1, 1, -1, 1, -1};
    final int circleOrder[] = {0, 1, 2, 5, 8, 7, 6, 3};
    int topBlocks[], botBlocks[];
    int sideCols[], sideW, sideH;
    int dragReg, twistSide = -1;
    int nearSide[], buffer[]; // Which side belongs to dragCorn
    double dragCorn[], dragDir[];
    double eye[] = {0.3651, 0.1826, -0.9129}; // Initial observer co-ordinate axes (view)
    double eX[] = {0.9309, -0.0716, 0.3581}; // (sideways)
    double eY[]; // (vertical)
    double Teye[], TeX[], TeY[];
    double light[], temp[] = {0, 0, 0}, temp2[] = {0, 0, 0}, newCoord[];
    double sx, sy, sdxh, sdyh, sdxv, sdyv, d, t1, t2, t3, t4, t5, t6;
    double phi, phibase = 0, Cphi, Sphi, currDragDir[];
    boolean naturalState = true, twisting = false, OKtoDrag = false;
    Math m;
    Graphics offGraphics;
    Image offImage;

    public void init() {

        offImage = createImage(120, 120); // Double buffer
        offGraphics = offImage.getGraphics();
        rectX = new int[4];
        rectY = new int[4];
        newCoord = new double[16]; // Projected co-ordinates (on screen)
        dragDir = new double[24];
        dragCorn = new double[96];
        topCorners = new double[24]; // Vertex co-ordinate storage
        botCorners = new double[24]; // for sub-cubes during twist
        topBlocks = new int[24];
        botBlocks = new int[24];
        buffer = new int[12];
        nearSide = new int[12];
        light = new double[3];
        Teye = new double[3];
        TeX = new double[3];
        TeY = new double[3];
        currDragDir = new double[2];
        eY = new double[3];
        vecProd(eye, 0, eX, 0, eY, 0); // Fix y axis of observer co-ordinate system
        normalize(eY, 0);
        colList = new Color[120];
        for (i = 0; i < 20; i++) {
            colList[i] = new Color(103 + i * 8, 103 + i * 8, 103 + i * 8);		// White
            colList[i + 20] = new Color(i * 6, i * 6, 84 + i * 9);			// Blue
            colList[i + 40] = new Color(84 + i * 9, i * 5, i * 5);			// Red
            colList[i + 60] = new Color(i * 6, 84 + i * 9, i * 6);			// Green
            colList[i + 80] = new Color(84 + i * 9, 84 + i * 9, i * 6);		    // Yellow
            colList[i + 100] = new Color(84 + i * 9, 55 + i * 8, i * 3);		// Orange
        }
        sideCols = new int[54];
        for (i = 0; i < 54; i++) {
            sideCols[i] = i / 9;
        }
        bgcolor = findBGColor();
        resize(125, 125);
        repaint();
    }

    public Color findBGColor() // Convert hexadecimal RGB parameter to color
    {
        int hex[];
        String s, h = "0123456789abcdef";
        Color c;
        hex = new int[6];
        s = getParameter("bgcolor");
        if ((s != null) && (s.length() == 6)) {
            for (i = 0; i < 6; i++) {
                for (j = 0; j < 16; j++) {
                    if (Character.toLowerCase(s.charAt(i)) == h.charAt(j)) {
                        hex[i] = j;
                    }
                }
            }
            c = new Color(hex[0] * 16 + hex[1], hex[2] * 16 + hex[3], hex[4] * 16 + hex[5]);
        } else {
            c = Color.lightGray; // Default
        }
        return c;
    }

// Various vector manipulation functions
    public double scalProd(double v1[], int ix1, double v2[], int ix2) {
        return v1[ix1] * v2[ix2] + v1[ix1 + 1] * v2[ix2 + 1] + v1[ix1 + 2] * v2[ix2 + 2];
    }

    public double vNorm(double v[], int ix) {
        return m.sqrt(v[ix] * v[ix] + v[ix + 1] * v[ix + 1] + v[ix + 2] * v[ix + 2]);
    }

    public double cosAng(double v1[], int ix1, double v2[], int ix2) {
        return scalProd(v1, ix1, v2, ix2) / (vNorm(v1, ix1) * vNorm(v2, ix2));
    }

    public void normalize(double v[], int ix) {
        double t = vNorm(v, ix);
        v[ix] = v[ix] / t;
        v[ix + 1] = v[ix + 1] / t;
        v[ix + 2] = v[ix + 2] / t;
    }

    public void scalMult(double v[], int ix, double a) {
        v[ix] = v[ix] * a;
        v[ix + 1] = v[ix + 1] * a;
        v[ix + 2] = v[ix + 2] * a;
    }

    public void addVec(double v1[], int ix1, double v2[], int ix2) {
        v2[ix2] += v1[ix1];
        v2[ix2 + 1] += v1[ix1 + 1];
        v2[ix2 + 2] += v1[ix1 + 2];
    }

    public void subVec(double v1[], int ix1, double v2[], int ix2) {
        v2[ix2] -= v1[ix1];
        v2[ix2 + 1] -= v1[ix1 + 1];
        v2[ix2 + 2] -= v1[ix1 + 2];
    }

    public void copyVec(double v1[], int ix1, double v2[], int ix2) {
        v2[ix2] = v1[ix1];
        v2[ix2 + 1] = v1[ix1 + 1];
        v2[ix2 + 2] = v1[ix1 + 2];
    }

    public void vecProd(double v1[], int ix1, double v2[], int ix2,
            double v3[], int ix3) {
        v3[ix3] = v1[ix1 + 1] * v2[ix2 + 2] - v1[ix1 + 2] * v2[ix2 + 1];
        v3[ix3 + 1] = v1[ix1 + 2] * v2[ix2] - v1[ix1] * v2[ix2 + 2];
        v3[ix3 + 2] = v1[ix1] * v2[ix2 + 1] - v1[ix1 + 1] * v2[ix2];
    }

    public void cutUpCube() // Produce large and small sub-cube for twisting
    {
        boolean check;
        for (i = 0; i < 24; i++) // Copy main coordinate data
        {
            topCorners[i] = corners[i];
            botCorners[i] = corners[i];
        }
        copyVec(sideVec, 3 * twistSide, temp, 0); // Start manipulating and build new parts
        copyVec(temp, 0, temp2, 0); // Fix new co-ordinates. Some need to be altered.
        scalMult(temp, 0, 1.3333);
        scalMult(temp2, 0, 0.6667);
        for (i = 0; i < 8; i++) {
            check = false;
            for (j = 0; j < 4; j++) {
                if (i == sides[twistSide * 4 + j]) {
                    check = true;
                }
            }
            if (check) {
                subVec(temp2, 0, botCorners, i * 3);
            } else {
                addVec(temp, 0, topCorners, i * 3);
            }
        }

// The sub-cubes need information about which colored fields belong to them.
        for (i = 0; i < 24; i++) // Fix the sub-cube blockings. First copy data from main
        {
            topBlocks[i] = mainBlocks[i]; // Large sub-cube data
            botBlocks[i] = mainBlocks[i]; // Small sub-cube data
        }
        for (i = 0; i < 6; i++) {
            if (i == twistSide) {
                botBlocks[i * 4 + 1] = 0; // Large sub-cube is blank on top
                botBlocks[i * 4 + 3] = 0;
            } else {
                k = -1;
                for (j = 0; j < 4; j++) {
                    if (nextSide[i * 4 + j] == twistSide) {
                        k = j;
                    }
                }
                switch (k) // Twisted side adjacent to...
                {
                    case 0: // Up side?
                    {
                        topBlocks[i * 4 + 3] = 1;
                        botBlocks[i * 4 + 2] = 1;
                        break;
                    }
                    case 1: // Right side?
                    {
                        topBlocks[i * 4] = 2;
                        botBlocks[i * 4 + 1] = 2;
                        break;
                    }
                    case 2: // Down side?
                    {
                        topBlocks[i * 4 + 2] = 2;
                        botBlocks[i * 4 + 3] = 2;
                        break;
                    }
                    case 3: // Left side?
                    {
                        topBlocks[i * 4 + 1] = 1;
                        botBlocks[i * 4] = 1;
                        break;
                    }
                    case -1: // None
                    {
                        topBlocks[i * 4 + 1] = 0; // Small sub-cube is blank on bottom
                        topBlocks[i * 4 + 3] = 0;
                        break;
                    }
                }
            }
        }
    }

    public boolean keyDown(java.awt.Event evt, int key) {
        if (key == 114) // Restore
        {
            twisting = false;
            naturalState = true;
            for (i = 0; i < 54; i++) {
                sideCols[i] = i / 9;
            }
            repaint();
        } else if (key == 115) // Scramble
        {
            twisting = false;
            naturalState = true;
            for (i = 0; i < 20; i++) {
                colorTwist((int) (m.random() * 6), (int) (m.random() * 3 + 1));
            }
            repaint();
        }
        return false;
    }

    public boolean mouseDrag(java.awt.Event evt, int x, int y) {
        boolean check;
        double x1, x2, y1, y2, alpha, beta;

        if ((!twisting) && (OKtoDrag)) {
            OKtoDrag = false;
            check = false;
            for (i = 0; i < dragReg; i++) // Check if inside a drag region
            {
                x1 = dragCorn[i * 8 + 1] - dragCorn[i * 8];
                x2 = dragCorn[i * 8 + 5] - dragCorn[i * 8 + 4];
                y1 = dragCorn[i * 8 + 3] - dragCorn[i * 8];
                y2 = dragCorn[i * 8 + 7] - dragCorn[i * 8 + 4];
                alpha = (y2 * (lastX - dragCorn[i * 8]) - y1 * (lastY - dragCorn[i * 8 + 4]))
                        / (x1 * y2 - y1 * x2);
                beta = (-x2 * (lastX - dragCorn[i * 8]) + x1 * (lastY - dragCorn[i * 8 + 4]))
                        / (x1 * y2 - y1 * x2);
                if ((alpha > 0) && (alpha < 1) && (beta > 0) && (beta < 1)) // We're in
                {
                    currDragDir[0] = dragDir[i * 2];
                    currDragDir[1] = dragDir[i * 2 + 1];
                    d = currDragDir[0] * (x - lastX) + currDragDir[1] * (y - lastY);
                    d = d * d / ((currDragDir[0] * currDragDir[0] + currDragDir[1] * currDragDir[1])
                            * ((x - lastX) * (x - lastX) + (y - lastY) * (y - lastY)));
                    if (d > 0.6) {
                        check = true;
                        twistSide = nearSide[i];
                        i = 100;
                    }
                }
            }
            if (check) // We're twisting
            {
                if (naturalState) // The cube still hasn't been split up
                {
                    cutUpCube();
                    naturalState = false;
                }
                twisting = true;
                phi = 0.02 * (currDragDir[0] * (x - lastX) + currDragDir[1] * (y - lastY))
                        / m.sqrt(currDragDir[0] * currDragDir[0] + currDragDir[1] * currDragDir[1]);
                repaint();
                return false;
            }
        }

        OKtoDrag = false;
        if (!twisting) // Normal rotation
        {
            dx = lastX - x; // Vertical shift
            copyVec(eX, 0, temp, 0);
            scalMult(temp, 0, ((double) dx) * 0.016);
            addVec(temp, 0, eye, 0);
            vecProd(eY, 0, eye, 0, eX, 0);
            normalize(eX, 0);
            normalize(eye, 0);
            dy = y - lastY; // Horizontal shift
            copyVec(eY, 0, temp, 0);
            scalMult(temp, 0, ((double) dy) * 0.016);
            addVec(temp, 0, eye, 0);
            vecProd(eye, 0, eX, 0, eY, 0);
            normalize(eY, 0);
            normalize(eye, 0);
            lastX = x;
            lastY = y;
            repaint();
        } else // Twist, compute twisting angle phi
        {
            phi = 0.02 * (currDragDir[0] * (x - lastX) + currDragDir[1] * (y - lastY))
                    / m.sqrt(currDragDir[0] * currDragDir[0] + currDragDir[1] * currDragDir[1]);
            repaint();
        }
        return false;
    }

    public boolean mouseDown(java.awt.Event evt, int x, int y) {
        lastX = x;
        lastY = y;
        OKtoDrag = true;
        return false;
    }

    public boolean mouseUp(java.awt.Event evt, int x, int y) {
        int quads;
        double qu;
        if (twisting) // We have let go of the mouse when twisting
        {
            twisting = false;
            phibase += phi; // Save twist angle
            phi = 0;
            qu = phibase;
            while (qu < 0) {
                qu += 125.662;
            }
            quads = ((int) (qu * 3.183));
            if (((quads % 5) == 0) || ((quads % 5) == 4)) // Close enough to a corner?
            {
                quads = ((quads + 1) / 5) % 4;
                if (colDir[twistSide] < 0) {
                    quads = (4 - quads) % 4;
                }
                phibase = 0;
                naturalState = true; // Return the cube to its natural state
                colorTwist(twistSide, quads); // and shift the colored fields
            }
            repaint();
        }
        return false;
    }

    public void colorTwist(int sideNum, int quads) // Shift colored fields
    {
        int i, j, k, l = 0;
        k = quads * 2; // quads = number of 90-degree multiples
        for (i = 0; i < 8; i++) {
            buffer[k] = sideCols[sideNum * 9 + circleOrder[i]];
            k = (k + 1) % 8;
        }
        for (i = 0; i < 8; i++) {
            sideCols[sideNum * 9 + circleOrder[i]] = buffer[i];
        }
        k = quads * 3;
        for (i = 0; i < 4; i++) {
            for (j = 0; j < 4; j++) {
                if (nextSide[nextSide[sideNum * 4 + i] * 4 + j] == sideNum) {
                    l = j;
                }
            }
            for (j = 0; j < 3; j++) {
                switch (l) {
                    case 0:
                        buffer[k] = sideCols[nextSide[sideNum * 4 + i] * 9 + j];
                        break;
                    case 1:
                        buffer[k] = sideCols[nextSide[sideNum * 4 + i] * 9 + 2 + 3 * j];
                        break;
                    case 2:
                        buffer[k] = sideCols[nextSide[sideNum * 4 + i] * 9 + 8 - j];
                        break;
                    case 3:
                        buffer[k] = sideCols[nextSide[sideNum * 4 + i] * 9 + 6 - 3 * j];
                        break;
                    default:
                        break;
                }
                k = (k + 1) % 12;
            }
        }
        k = 0;
        for (i = 0; i < 4; i++) {
            for (j = 0; j < 4; j++) {
                if (nextSide[nextSide[sideNum * 4 + i] * 4 + j] == sideNum) {
                    l = j;
                }
            }
            for (j = 0; j < 3; j++) {
                switch (l) {
                    case 0:
                        sideCols[nextSide[sideNum * 4 + i] * 9 + j] = buffer[k];
                        break;
                    case 1:
                        sideCols[nextSide[sideNum * 4 + i] * 9 + 2 + 3 * j] = buffer[k];
                        break;
                    case 2:
                        sideCols[nextSide[sideNum * 4 + i] * 9 + 8 - j] = buffer[k];
                        break;
                    case 3:
                        sideCols[nextSide[sideNum * 4 + i] * 9 + 6 - 3 * j] = buffer[k];
                        break;
                    default:
                        break;
                }
                k++;
            }
        }
    }

    public void paint(Graphics g) {
        dragReg = 0;
        offGraphics.setColor(bgcolor); // Clear drawing buffer
        offGraphics.fillRect(0, 0, 120, 120);
        if (naturalState) {
            fixBlock(eye, eX, eY, corners, mainBlocks, 0); // Draw cube
        } else {
            copyVec(eye, 0, Teye, 0); // In twisted state? Compute top observer
            copyVec(eX, 0, TeX, 0);
            Cphi = m.cos(phi + phibase);
            Sphi = -m.sin(phi + phibase);
            switch (twistSide) // Twist around which axis?
            {
                case 0: // z
                    Teye[0] = Cphi * eye[0] + Sphi * eye[1];
                    TeX[0] = Cphi * eX[0] + Sphi * eX[1];
                    Teye[1] = -Sphi * eye[0] + Cphi * eye[1];
                    TeX[1] = -Sphi * eX[0] + Cphi * eX[1];
                    break;
                case 1: // -z
                    Teye[0] = Cphi * eye[0] - Sphi * eye[1];
                    TeX[0] = Cphi * eX[0] - Sphi * eX[1];
                    Teye[1] = Sphi * eye[0] + Cphi * eye[1];
                    TeX[1] = Sphi * eX[0] + Cphi * eX[1];
                    break;
                case 2: // -y
                    Teye[0] = Cphi * eye[0] - Sphi * eye[2];
                    TeX[0] = Cphi * eX[0] - Sphi * eX[2];
                    Teye[2] = Sphi * eye[0] + Cphi * eye[2];
                    TeX[2] = Sphi * eX[0] + Cphi * eX[2];
                    break;
                case 3: // x
                    Teye[1] = Cphi * eye[1] + Sphi * eye[2];
                    TeX[1] = Cphi * eX[1] + Sphi * eX[2];
                    Teye[2] = -Sphi * eye[1] + Cphi * eye[2];
                    TeX[2] = -Sphi * eX[1] + Cphi * eX[2];
                    break;
                case 4: // y
                    Teye[0] = Cphi * eye[0] + Sphi * eye[2];
                    TeX[0] = Cphi * eX[0] + Sphi * eX[2];
                    Teye[2] = -Sphi * eye[0] + Cphi * eye[2];
                    TeX[2] = -Sphi * eX[0] + Cphi * eX[2];
                    break;
                case 5: // -x
                    Teye[1] = Cphi * eye[1] - Sphi * eye[2];
                    TeX[1] = Cphi * eX[1] - Sphi * eX[2];
                    Teye[2] = Sphi * eye[1] + Cphi * eye[2];
                    TeX[2] = Sphi * eX[1] + Cphi * eX[2];
                    break;
                default:
                    break;
            }
            vecProd(Teye, 0, TeX, 0, TeY, 0);
            if (scalProd(eye, 0, sideVec, twistSide * 3) < 0) // Top facing away? Draw it first
            {
                fixBlock(Teye, TeX, TeY, topCorners, topBlocks, 2);
                fixBlock(eye, eX, eY, botCorners, botBlocks, 1);
            } else {
                fixBlock(eye, eX, eY, botCorners, botBlocks, 1);
                fixBlock(Teye, TeX, TeY, topCorners, topBlocks, 2);
            }
        }
        g.drawImage(offImage, 0, 0, this);
    }

    public void update(Graphics g) {
        paint(g);
    }

// Draw cube or sub-cube
    public void fixBlock(double beye[], double beX[], double beY[],
            double bcorners[], int bblocks[], int mode) {
        copyVec(beye, 0, light, 0);
        scalMult(light, 0, -3);
        addVec(beX, 0, light, 0);
        subVec(beY, 0, light, 0);

        for (i = 0; i < 8; i++) // Project 3D co-ordinates into 2D screen ones
        {
            newCoord[i * 2] = (60 + 35.1 * scalProd(bcorners, i * 3, beX, 0));
            newCoord[i * 2 + 1] = (60 - 35.1 * scalProd(bcorners, i * 3, beY, 0));
        }

        for (i = 0; i < 6; i++) {
            if (scalProd(beye, 0, sideVec, 3 * i) > 0.001) // Face towards us? Draw it.
            {
                k = (int) (9.6 * (1 - cosAng(light, 0, sideVec, 3 * i)));
                offGraphics.setColor(Color.black);
                for (j = 0; j < 4; j++) // Find corner co-ordinates
                {
                    rectX[j] = (int) newCoord[2 * sides[i * 4 + j]];
                    rectY[j] = (int) newCoord[2 * sides[i * 4 + j] + 1];
                }
                offGraphics.fillPolygon(rectX, rectY, 4); // First draw black
                sideW = bblocks[i * 4 + 1] - bblocks[i * 4];
                sideH = bblocks[i * 4 + 3] - bblocks[i * 4 + 2];
                if (sideW > 0) {
                    sx = newCoord[2 * sides[i * 4]];
                    sy = newCoord[2 * sides[i * 4] + 1];
                    sdxh = (newCoord[2 * sides[i * 4 + 1]] - sx) / sideW;
                    sdxv = (newCoord[2 * sides[i * 4 + 3]] - sx) / sideH;
                    sdyh = (newCoord[2 * sides[i * 4 + 1] + 1] - sy) / sideW;
                    sdyv = (newCoord[2 * sides[i * 4 + 3] + 1] - sy) / sideH;
                    p = bblocks[i * 4 + 2];
                    for (n = 0; n < sideH; n++) // Then draw colored fields
                    {
                        q = bblocks[i * 4];
                        for (o = 0; o < sideW; o++) {
                            rectX[0] = (int) (sx + (o + 0.1) * sdxh + (n + 0.1) * sdxv);
                            rectX[1] = (int) (sx + (o + 0.9) * sdxh + (n + 0.1) * sdxv);
                            rectX[2] = (int) (sx + (o + 0.9) * sdxh + (n + 0.9) * sdxv);
                            rectX[3] = (int) (sx + (o + 0.1) * sdxh + (n + 0.9) * sdxv);
                            rectY[0] = (int) (sy + (o + 0.1) * sdyh + (n + 0.1) * sdyv);
                            rectY[1] = (int) (sy + (o + 0.9) * sdyh + (n + 0.1) * sdyv);
                            rectY[2] = (int) (sy + (o + 0.9) * sdyh + (n + 0.9) * sdyv);
                            rectY[3] = (int) (sy + (o + 0.1) * sdyh + (n + 0.9) * sdyv);
                            offGraphics.setColor(colList[20 * sideCols[i * 9 + p * 3 + q] + k]);
                            offGraphics.fillPolygon(rectX, rectY, 4);
                            q++;
                        }
                        p++;
                    }
                }
                switch (mode) // Determine allowed drag regions and directions
                {
                    case 0: // Just the normal cube
                        t1 = sx;
                        t2 = sy;
                        t3 = sdxh;
                        t4 = sdyh;
                        t5 = sdxv;
                        t6 = sdyv;
                        for (j = 0; j < 4; j++) {
                            dragCorn[8 * dragReg] = t1;
                            dragCorn[8 * dragReg + 4] = t2;
                            dragCorn[8 * dragReg + 3] = t1 + t5;
                            dragCorn[8 * dragReg + 7] = t2 + t6;
                            t1 = t1 + t3 * 3;
                            t2 = t2 + t4 * 3;
                            dragCorn[8 * dragReg + 1] = t1;
                            dragCorn[8 * dragReg + 5] = t2;
                            dragCorn[8 * dragReg + 2] = t1 + t5;
                            dragCorn[8 * dragReg + 6] = t2 + t6;
                            dragDir[dragReg * 2] = t3 * twistDir[i * 4 + j];
                            dragDir[dragReg * 2 + 1] = t4 * twistDir[i * 4 + j];
                            d = t3;
                            t3 = t5;
                            t5 = -d;
                            d = t4;
                            t4 = t6;
                            t6 = -d;
                            nearSide[dragReg] = nextSide[i * 4 + j];
                            dragReg++;
                        }
                        break;
                    case 1: // The large sub-cube
                        break;
                    case 2: // The small sub-cube (twistable part)
                        if ((i != twistSide) && (sideW > 0)) {
                            if (sideW == 3) // Determine positive drag direction
                            {
                                if (bblocks[i * 4 + 2] == 0) {
                                    dragDir[dragReg * 2] = sdxh * twistDir[i * 4];
                                    dragDir[dragReg * 2 + 1] = sdyh * twistDir[i * 4];
                                } else {
                                    dragDir[dragReg * 2] = -sdxh * twistDir[i * 4 + 2];
                                    dragDir[dragReg * 2 + 1] = -sdyh * twistDir[i * 4 + 2];
                                }
                            } else if (bblocks[i * 4] == 0) {
                                dragDir[dragReg * 2] = -sdxv * twistDir[i * 4 + 3];
                                dragDir[dragReg * 2 + 1] = -sdyv * twistDir[i * 4 + 3];
                            } else {
                                dragDir[dragReg * 2] = sdxv * twistDir[i * 4 + 1];
                                dragDir[dragReg * 2 + 1] = sdyv * twistDir[i * 4 + 1];
                            }
                            for (j = 0; j < 4; j++) {
                                dragCorn[dragReg * 8 + j] = newCoord[2 * sides[i * 4 + j]];
                                dragCorn[dragReg * 8 + 4 + j] = newCoord[2 * sides[i * 4 + j] + 1];
                            }
                            nearSide[dragReg] = twistSide;
                            dragReg++;
                        }
                        break;
                    default:
                        break;
                }
            }
        }
    }
}

*s3.postimg.org/5yjoodpwf/rubik.png



10) Write a Synth look demo using diffrent color buttons?

Even as Oracle continues its attempt to "reintroduce the Java Desktop," one complaint keeps surfacing from Java UI developers: It's far too difficult to create a
complete custom look and feel. Not only is the effort too time consuming, but the Swing UI code is poorly written and poorly documented, often looking hacked
together and badly planned. Creating a complete look and feel has required developers to subclass either the Metal look and feel's 39 classes or the Basic look
and feel's 60 classes. Who the heck wants to override an entire package to change the way an application looks? Another sure indicator of how difficult it is to
create a custom look and feel in Swing is that in an age when many developers donate their hard work for free to open source projects.

Beauty's only skin deep!!

Enter Synth, which Sun hopes will ease the process of personalizing an application's look. The goal for Synth is simple -- let developers create a new look and feel without writing one line of Java code. This seems like a good solution. Programmers in general aren't known as the most artistic of people, and graphic artists aren't generally experts on Java coding. Synth provides a happy compromise by removing the entire description of the look from the code and placing it in an external XML file and image files. This type of look and feel, one that's described solely in external files, is called a skin.
Sun isn't breaking any new ground with the idea of a skin. Hundreds of skins exist for Winamp and dozens for Firefox, for example, because they're simple to create with only an XML file to change. Imagine creating a look and feel quickly and easily for a Java application by simply modifying an XML file. And then imagine the possible results -- hundreds of unique Swing look and feels. Java UI developers have reason to celebrate.
This article digs deeply into the Synth look and feel. I'll show you everything you need to know to create a complete look or skin. You'll examine a sample skinned application that uses all of Synth's important concepts. Then I'll break the skin down step-by-step to teach you Synth concepts as you build the XML file.

But with Synth it's the XML code, not the Java code, that's most important to understand. Although the Synth XML format initially seems difficult and intimidating, it's really rather straightforward. If you use the KISS (Keep It Simple Stupid) mantra, you can quickly create an XML file and get a new look and feel up and running.
With the KISS directive in mind, I'll start by introducing the main building block of the Synth XML file -- the <style> tag. The <style> tag contains all the information needed to describe a component's style, such as the colors, fonts, image files, states, and component-specific properties. Although a single <style> tag can describe more than one component, the easiest way to build a Synth file is to create a style for each individual Swing component.
Once you finish creating the style, you link the style to a component. The <bind> tag tells the Synth engine to link a defined style to a component, as in Listing 2. This combination completely creates the component's new look.

Listing 2. Linking one style to one component

Code:
<style id="textfield">
   // describe colors, fonts, and states
</style>
<bind style="textfield" type="region" key="Textfield"/>

<style id="button">
   // describe colors, fonts, and states
</style>
<bind style="button" type="region" key="Button"/>

One note about the <bind> tag: The key attribute inside the <bind> tag maps to constants in the javax.swing.plaf.synth.Region class. The Synth engine uses these constants to link the style with an actual Swing component. Simple components, such as JButton and JTextField, use one constant. Some more-complex components, such as the JScrollBar and the JTabbedPane, have multiple constants for their different parts.
I recommend using the one-style-per-component setup until you are more familiar with the Synth format and can set up inheritance models in the XML. This structure doesn't take advantage of all of XML's hierarchical capabilities, but it's the most straightforward to set up, code, and debug.
Another important point to remember when dealing with the Synth XML file is that it doesn't validate anything. If you make typographical errors or use incorrect attributes in the XML, your mistake won't show up until a runtime exception is thrown when the look and feel is loaded. Translation: Test the XML file before you ship it to a customer.

Changing a color and font

The first step in creating the look and feel for the demo application is to set the default colors and fonts. You'll make the white Aharoni font the default font for every component that doesn't specifically set it otherwise.
You can place the XML to change a font anywhere inside the <style> tag. You embed the color inside a <state> tag. I'll discuss the <state> tag more later in this article, but it's enough to understand now that a simple <state></state> tag with no attributes encompasses every state, which is what you need here.
The color tag itself requires two attributes:
value can be any String representation of the java.awt.Color constants (for example, RED, BLUE), or it can be the hex representation of a color preceded by a "#" (for example, #669966).

type describes which area's color the file should set. The choices are BACKGROUND, FOREGROUND, TEXT_FOREGROUND, TEXT_BACKGROUND, and FOCUS.
The font tag has two required attributes and one optional one. They map directly to the three parameters in the java.awt.Font class:
name: The font's name (for example, Verdana, Arial).
size:The size in pixels.

style: Leaving out this optional tag results in a normal-looking font. Other options include BOLD and ITALIC. You can also specify a bold italic font by putting a space between the two attributes: BOLD ITALIC. (This technique for combining attributes holds true for all attributes in the Synth XML file.)
Finally, instead of binding this style to each JLabel and each JButton, you can bind it to every component in the application, by using the .* wildcard. This wildcard tells the Synth look and feel to give every component a default white Aharoni font. Listing 3 shows the complete XML code for setting the components' font and color:
Listing 3. Changing multiple components' fonts and colors

Code:
<style id="default">
   <font name="Aharoni" size="14"/>
   <state>
      <color value="#FFFFFF" type="FOREGROUND"/>
   </state>
</style>
<bind style="default" type="region" key=".*"/>

Using images

The textfield borders in Figure 2 aren't normal-looking single-pixel rectangular borders. You create them by using an image. This isn't an unfamiliar concept -- images have been used in buttons and labels for a while now -- but you can imagine where some issues will come up. How does the cursor know where to go, how does the text get painted, and how do you create different-sized text fields? These issues are addressed by the concept of image stretching. One image file must describe every size of text field in the application, so you need a way to tell the XML file how to stretch the image properly and how to handle the normal textfield activities (carat and text control).
Luckily, a method of doing this type of stretching has been available since the early skinned applications. The image must be divided into nine regions -- top, top right, right, bottom right, bottom, bottom left, left, top left, and center -- that you specify by an attribute in the XML file. The renderer can then stretch the image in a certain way to fit the allotted space. Figure 3 shows how the text-field image stretches.

Listing 4. Loading images

Code:
<style id="textfield">
   <opaque value="true"/>
   <state>
      <font name="Aharoni" size="14"/>
      <color value="#D2DFF2" type="BACKGROUND"/>
      <color value="#000000" type="TEXT_FOREGROUND"/>
   </state>
   <imagePainter method="textFieldBorder" path="*forum.digit.in/images/textfield.png"
      sourceInsets="4 6 4 6" paintCenter="false"/>
   <insets top="4" left="6" bottom="4" right="6"/>
</style>
<bind style="textfield" type="region" key="TextField"/>


Ans) Here is the code!!!

Code:
/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */


import java.text.ParseException;
import javax.swing.*;
import javax.swing.plaf.synth.SynthLookAndFeel;

/**
 *
 * @author Sowndar
 */
public class SynthFrame extends JFrame {

    private static final long serialVersionUID = 1L;

    private JPanel jContentPane = null;
    private JLabel lblName = null;
    private JLabel lblPass = null;
    private JLabel lblSound = null;
    private JButton btnCancel = null;
    private JButton btnOk = null;
    private JTextField txtName = null;
    private JTextField txtPass = null;
    private JCheckBox checkNew = null;

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

        SwingUtilities.invokeAndWait(new Runnable() {

            public void run() {
                try {

                    SynthLookAndFeel synth = new SynthLookAndFeel();
                    synth.load(SynthFrame.class.getResourceAsStream("demo.xml"), SynthFrame.class);
                    UIManager.setLookAndFeel(synth);
                } catch (ParseException ex) {
                    System.err.println("Error loading file demo.xml!!");
                } catch (UnsupportedLookAndFeelException ex) {
                    System.err.println("Error loading file demo.xml!!");
                }
            }
        });

        SynthFrame f = new SynthFrame();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

    public SynthFrame() {
        super();
        initialize();
    }

    private void initialize() {
        this.setSize(341, 275);
        this.setContentPane(getJContentPane());
        this.setTitle("SynthDemo - Login");
        this.setResizable(false);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    private JPanel getJContentPane() {
        if (jContentPane == null) {
            jContentPane = new JPanel();
            java.awt.GridBagConstraints consGridBagConstraints2 = new java.awt.GridBagConstraints();
            java.awt.GridBagConstraints consGridBagConstraints1 = new java.awt.GridBagConstraints();
            java.awt.GridBagConstraints consGridBagConstraints3 = new java.awt.GridBagConstraints();
            java.awt.GridBagConstraints consGridBagConstraints5 = new java.awt.GridBagConstraints();
            java.awt.GridBagConstraints consGridBagConstraints6 = new java.awt.GridBagConstraints();
            java.awt.GridBagConstraints consGridBagConstraints7 = new java.awt.GridBagConstraints();
            java.awt.GridBagConstraints consGridBagConstraints4 = new java.awt.GridBagConstraints();
            java.awt.GridBagConstraints consGridBagConstraints8 = new java.awt.GridBagConstraints();
            consGridBagConstraints2.insets = new java.awt.Insets(12, 36, 16, 6);
            consGridBagConstraints2.ipady = 4;
            consGridBagConstraints2.ipadx = 25;
            consGridBagConstraints2.gridy = 1;
            consGridBagConstraints2.gridx = 0;
            consGridBagConstraints1.insets = new java.awt.Insets(42, 46, 12, 6);
            consGridBagConstraints1.ipady = 3;
            consGridBagConstraints1.ipadx = 10;
            consGridBagConstraints1.gridy = 0;
            consGridBagConstraints1.gridx = 0;
            consGridBagConstraints5.insets = new java.awt.Insets(13, 18, 35, 7);
            consGridBagConstraints5.ipady = -1;
            consGridBagConstraints5.ipadx = 22;
            consGridBagConstraints5.gridy = 3;
            consGridBagConstraints5.gridx = 1;
            consGridBagConstraints6.insets = new java.awt.Insets(38, 6, 8, 29);
            consGridBagConstraints6.ipady = 7;
            consGridBagConstraints6.ipadx = 166;
            consGridBagConstraints6.fill = java.awt.GridBagConstraints.HORIZONTAL;
            consGridBagConstraints6.weightx = 1.0;
            consGridBagConstraints6.gridwidth = 2;
            consGridBagConstraints6.gridy = 0;
            consGridBagConstraints6.gridx = 1;
            consGridBagConstraints3.insets = new java.awt.Insets(12, 39, 14, 13);
            consGridBagConstraints3.ipady = 5;
            consGridBagConstraints3.ipadx = 14;
            consGridBagConstraints3.gridy = 2;
            consGridBagConstraints3.gridx = 0;
            consGridBagConstraints8.insets = new java.awt.Insets(13, 7, 13, 42);
            consGridBagConstraints8.ipadx = 28;
            consGridBagConstraints8.gridy = 2;
            consGridBagConstraints8.gridx = 1;
            consGridBagConstraints7.insets = new java.awt.Insets(9, 6, 12, 0);
            consGridBagConstraints7.ipady = 7;
            consGridBagConstraints7.ipadx = 88;
            consGridBagConstraints7.fill = java.awt.GridBagConstraints.HORIZONTAL;
            consGridBagConstraints7.weightx = 1.0;
            consGridBagConstraints7.gridy = 1;
            consGridBagConstraints7.gridx = 1;
            consGridBagConstraints4.insets = new java.awt.Insets(13, 0, 35, 34);
            consGridBagConstraints4.ipady = -1;
            consGridBagConstraints4.gridy = 3;
            consGridBagConstraints4.gridx = 2;
            jContentPane.setLayout(new java.awt.GridBagLayout());
            jContentPane.add(getLblName(), consGridBagConstraints1);
            jContentPane.add(getLblPass(), consGridBagConstraints2);
            jContentPane.add(getLblSound(), consGridBagConstraints3);
            jContentPane.add(getBtnCancel(), consGridBagConstraints4);
            jContentPane.add(getBtnOk(), consGridBagConstraints5);
            jContentPane.add(getTxtName(), consGridBagConstraints6);
            jContentPane.add(getTxtPass(), consGridBagConstraints7);
            jContentPane.add(getCheckNew(), consGridBagConstraints8);
        }
        return jContentPane;
    }

    private JLabel getLblName() {
        if (lblName == null) {
            lblName = new JLabel();
            lblName.setText("User Name:");
            lblName.setHorizontalAlignment(SwingConstants.RIGHT);
        }
        return lblName;
    }

    private JLabel getLblPass() {
        if (lblPass == null) {
            lblPass = new JLabel();
            lblPass.setText("Password:");
            lblPass.setHorizontalAlignment(SwingConstants.RIGHT);
        }
        return lblPass;
    }

    private JLabel getLblSound() {
        if (lblSound == null) {
            lblSound = new JLabel();
            lblSound.setText("New User?");
            lblSound.setHorizontalAlignment(SwingConstants.RIGHT);
        }
        return lblSound;
    }

    private JButton getBtnCancel() {
        if (btnCancel == null) {
            btnCancel = new JButton();
            btnCancel.setText("Cancel");
        }
        return btnCancel;
    }

    private JButton getBtnOk() {
        if (btnOk == null) {
            btnOk = new JButton();
            btnOk.setText("OK");
        }
        return btnOk;
    }

    private JTextField getTxtName() {
        if (txtName == null) {
            txtName = new JTextField();
        }
        return txtName;
    }

    private JTextField getTxtPass() {
        if (txtPass == null) {
            txtPass = new JTextField();
        }
        return txtPass;
    }

    private JCheckBox getCheckNew() {
        if (checkNew == null) {
            checkNew = new JCheckBox();
        }
        return checkNew;
    }
}  //  @jve:visual-info  decl-index=0 visual-constraint="10,10"

BackgroundPainter.java - accesssory class to SynthFrame class
Code:
/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

import java.awt.Color;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.CubicCurve2D;

import javax.swing.UIManager;
import javax.swing.plaf.synth.SynthContext;
import javax.swing.plaf.synth.SynthPainter;

public class BackgroundPainter extends SynthPainter {

    @Override
    public void paintPanelBackground(SynthContext context,
            Graphics g, int x, int y,
            int w, int h) {
        Color start = UIManager.getColor("Panel.startBackground");
        Color end = UIManager.getColor("Panel.endBackground");
        Graphics2D g2 = (Graphics2D) g;
        GradientPaint grPaint = new GradientPaint(
                (float) x, (float) y, start,
                (float) w, (float) h, end);
        g2.setPaint(grPaint);
        g2.fillRect(x, y, w, h);
        g2.setPaint(null);
        g2.setColor(new Color(255, 255, 255, 120));
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        CubicCurve2D.Double arc2d = new CubicCurve2D.Double(0, h / 4, w / 3, h / 10, .66 * w, 1.5 * h, w, h / 8);
        g2.draw(arc2d);
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
    }
}

demo.xml file is needed for Synth painting!!

Code:
<synth>

  <style id="panel">  
	  <object id="background" class="BackgroundPainter"/>
	  <object class="javax.swing.plaf.ColorUIResource" id="startColor">
	    <int>30</int>
	    <int>123</int>
	    <int>235</int>
	  </object>
	  <defaultsProperty key="Panel.startBackground" type="idref" value="startColor"/>
	  <object class="javax.swing.plaf.ColorUIResource" id="endColor">
	    <int>1</int>
	    <int>20</int>
	    <int>80</int>
	  </object>
	  <defaultsProperty key="Panel.endBackground" type="idref" value="endColor"/>
     <painter method="panelBackground" idref="background"/>
  </style>
  <bind style="panel" type="region" key="Panel"/>


  <style id="default">
  		<font name="Aharoni" size="14"/> 
  		<state>
  		   <color value="#FFFFFF" type="FOREGROUND"/> 
  		</state>
  </style>
  <bind style="default" type="region" key=".*"/>	
  

  <style id="textfield">
    <state>
       <font name="Aharoni" size="14"/>
       <color value="#D2DFF2" type="BACKGROUND"/>
       <color value="#000000" type="TEXT_FOREGROUND"/>
    </state>
    <imagePainter method="textFieldBorder" path="*forum.digit.in/images/textfield.png"
                  sourceInsets="4 6 4 6" paintCenter="false"/>
    <insets top="4" left="6" bottom="4" right="6"/>
  </style>
  <bind style="textfield" type="region" key="TextField"/>
    
  
  <style id="button">
      <state>
	    <imagePainter method="buttonBackground" path="*forum.digit.in/images/button.png"
	                  sourceInsets="9 10 9 12" paintCenter="true" stretch="true"/>
	    <insets top="9" left="10" bottom="9" right="12"/>
	    <font name="Aharoni" size="16"/>
	    <color type="TEXT_FOREGROUND" value="#FFFFFF"/>
      </state>
      <state value="MOUSE_OVER">    
	    <imagePainter method="buttonBackground" path="*forum.digit.in/images/button_on.png"
	                  sourceInsets="9 10 9 12" paintCenter="true" stretch="true"/>
	    <insets top="9" left="10" bottom="9" right="12"/>
	    <color type="TEXT_FOREGROUND" value="#FFFFFF"/>
      </state>
      <state value="PRESSED"> 
	    <imagePainter method="buttonBackground" path="*forum.digit.in/images/button_press.png"
	                  sourceInsets="10 12 8 9" paintCenter="true" stretch="true"/>
	    <insets top="10" left="12" bottom="8" right="9"/>
	    <color type="TEXT_FOREGROUND" value="#FFFFFF"/>      
      </state>
    <property key="Button.margin" type="insets" value="0 0 0 0"/>
  </style>
  <bind style="button" type="region" key="Button"/>
      
      
  <style id="checkbox">
    	<imageIcon id="check_off" path="*forum.digit.in/images/checkbox_off.png"/>
    	<imageIcon id="check_on" path="*forum.digit.in/images/checkbox_on.png"/>
      <property key="CheckBox.icon" value="check_off"/>
      <state value="SELECTED">   
         <property key="CheckBox.icon" value="check_on"/>
	  </state>
  </style>
  <bind style="checkbox" type="region" key="Checkbox"/>	 
  
</synth>

*s4.postimg.org/5bsencj4p/Synth_Demo.png

*s3.postimg.org/gv656jbcf/Synth_Files.png
 
Last edited:
Top Bottom