commit e77f61825e9e9382a45ae6ebb54c2e2512e589f1 Author: knut Date: Sun Apr 1 02:44:17 2007 +0000 Initial checkin diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..367c9a5 --- /dev/null +++ b/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/.project b/.project new file mode 100644 index 0000000..7936a9f --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + album + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..19940ae --- /dev/null +++ b/Makefile @@ -0,0 +1,40 @@ +# +# Java interpreter and compiler +# +JAVA = java +JAVAC = javac + +# +# Various tools +# +RM = rm -f + +# +# Which libraries do we use? +# +CLASSPATH=$(HOME)/src:$(HOME)/src/album/freeJSX1.0.7.4.jar + +CLASSES = Photo.class Album.class Item.class Transform.class + + +# +# The file types we deal with +# +.SUFFIXES: .java .class + + +all: $(CLASSES) + @echo Done + +clean: + $(RM) *.class + $(RM) *~ + +.java.class: + $(JAVAC) -classpath $(CLASSPATH) $< + + +test: $(CLASSES) Test.class + $(JAVA) -cp $(CLASSPATH) album.config.Test /home/knut/src/album/photos + +#eof diff --git a/album.png b/album.png new file mode 100644 index 0000000..dd5f8fe Binary files /dev/null and b/album.png differ diff --git a/design.txt b/design.txt new file mode 100644 index 0000000..86aa79f --- /dev/null +++ b/design.txt @@ -0,0 +1,60 @@ + + +Configuration +============= + +A (hidden) file exists in each album directory, holding information +about each image and each sub-album. For both images and albums the key is the +filename and the order in which each is listed determines the ordering +in the album. Common to both we have the following attributes: + + - Caption, a text fragment explaining what's in the picture/album. + +Special to images additional attributes stored are: + + - Rotation (floating point number) + + - Mask, represented by the four border sizes; top, left, bottom, + right. All floating point. + + - Thumbnail mask. Same as above, but applies to thumbnail images. + As these are very small there may be a need to focus on a detail + of the original image to have a visual cue about the subject of + the photo. + +For albums the additional attributes are: + + - Cover picture, the picture (or album) representing the album as a + whole for the purpose of a thumbnail. + + +Web Interface +============= + +The theory of operation is that the directory holding all the +subdirectories with albums maps to a webapp. The URLs accepted by the +webapp reflect the directory structure of the photo directories, but +adds some twists to make it possible to represent thumbnails, medium +sized images and large images in addition to the album pages +themselves. URLs for the albums are: + + /album/index-num.html + +There is a configuretion switch indicating how many thumbnails to +show per album page. This makes "num" in index-num.html represent the +associated subset of images. There are also one picture per page +pages. These have the pattern: + + /album/filename.html + +The individual pictures are accessed with URLs like the following: + + /album/.../size/filename.ext + +In this URL size has to be one of the following: "thumb", "medium", +"large". The (global?) configuration determines the actual size +constraints for each of the sizes. Only certain file extentions are +supported. Notably "jpeg" and "png". "html" can not be allowed as it +would destroy the page per picture setup. + + diff --git a/photos/forkalsrud-ca-1951.jpg b/photos/forkalsrud-ca-1951.jpg new file mode 100755 index 0000000..e3684a0 Binary files /dev/null and b/photos/forkalsrud-ca-1951.jpg differ diff --git a/photos/fra_jakt.jpg b/photos/fra_jakt.jpg new file mode 100755 index 0000000..ac97e43 Binary files /dev/null and b/photos/fra_jakt.jpg differ diff --git a/photos/l1000729.jpg b/photos/l1000729.jpg new file mode 100644 index 0000000..fe08ddf Binary files /dev/null and b/photos/l1000729.jpg differ diff --git a/photos/l1000802.jpg b/photos/l1000802.jpg new file mode 100644 index 0000000..0c9bcaa Binary files /dev/null and b/photos/l1000802.jpg differ diff --git a/photos/portraits/anton-rudi.jpg b/photos/portraits/anton-rudi.jpg new file mode 100755 index 0000000..eef4731 Binary files /dev/null and b/photos/portraits/anton-rudi.jpg differ diff --git a/photos/portraits/otto-kaurstad.jpg b/photos/portraits/otto-kaurstad.jpg new file mode 100755 index 0000000..9f99b94 Binary files /dev/null and b/photos/portraits/otto-kaurstad.jpg differ diff --git a/photos/portraits/valdemar-dahl.jpg b/photos/portraits/valdemar-dahl.jpg new file mode 100755 index 0000000..863077a Binary files /dev/null and b/photos/portraits/valdemar-dahl.jpg differ diff --git a/src/album/config/Album$1.class b/src/album/config/Album$1.class new file mode 100644 index 0000000..474ba24 Binary files /dev/null and b/src/album/config/Album$1.class differ diff --git a/src/album/config/Album$2.class b/src/album/config/Album$2.class new file mode 100644 index 0000000..934ce60 Binary files /dev/null and b/src/album/config/Album$2.class differ diff --git a/src/album/config/Album.class b/src/album/config/Album.class new file mode 100644 index 0000000..1bd3e55 Binary files /dev/null and b/src/album/config/Album.class differ diff --git a/src/album/config/Album.java b/src/album/config/Album.java new file mode 100644 index 0000000..b4b9c0b --- /dev/null +++ b/src/album/config/Album.java @@ -0,0 +1,49 @@ +package album.config; + +import java.io.*; + +public class Album + extends Item +{ + String frontPic; + + Entry[] contents; + + public Album(String name) { + super(name); + frontPic = null; + contents = null; + } + + + public void scan(File dir) + { + File[] dirs = dir.listFiles(new FileFilter() { + public boolean accept(File f) { + return f.isDirectory(); + } + }); + + File[] files = dir.listFiles(new FilenameFilter() { + public boolean accept(File dir, String name) { + return name.endsWith(".JPEG") + || name.endsWith(".jpeg") + || name.endsWith(".JPG") + || name.endsWith(".jpg"); + } + }); + + contents = new Entry[dirs.length + files.length]; + int idx = 0; + + for (File d : dirs) { + contents[idx++] = new Reference(d.getName()); + } + + for (File f : files) { + contents[idx++] = new Photo(f.getName()); + } + } +} + +// eof diff --git a/src/album/config/Entry.class b/src/album/config/Entry.class new file mode 100644 index 0000000..3834398 Binary files /dev/null and b/src/album/config/Entry.class differ diff --git a/src/album/config/Entry.java b/src/album/config/Entry.java new file mode 100644 index 0000000..2c5de58 --- /dev/null +++ b/src/album/config/Entry.java @@ -0,0 +1,11 @@ +package album.config; + +public abstract class Entry { + String name; + + public Entry(String name) { + this.name = name; + } +} + +// eof diff --git a/src/album/config/Item.class b/src/album/config/Item.class new file mode 100644 index 0000000..e1177a3 Binary files /dev/null and b/src/album/config/Item.class differ diff --git a/src/album/config/Item.java b/src/album/config/Item.java new file mode 100644 index 0000000..7027f6f --- /dev/null +++ b/src/album/config/Item.java @@ -0,0 +1,19 @@ +package album.config; + +import java.util.*; +import java.io.*; + +public class Item + extends Entry +{ + String caption; + boolean hidden; + + public Item(String name) { + super(name); + caption = null; + hidden = false; + } +} + +// eof diff --git a/src/album/config/Photo.class b/src/album/config/Photo.class new file mode 100644 index 0000000..f4a7427 Binary files /dev/null and b/src/album/config/Photo.class differ diff --git a/src/album/config/Photo.java b/src/album/config/Photo.java new file mode 100644 index 0000000..a86ffe7 --- /dev/null +++ b/src/album/config/Photo.java @@ -0,0 +1,21 @@ +package album.config; + +import java.util.*; +import java.io.*; +import javax.imageio.*; +import java.awt.image.*; +import java.awt.geom.*; + +public class Photo + extends Item +{ + Transform xform; + + public Photo(String name) { + super(name); + xform = null; + } + +} + +// eof diff --git a/src/album/config/Reference.class b/src/album/config/Reference.class new file mode 100644 index 0000000..0ab0338 Binary files /dev/null and b/src/album/config/Reference.class differ diff --git a/src/album/config/Reference.java b/src/album/config/Reference.java new file mode 100644 index 0000000..ffa6be8 --- /dev/null +++ b/src/album/config/Reference.java @@ -0,0 +1,11 @@ +package album.config; + +public class Reference + extends Entry +{ + public Reference(String name) { + super(name); + } +} + +// eof diff --git a/src/album/config/Test.class b/src/album/config/Test.class new file mode 100644 index 0000000..b2e229f Binary files /dev/null and b/src/album/config/Test.class differ diff --git a/src/album/config/Test.java b/src/album/config/Test.java new file mode 100644 index 0000000..1f310f8 --- /dev/null +++ b/src/album/config/Test.java @@ -0,0 +1,20 @@ +package album.config; + +import java.io.*; + +public class Test +{ + + public static void main(String args[]) + throws Exception + { + File dir = new File(args[0]); + + Album a = new Album(dir.getName()); + + a.scan(dir); + } + +} + +// eof diff --git a/src/album/config/Transform.class b/src/album/config/Transform.class new file mode 100644 index 0000000..42f3f82 Binary files /dev/null and b/src/album/config/Transform.class differ diff --git a/src/album/config/Transform.java b/src/album/config/Transform.java new file mode 100644 index 0000000..75eef03 --- /dev/null +++ b/src/album/config/Transform.java @@ -0,0 +1,12 @@ +package album.config; + +import java.awt.*; +import java.awt.geom.*; + +public class Transform { + Dimension size; + float rotation; + Rectangle region; +} + +// eof diff --git a/src/album/editor/Editor$1.class b/src/album/editor/Editor$1.class new file mode 100644 index 0000000..888fbfb Binary files /dev/null and b/src/album/editor/Editor$1.class differ diff --git a/src/album/editor/Editor$2.class b/src/album/editor/Editor$2.class new file mode 100644 index 0000000..347ec07 Binary files /dev/null and b/src/album/editor/Editor$2.class differ diff --git a/src/album/editor/Editor$CropFrame$CropSpinnerModel.class b/src/album/editor/Editor$CropFrame$CropSpinnerModel.class new file mode 100644 index 0000000..dc10db2 Binary files /dev/null and b/src/album/editor/Editor$CropFrame$CropSpinnerModel.class differ diff --git a/src/album/editor/Editor$CropFrame.class b/src/album/editor/Editor$CropFrame.class new file mode 100644 index 0000000..40c7c6d Binary files /dev/null and b/src/album/editor/Editor$CropFrame.class differ diff --git a/src/album/editor/Editor$Renderer$1.class b/src/album/editor/Editor$Renderer$1.class new file mode 100644 index 0000000..9dddaf2 Binary files /dev/null and b/src/album/editor/Editor$Renderer$1.class differ diff --git a/src/album/editor/Editor$Renderer.class b/src/album/editor/Editor$Renderer.class new file mode 100644 index 0000000..a6f7618 Binary files /dev/null and b/src/album/editor/Editor$Renderer.class differ diff --git a/src/album/editor/Editor.class b/src/album/editor/Editor.class new file mode 100644 index 0000000..a477b77 Binary files /dev/null and b/src/album/editor/Editor.class differ diff --git a/src/album/editor/Editor.java b/src/album/editor/Editor.java new file mode 100644 index 0000000..31199d5 --- /dev/null +++ b/src/album/editor/Editor.java @@ -0,0 +1,473 @@ +package album.editor; + +import java.awt.*; +import java.awt.event.*; +import java.awt.geom.*; +import java.awt.image.*; +import java.io.*; +import java.util.*; +import javax.imageio.*; +import javax.imageio.stream.*; +import javax.swing.*; +import javax.swing.event.*; +import javax.swing.border.*; + +/** + * Editor + * + * @author knut@forkalsrud.org + */ +public class Editor + extends JComponent +{ + BufferedImage img; + double rotation; + double scale; + Rectangle region; + Dimension displaySize; + AffineTransform t; + + CropFrame mask; + + Rectangle currentRect = null; + + Rectangle rectToDraw = null; + + Rectangle previousRectDrawn = new Rectangle(); + + public Editor(BufferedImage img) { + this.img = img; + + rotation = 0; // 135.0; + displaySize = new Dimension(480, 300); + int w = img.getWidth(); + int h = img.getHeight(); + + scale = Math.min(displaySize.width, displaySize.height) + / Math.sqrt(w * w + h * h); + + t = new AffineTransform(); + t.translate(displaySize.width/2.0, displaySize.height/2.0); + t.scale(scale, scale); + t.translate(-w/2.0, -h/2.0); + Point topLeft = new Point(0, 0); + Point topLeft1 = new Point(); + Point bottomRight = new Point(w, h); + Point bottomRight2 = new Point(); + t.transform(topLeft, topLeft1); + t.transform(bottomRight, bottomRight2); + Dimension d = new Dimension((int)Math.round(bottomRight2.getX() - topLeft1.getX() + 1), + (int)Math.round(bottomRight2.getY() - topLeft1.getY() + 1)); + Rectangle imgRec = new Rectangle(topLeft1, d); + + log("rect: " + imgRec); + + mask = new CropFrame(); + add(mask); + mask.setBounds(imgRec); + mask.setVisible(true); + + setPreferredSize(displaySize); + MyListener ml = new MyListener(); + addMouseListener(ml); + addMouseMotionListener(ml); + + + mask.addMouseListener(mask); + mask.addMouseMotionListener(mask); + + } + + + + AffineTransform render() { + int w = img.getWidth(); + int h = img.getHeight(); + + scale = Math.min(displaySize.width, displaySize.height) + / Math.sqrt(w * w + h * h); + + t = new AffineTransform(); + + t.translate(displaySize.width/2.0, displaySize.height/2.0); + t.scale(scale, scale); + t.rotate(rotation * Math.PI / 180); + t.translate(-w/2.0, -h/2.0); + + return t; + } + + + VolatileImage dst; + + protected void paintComponent(Graphics g1) { + super.paintComponent(g1); + + if (getWidth() != displaySize.width + || getHeight() != displaySize.height + || t == null || dst == null) + { + displaySize.width = getWidth(); + displaySize.height = getHeight(); + AffineTransform dxf = render(); + + + dst = createVolatileImage(getWidth(), getHeight()); + Graphics2D g = dst.createGraphics(); + g.drawRenderedImage(img, dxf); + } + g1.drawImage(dst, 0, 0, dst.getWidth(), dst.getHeight(), this); + } + + + + void drawWireframe(Graphics2D g, int w, int h) { + double delta = Math.round(Math.sqrt(w * w + h * h) / 20); + // + // 0------------4------------1 + // | /|\ | + // | 6 | 7 | + // | | | + // | 5 | + // | 8 | + // | | | + // | 10-+-11 | + // | | | + // | 9 | + // | | + // | | + // | | + // | | + // 3-------------------------2 + double[] wireFramePoints = new double[] { + 0, 0, + w, 0, + w, h, + 0, h, + w/2, 0, + w/2, 3 * delta, + w/2 - delta, delta, + w/2 + delta, delta, + w/2, h/2 - delta, + w/2, h/2 + delta, + w/2 - delta, h/2, + w/2 + delta, h/2 + }; + + float[] screen = new float[wireFramePoints.length]; + int[] x = new int[wireFramePoints.length / 2]; + int[] y = new int[wireFramePoints.length / 2]; + + t.transform(wireFramePoints, 0, screen, 0, x.length); + + for (int i = 0; i < x.length; ++i) { + x[i] = Math.round(screen[2*i]); + y[i] = Math.round(screen[2*i + 1]); + } + g.setColor(Color.BLUE); + + g.drawPolygon(x, y, 4); + g.drawLine(x[4], y[4], x[5], y[5]); + g.drawLine(x[4], y[4], x[6], y[6]); + g.drawLine(x[4], y[4], x[7], y[7]); + g.drawLine(x[8], y[8], x[9], y[9]); + g.drawLine(x[10], y[10], x[11], y[11]); + } + + + + private class MyListener extends MouseInputAdapter { + + public void mousePressed(MouseEvent e) { + int x = e.getX(); + int y = e.getY(); + mask.setLocation(x, y); + mask.setSize(1, 1); + mask.repaint(); + } + + public void mouseDragged(MouseEvent e) { + mask.updateFrame(e.getX(), e.getY()); + } + + public void mouseReleased(MouseEvent e) { + mask.updateFrame(e.getX(), e.getY()); + } + } + + + + + + + + + + + + + JComponent createControlPanel() { + BevelBorder border = new BevelBorder(BevelBorder.LOWERED); + JPanel hp = new JPanel(); + hp.setLayout(new BoxLayout(hp, BoxLayout.Y_AXIS)); + hp.setBorder( + new TitledBorder(border, "Controls", TitledBorder.LEFT, + TitledBorder.ABOVE_TOP)); + hp.add(new JLabel("Rotation")); + final JSlider rotationSlider = new JSlider(-180, 180, 0); + rotationSlider.setPaintTicks(true); + rotationSlider.setMajorTickSpacing(15); + rotationSlider.addChangeListener(new ChangeListener() { + int previousValue = 0; + public void stateChanged(ChangeEvent e) { + int value = rotationSlider.getValue(); + if (value != previousValue) { + previousValue = value; + rotation = value; + t = null; +// render(); + repaint(); + } + } + }); + hp.add(rotationSlider); + + + final JCheckBox renderQualityControl = new JCheckBox("Render Quality", false); + renderQualityControl.addChangeListener(new ChangeListener() { + boolean previousValue = false; + public void stateChanged(ChangeEvent e) { + boolean value = renderQualityControl.isSelected(); + if (value != previousValue) { + previousValue = value; + render(); + repaint(); + } + } + }); + hp.add(renderQualityControl); + + + hp.add(new JPanel()); + + return hp; + } + + + + + public static void main(String args[]) + throws Exception + { + Iterator readers = ImageIO.getImageReadersByFormatName("jpg"); + ImageReader reader = (ImageReader)readers.next(); + ImageInputStream iis = ImageIO.createImageInputStream( + new File(args[0])); + reader.setInput(iis, true); + ImageReadParam param = reader.getDefaultReadParam(); +// param.setSourceSubsampling(8, 8, 0, 0); + BufferedImage raw = reader.read(0, param); + + Editor x = new Editor(raw); + + JFrame frame = new JFrame("Editor"); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + + Container c = frame.getContentPane(); + c.setLayout(new BorderLayout()); + c.add(x.createControlPanel(), BorderLayout.EAST); + c.add(x, BorderLayout.CENTER); + + frame.pack(); + frame.setVisible(true); + } + + + + + static class CropFrame extends JComponent implements MouseListener, MouseMotionListener { + + /** + */ + private static final long serialVersionUID = 1L; + + private int resizeBand = 6; + + Stroke stroke = new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 1f, new float[] { 5f, 5f }, 0); + + public CropFrame() + { + setOpaque(false); + } + + + + // + // 1------(5)-------2 + // | | + // | | + // (8) (6) + // | | + // | | + // 4------(7)-------3 + // + protected void paintComponent(Graphics g) { + super.paintComponent(g); //paints the background and image + + // Draw a rectangle on top of the image. +// g.setColor(Color.WHITE); + g.setXORMode(Color.white); //Color of line varies + ((Graphics2D)g).setStroke(stroke); + //depending on image colors + g.drawRect(0, 0, getWidth() - 1, getHeight() - 1); + } + + + + public void mouseClicked(MouseEvent e) { + // TODO Auto-generated method stub + + } + + + + public void mouseEntered(MouseEvent e) { + // TODO Auto-generated method stub + + } + + + + public void mouseExited(MouseEvent e) { + // TODO Auto-generated method stub + + } + + + + public void mousePressed(MouseEvent e) { + int r = findRegion(e.getX(), e.getY()); + if (r < 8) { + edge = r; + } + } + + public void mouseReleased(MouseEvent e) { + updateFrame(e.getX() + getX(), e.getY() + getY()); + } + + + + public void mouseDragged(MouseEvent e) { + updateFrame(e.getX() + getX(), e.getY() + getY()); + } + + + + boolean is(int who, int alt0, int alt1, int alt2) { + return who == alt0 || who == alt1 || who == alt2; + } + int[] oppositeX = new int[] { NE, N, NW, W, SW, S, SE, E }; + int[] oppositeY = new int[] { SW, S, SE, E, NE, N, NW, W }; + Cursor[] cursors = new Cursor[] { + Cursor.getPredefinedCursor(Cursor.NW_RESIZE_CURSOR), + Cursor.getPredefinedCursor(Cursor.N_RESIZE_CURSOR), + Cursor.getPredefinedCursor(Cursor.NE_RESIZE_CURSOR), + Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR), + Cursor.getPredefinedCursor(Cursor.SE_RESIZE_CURSOR), + Cursor.getPredefinedCursor(Cursor.S_RESIZE_CURSOR), + Cursor.getPredefinedCursor(Cursor.SW_RESIZE_CURSOR), + Cursor.getPredefinedCursor(Cursor.W_RESIZE_CURSOR), + Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR), + }; + String[] edgeName = new String[] { "NW", "N", "NE", "E", "SE", "S", "SW", "W" }; + static final int NW = 0; + static final int N = 1; + static final int NE = 2; + static final int E = 3; + static final int SE = 4; + static final int S = 5; + static final int SW = 6; + static final int W = 7; + int edge = SE; + Rectangle frame = new Rectangle(); + + void updateFrame(int x, int y) { + getBounds(frame); + if (is(edge, NW, W, SW)) { + frame.width += frame.x - x; + frame.x = x; + } + if (is(edge, NE, E, SE)) { + frame.width = x - frame.x; + } + if (is(edge, NW, N, NE)) { + frame.height += frame.y - y; + frame.y = y; + } + if (is(edge, SW, S, SE)) { + frame.height = y - frame.y; + } + if (frame.width < 0) { + frame.x += frame.width; + frame.width = -frame.width; + edge = oppositeX[edge]; + } + if (frame.height < 0) { + frame.y += frame.height; + frame.height = -frame.height; + edge = oppositeY[edge]; + } + setBounds(frame); + repaint(); + } + + + + int findRegion(int ex, int ey) { + int w = getWidth(); + int h = getHeight(); + int c = 8; + + if (ex > (w - resizeBand)) { + if (ey > (h - resizeBand)) { + c = SE; + } else if (ey < resizeBand) { + c = NE; + } else { + c = E; + } + } else if (ex < resizeBand) { + if (ey > (h - resizeBand)) { + c = SW; + } else if (ey < resizeBand) { + c = NW; + } else { + c = W; + } + } else { + if (ey > (h - resizeBand)) { + c = S; + } else if (ey < resizeBand) { + c = N; + } else { + c = 8; + } + } + return c; + } + + public void mouseMoved(MouseEvent e) { + setCursor(cursors[findRegion(e.getX(), e.getY())]); + } + + } + + static void log(String x) { + System.out.println(x); + } + + +} + +// eof diff --git a/src/album/editor/Makefile b/src/album/editor/Makefile new file mode 100644 index 0000000..ce54bd2 --- /dev/null +++ b/src/album/editor/Makefile @@ -0,0 +1,44 @@ +# +# Java interpreter and compiler +# +JAVA = java +JAVAC = javac + +# +# Various tools +# +RM = rm -f + +# +# Which libraries do we use? +# +CLASSPATH=$(HOME)/src/album + +CLASSES = Editor.class + + +# +# The file types we deal with +# +.SUFFIXES: .java .class + + +all: $(CLASSES) + @echo Done + +clean: + $(RM) *.class + $(RM) *~ + +.java.class: + $(JAVAC) -classpath $(CLASSPATH) $< + + +editor: Editor.class + $(JAVA) -cp $(CLASSPATH) editor.Editor /home/knut/src/album/photos/l1000802.jpg + +reader: ReaderHelper.class + $(JAVA) -cp $(CLASSPATH) editor.ReaderHelper /home/knut/src/album/photos + + +#eof diff --git a/src/album/editor/ReaderHelper$1.class b/src/album/editor/ReaderHelper$1.class new file mode 100644 index 0000000..a7f0013 Binary files /dev/null and b/src/album/editor/ReaderHelper$1.class differ diff --git a/src/album/editor/ReaderHelper.class b/src/album/editor/ReaderHelper.class new file mode 100644 index 0000000..9c06ac2 Binary files /dev/null and b/src/album/editor/ReaderHelper.class differ diff --git a/src/album/editor/SelectionDemo$1.class b/src/album/editor/SelectionDemo$1.class new file mode 100644 index 0000000..ce4ab83 Binary files /dev/null and b/src/album/editor/SelectionDemo$1.class differ diff --git a/src/album/editor/SelectionDemo$SelectionArea$MyListener.class b/src/album/editor/SelectionDemo$SelectionArea$MyListener.class new file mode 100644 index 0000000..1212394 Binary files /dev/null and b/src/album/editor/SelectionDemo$SelectionArea$MyListener.class differ diff --git a/src/album/editor/SelectionDemo$SelectionArea.class b/src/album/editor/SelectionDemo$SelectionArea.class new file mode 100644 index 0000000..c88caa9 Binary files /dev/null and b/src/album/editor/SelectionDemo$SelectionArea.class differ diff --git a/src/album/editor/SelectionDemo.class b/src/album/editor/SelectionDemo.class new file mode 100644 index 0000000..5100961 Binary files /dev/null and b/src/album/editor/SelectionDemo.class differ diff --git a/src/album/editor/SelectionDemo.java b/src/album/editor/SelectionDemo.java new file mode 100644 index 0000000..e1311a2 --- /dev/null +++ b/src/album/editor/SelectionDemo.java @@ -0,0 +1,209 @@ +/* From http://java.sun.com/docs/books/tutorial/index.html */ +/* + * SelectionDemo.java is a 1.4 application that requires one other file: + * images/starfield.gif + */ + +package album.editor; + +import javax.swing.BoxLayout; +import javax.swing.ImageIcon; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.event.MouseInputAdapter; +import java.awt.*; +import java.awt.event.MouseEvent; + +/* + * This displays an image. When the user drags within the image, this program + * displays a rectangle and a string indicating the bounds of the rectangle. + */ +public class SelectionDemo { + JLabel label; + + static String starFile = "/home/knut/src/album/photos/forkalsrud-ca-1951.jpg"; + + private void buildUI(Container container, ImageIcon image) { + container.setLayout(new BoxLayout(container, BoxLayout.PAGE_AXIS)); + + SelectionArea area = new SelectionArea(image, this); + container.add(area); + + label = new JLabel("Drag within the image."); + label.setLabelFor(area); + container.add(label); + + //Align the left edges of the components. + area.setAlignmentX(Component.LEFT_ALIGNMENT); + label.setAlignmentX(Component.LEFT_ALIGNMENT); //redundant + } + + public void updateLabel(Rectangle rect) { + int width = rect.width; + int height = rect.height; + + //Make the coordinates look OK if a dimension is 0. + if (width == 0) { + width = 1; + } + if (height == 0) { + height = 1; + } + + label.setText("Rectangle goes from (" + rect.x + ", " + rect.y + + ") to (" + (rect.x + width - 1) + ", " + + (rect.y + height - 1) + ")."); + } + + /** Returns an ImageIcon, or null if the path was invalid. */ + protected static ImageIcon createImageIcon(String path) { + return new ImageIcon(path, "description"); + } + + /** + * Create the GUI and show it. For thread safety, this method should be + * invoked from the event-dispatching thread. + */ + private static void createAndShowGUI() { + //Make sure we have nice window decorations. + JFrame.setDefaultLookAndFeelDecorated(true); + + //Create and set up the window. + JFrame frame = new JFrame("SelectionDemo"); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + + //Set up the content pane. + SelectionDemo controller = new SelectionDemo(); + controller.buildUI(frame.getContentPane(), createImageIcon(starFile)); + + //Display the window. + frame.pack(); + frame.setVisible(true); + } + + public static void main(String[] args) { + //Schedule a job for the event-dispatching thread: + //creating and showing this application's GUI. + javax.swing.SwingUtilities.invokeLater(new Runnable() { + public void run() { + createAndShowGUI(); + } + }); + } + + private class SelectionArea extends JLabel { + Rectangle currentRect = null; + + Rectangle rectToDraw = null; + + Rectangle previousRectDrawn = new Rectangle(); + + SelectionDemo controller; + + public SelectionArea(ImageIcon image, SelectionDemo controller) { + super(image); //This component displays an image. + this.controller = controller; + setOpaque(true); + setMinimumSize(new Dimension(10, 10)); //don't hog space + + MyListener myListener = new MyListener(); + addMouseListener(myListener); + addMouseMotionListener(myListener); + } + + private class MyListener extends MouseInputAdapter { + public void mousePressed(MouseEvent e) { + int x = e.getX(); + int y = e.getY(); + currentRect = new Rectangle(x, y, 0, 0); + updateDrawableRect(getWidth(), getHeight()); + repaint(); + } + + public void mouseDragged(MouseEvent e) { + updateSize(e); + } + + public void mouseReleased(MouseEvent e) { + updateSize(e); + } + + /* + * Update the size of the current rectangle and call repaint. + * Because currentRect always has the same origin, translate it if + * the width or height is negative. + * + * For efficiency (though that isn't an issue for this program), + * specify the painting region using arguments to the repaint() + * call. + * + */ + void updateSize(MouseEvent e) { + int x = e.getX(); + int y = e.getY(); + currentRect.setSize(x - currentRect.x, y - currentRect.y); + updateDrawableRect(getWidth(), getHeight()); + Rectangle totalRepaint = rectToDraw.union(previousRectDrawn); + repaint(totalRepaint.x, totalRepaint.y, totalRepaint.width, + totalRepaint.height); + } + } + + protected void paintComponent(Graphics g) { + super.paintComponent(g); //paints the background and image + + //If currentRect exists, paint a box on top. + if (currentRect != null) { + //Draw a rectangle on top of the image. + g.setXORMode(Color.white); //Color of line varies + //depending on image colors + g.drawRect(rectToDraw.x, rectToDraw.y, rectToDraw.width - 1, + rectToDraw.height - 1); + + controller.updateLabel(rectToDraw); + } + } + + private void updateDrawableRect(int compWidth, int compHeight) { + int x = currentRect.x; + int y = currentRect.y; + int width = currentRect.width; + int height = currentRect.height; + + //Make the width and height positive, if necessary. + if (width < 0) { + width = 0 - width; + x = x - width + 1; + if (x < 0) { + width += x; + x = 0; + } + } + if (height < 0) { + height = 0 - height; + y = y - height + 1; + if (y < 0) { + height += y; + y = 0; + } + } + + //The rectangle shouldn't extend past the drawing area. + if ((x + width) > compWidth) { + width = compWidth - x; + } + if ((y + height) > compHeight) { + height = compHeight - y; + } + + //Update rectToDraw after saving old value. + if (rectToDraw != null) { + previousRectDrawn.setBounds(rectToDraw.x, rectToDraw.y, + rectToDraw.width, rectToDraw.height); + rectToDraw.setBounds(x, y, width, height); + } else { + rectToDraw = new Rectangle(x, y, width, height); + } + } + } +} diff --git a/src/album/web/AlbumServlet.java b/src/album/web/AlbumServlet.java new file mode 100644 index 0000000..1aabc85 --- /dev/null +++ b/src/album/web/AlbumServlet.java @@ -0,0 +1,122 @@ +package album.web; + +import java.io.*; +import java.util.*; + +import javax.servlet.*; +import javax.servlet.http.*; + +public class AlbumServlet + extends HttpServlet +{ + File baseDir = new File("/home/knut"); + + public void init() + throws ServletException + { + System.out.println("in init of Album"); + } + + public void doGet(HttpServletRequest req, HttpServletResponse res) + throws ServletException, IOException + { + /* System.out.println("pathInfo: " + req.getPathInfo()); + System.out.println("pathXlat: " + req.getPathTranslated()); + System.out.println("queryString: " + req.getQueryString()); + System.out.println("requestUri: " + req.getRequestURI()); + System.out.println("servletPath: " + req.getServletPath()); + */ + res.setContentType("text/html"); + // Determine which directory to list + String directory = req.getServletPath(); + directory = directory.substring( + "/".length(), + directory.length() - ".album".length()); + + System.out.println("directory: " + directory); + + File dir = new File(baseDir, directory); + if (dir == null) { + res.setStatus(HttpServletResponse.SC_NOT_FOUND); + return; + } + + if (!dir.isDirectory()) { + res.setStatus(HttpServletResponse.SC_NOT_FOUND); + return; + } + + if (!dir.canRead()) { + res.setStatus(HttpServletResponse.SC_FORBIDDEN); + return; + } + + // Determine sort order + File[] files = dir.listFiles(new FileFilter() { + public boolean accept(File candidate) { + return candidate.isDirectory() + || candidate.getName().endsWith(".gif") + || candidate.getName().endsWith(".GIF") + || candidate.getName().endsWith(".png") + || candidate.getName().endsWith(".PNG") + || candidate.getName().endsWith(".jpg") + || candidate.getName().endsWith(".JPG") + || candidate.getName().endsWith(".jpeg") + || candidate.getName().endsWith(".JPEG"); + } + }); + + Arrays.sort(files, new Comparator() { + public int compare(File f1, File f2) { + + // Directories first + if (f1.isDirectory() && !f2.isDirectory()) + return -1; + if (f2.isDirectory() && !f1.isDirectory()) + return 1; + return f1.getName().compareTo(f2.getName()); + } + }); + + // Produce tags for a thumbnail of each image or other + // album + PrintWriter out = res.getWriter(); + out.println(""); + out.println(""); + out.println("" + directory + ""); + out.println(""); + out.println(""); + + String baseUrl = req.getContextPath() + + "/" + directory + "/"; + + for (int i = 0; i < files.length; ++i) { + File file = files[i]; + String anchorUrl = ""; + String iconUrl = ""; + if (file.isDirectory()) { + anchorUrl = baseUrl + file.getName() + ".album"; + iconUrl = req.getContextPath() + "/album.png"; + } else if (file.isFile()) { + anchorUrl = baseUrl + file.getName() + ".photo"; + iconUrl = baseUrl + file.getName() + ".bitmap?size=100"; + } else { + throw new ServletException("Unknown file: " + file); + } + + out.print(""); + out.print(""); + out.println(""); + } + out.println(""); + out.println(""); + } + + + public String getServletInfo() { + return "Display a directory as an album"; + } + +} + +// eof diff --git a/src/album/web/BitmapServlet.java b/src/album/web/BitmapServlet.java new file mode 100644 index 0000000..14f1347 --- /dev/null +++ b/src/album/web/BitmapServlet.java @@ -0,0 +1,223 @@ +package album.web; + +import java.io.*; +import java.util.*; + +import java.awt.*; +import java.awt.image.*; + +import javax.imageio.*; + +import javax.servlet.*; +import javax.servlet.http.*; + +public class BitmapServlet + extends HttpServlet + implements ImageObserver +{ + File baseDir = new File("/home/knut"); + File cacheDir = new File("/var/album"); + + final static int BUF_SIZE = 1024; + protected volatile boolean complete = false; + protected int size; + + + public void init() + throws ServletException + { + System.out.println("in init of Bitmap"); + } + + + File cacheFile(String filename, int size) { + return new File(cacheDir, filename + "-" + size + ".png"); + } + + + public void doGet(HttpServletRequest req, HttpServletResponse res) + throws ServletException, IOException + { + res.setContentType("image/png"); + + // Determine which file to show + String filename = req.getServletPath(); + filename = filename.substring( + "/".length(), + filename.length() - ".bitmap".length()); + + System.out.println("file: " + filename); + + File file = new File(baseDir, filename); + if (file == null) { + res.setStatus(HttpServletResponse.SC_NOT_FOUND); + return; + } + + if (!file.isFile()) { + res.setStatus(HttpServletResponse.SC_NOT_FOUND); + return; + } + + if (!file.canRead()) { + res.setStatus(HttpServletResponse.SC_FORBIDDEN); + return; + } + + int size = Integer.parseInt(req.getParameter("size")); + + File cacheFile = cacheFile(filename, size); + + if (!cacheFile.exists()) { + writeJpegImage(file, size, cacheFile); + } + + try { + OutputStream out = res.getOutputStream(); + FileInputStream infile = new FileInputStream(cacheFile); + byte[] buf = new byte[BUF_SIZE]; + int chunksize; + do { + chunksize = infile.read(buf); + if (chunksize > 0) + out.write(buf, 0, chunksize); + } while (!(chunksize < 0)); + infile.close(); + } catch (FileNotFoundException e1) { + System.err.println("File creation error! " + e1.getMessage()); + } catch (IOException e2) { + System.err.println("IO error! " + e2.getMessage()); + } + } + + static int DONE = (ABORT | ALLBITS | ERROR | FRAMEBITS); + + int previousInfoFlags = 0; + // HEIGHT PROPERTIES SOMEBITS WIDTH + public boolean imageUpdate(Image img, int infoflags, + int x, int y, int width, int height) + { + if (infoflags != previousInfoFlags) { +/* StringBuffer b = new StringBuffer("Flags:"); + if ((infoflags & ABORT) != 0) + b.append(" ABORT"); + if ((infoflags & ALLBITS) != 0) + b.append(" ALLBITS"); + if ((infoflags & ERROR) != 0) + b.append(" ERROR"); + if ((infoflags & FRAMEBITS) != 0) + b.append(" FRAMEBITS"); + if ((infoflags & HEIGHT) != 0) + b.append(" HEIGHT"); + if ((infoflags & WIDTH) != 0) + b.append(" WIDTH"); + if ((infoflags & PROPERTIES) != 0) + b.append(" PROPERTIES"); + if ((infoflags & SOMEBITS) != 0) + b.append(" SOMEBITS"); + System.err.println(b.toString()); +*/ + previousInfoFlags = infoflags; + } + + if ((infoflags & DONE) != 0) { + synchronized (this) { + this.complete = true; + // new Throwable().printStackTrace(System.err); + notify(); +// System.err.println("Wakeup! " + this.complete + " " + this); + } + return false; + } else { + return true; + } + } + + protected Dimension scaleMaxExtent(int extent, Dimension in) + { + Dimension original = new Dimension(in); + if (extent > 0) { + if (original.width > original.height && original.width > extent) { + original.height *= extent; + original.height /= original.width; + original.width = extent; + } else if (original.height > extent) { + original.width *= extent; + original.width /= original.height; + original.height = extent; + } + } + return original; + } + + + protected void writeJpegImage(File source, int size, File destination) + throws IOException + { + BufferedImage img = ImageIO.read(source); + Dimension dim = scaleMaxExtent(size, new Dimension( + img.getWidth(), img.getHeight())); + + Toolkit tk = Toolkit.getDefaultToolkit(); + + synchronized (this) { + complete = false; + boolean alreadyDone = tk.prepareImage(img, -1, -1, this); + complete = complete || alreadyDone; + + // Suspend until the image is rendered completely + for (int i = 0; !this.complete && i < 10; ++i) + try { +// System.err.println("Waiting 1: " + this.complete + " " + this); + wait(2000); + } catch (InterruptedException e) { + // ignore + } + } + + BufferedImage buff = new BufferedImage( + dim.width, dim.height, BufferedImage.TYPE_INT_RGB); + Graphics g = buff.createGraphics(); + + synchronized (this) { + complete = false; + + boolean alreadyDone = g.drawImage(img, 0, 0, dim.width, dim.height, this); + + complete = complete || alreadyDone; + + // Suspend until the image is rendered completely + for (int i = 0; !complete && i < 10; ++i) + try { +// System.err.println("Waiting 2: " + this.complete); + wait(2000); + } catch (InterruptedException e) { + // ignore + } + } + + if (size > 120 || size == 0) { + g.setColor(Color.cyan); + int lineHeight = g.getFontMetrics().getHeight(); + int year = Calendar.getInstance().get(Calendar.YEAR); + g.drawString("� " + year + " forkalsrud.org", 5, 5 + lineHeight); + } + + try { + destination.getParentFile().mkdirs(); + FileOutputStream out = new FileOutputStream(destination); + ImageIO.write(buff, "png", out); + out.close(); +/* System.err.println("OK: " + ok + + " Writers : " + + Arrays.asList(ImageIO.getWriterFormatNames())); +*/ + } catch (FileNotFoundException e1) { + System.err.println("File creation error! " + e1.getMessage()); + } catch (IOException e2) { + System.err.println("IO error! " + e2.getMessage()); + } + } +} + +// eof diff --git a/src/album/web/PhotoServlet.java b/src/album/web/PhotoServlet.java new file mode 100644 index 0000000..a1f5f29 --- /dev/null +++ b/src/album/web/PhotoServlet.java @@ -0,0 +1,171 @@ +package album.web; + +import java.io.*; +import java.util.*; + +import java.awt.*; +import java.awt.image.*; + +import javax.imageio.*; + +import javax.servlet.*; +import javax.servlet.http.*; + +public class PhotoServlet + extends HttpServlet + implements ImageObserver +{ + File baseDir = new File("/home/knut"); + File cacheDir = new File("/var/album"); + + final static int BUF_SIZE = 1024; + protected volatile boolean complete = false; + protected int size; + + + public void init() + throws ServletException + { + System.out.println("in init of Bitmap"); + } + + + File cacheFile(String filename, int size) { + return new File(cacheDir, filename + "-" + size + ".jpeg"); + } + + + public void doGet(HttpServletRequest req, HttpServletResponse res) + throws ServletException, IOException + { + res.setContentType("text/plain"); + + // Determine which file to show + String filename = req.getServletPath(); + filename = filename.substring( + "/".length(), + filename.length() - ".photo".length()); + + System.out.println("file: " + filename); + + File file = new File(baseDir, filename); + if (file == null) { + res.setStatus(HttpServletResponse.SC_NOT_FOUND); + return; + } + + if (!file.isFile()) { + res.setStatus(HttpServletResponse.SC_NOT_FOUND); + return; + } + + if (!file.canRead()) { + res.setStatus(HttpServletResponse.SC_FORBIDDEN); + return; + } + + PrintWriter out = res.getWriter(); + + + String[] formats = ImageIO.getReaderFormatNames(); + out.println(Arrays.asList(formats).toString()); + + Iterator ii = ImageIO.getImageReadersByFormatName("jpeg"); + while (ii.hasNext()) { + ImageReader ir = (ImageReader)ii.next(); + + Class[] inputs = ir.getOriginatingProvider().getInputTypes(); + out.println(" inputs: " + Arrays.asList(inputs).toString()); + + ir.setInput(ImageIO.createImageInputStream(file)); + + int cnt = ir.getNumImages(true); + + int thCnt = ir.getNumThumbnails(0); + + out.println("Num images: " + cnt + " thumbnails: " + thCnt); + +// BufferedImage th = ir.readThumbnail(0, 0); + +// out.println(" Thumbnail size: " + th.getWidth() + "x" + th.getWidth()); + ir.dispose(); + } +/* + EXIFInfo info = new EXIFInfo(file); + Map props = info.getEXIFMetaData(); + + Set s = props.entrySet(); + Iterator i = s.iterator(); + + while (i.hasNext()) { + Map.Entry e = (Map.Entry)i.next(); + out.println(e.getKey() + ": " + e.getValue()); + } +*/ + } + + + static int DONE = (ABORT | ALLBITS | ERROR | FRAMEBITS); + + int previousInfoFlags = 0; + // HEIGHT PROPERTIES SOMEBITS WIDTH + public boolean imageUpdate(Image img, int infoflags, + int x, int y, int width, int height) + { + if (infoflags != previousInfoFlags) { +/* StringBuffer b = new StringBuffer("Flags:"); + if ((infoflags & ABORT) != 0) + b.append(" ABORT"); + if ((infoflags & ALLBITS) != 0) + b.append(" ALLBITS"); + if ((infoflags & ERROR) != 0) + b.append(" ERROR"); + if ((infoflags & FRAMEBITS) != 0) + b.append(" FRAMEBITS"); + if ((infoflags & HEIGHT) != 0) + b.append(" HEIGHT"); + if ((infoflags & WIDTH) != 0) + b.append(" WIDTH"); + if ((infoflags & PROPERTIES) != 0) + b.append(" PROPERTIES"); + if ((infoflags & SOMEBITS) != 0) + b.append(" SOMEBITS"); + System.err.println(b.toString()); +*/ + previousInfoFlags = infoflags; + } + + if ((infoflags & DONE) != 0) { + synchronized (this) { + this.complete = true; + // new Throwable().printStackTrace(System.err); + notify(); +// System.err.println("Wakeup! " + this.complete + " " + this); + } + return false; + } else { + return true; + } + } + + protected Dimension scaleMaxExtent(int extent, Dimension in) + { + Dimension original = new Dimension(in); + if (extent > 0) { + if (original.width > original.height && original.width > extent) { + original.height *= extent; + original.height /= original.width; + original.width = extent; + } else if (original.height > extent) { + original.width *= extent; + original.width /= original.height; + original.height = extent; + } + } + return original; + } + + +} + +// eof diff --git a/webapp/WEB-INF/web.xml b/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..86bdf91 --- /dev/null +++ b/webapp/WEB-INF/web.xml @@ -0,0 +1,10 @@ + + + + + + +