1 package com.hammurapi.jcapture;
2 
3 import java.awt.image.BufferedImage;
4 import java.io.ByteArrayInputStream;
5 import java.io.ByteArrayOutputStream;
6 import java.io.IOException;
7 import java.lang.ref.Reference;
8 import java.lang.ref.SoftReference;
9 import java.nio.ByteBuffer;
10 import java.nio.MappedByteBuffer;
11 import java.nio.channels.FileChannel;
12 import java.nio.channels.FileChannel.MapMode;
13 import java.util.zip.Adler32;
14 
15 import javax.imageio.ImageIO;
16 
17 /**
18  * Mapped image is softly kept in memory and also is written to a temporary file.
19  * If image reference is cleared by the garbage collector, the image is loaded from the file on demand.
20  * @author Pavel
21  *
22  */
23 public class MappedImage {
24 
25 	private Reference<BufferedImage> imageRef;
26 	private Reference<byte[]> imageBytesRef;
27 	private MappedByteBuffer buffer;
28 	private int height;
29 	private int width;
30 	private String format;
31 	private long checksum;
32 	private int bytesLength;
33 
MappedImage(final BufferedImage image, String format, FileChannel channel)34 	public MappedImage(final BufferedImage image, String format, FileChannel channel) throws IOException {
35 		if (format==null) {
36 			throw new NullPointerException("Format is null");
37 		}
38 
39 		class HardReference extends SoftReference<BufferedImage> {
40 
41 			HardReference(BufferedImage referent) {
42 				super(referent);
43 			}
44 
45 			@Override
46 			public BufferedImage get() {
47 				return image;
48 			}
49 
50 		}
51 		imageRef = channel==null ? new HardReference(image) : new SoftReference<BufferedImage>(image);
52 		width = image.getWidth();
53 		height = image.getHeight();
54 		this.format = format;
55 		if (channel!=null) {
56 			ByteArrayOutputStream baos = new ByteArrayOutputStream();
57 			ImageIO.write(imageRef.get(), format, baos);
58 			baos.close();
59 			byte[] imageBytes = baos.toByteArray();
60 			Adler32 adler = new Adler32();
61 			adler.update(imageBytes);
62 			checksum = adler.getValue();
63 			bytesLength = imageBytes.length;
64 			imageBytesRef = new SoftReference<byte[]>(imageBytes);
65 			synchronized (channel) {
66 				long position = channel.position();
67 				channel.write(ByteBuffer.wrap(imageBytes));
68 				buffer = channel.map(MapMode.READ_ONLY, position, imageBytes.length);
69 			}
70 		}
71 	}
72 
getImageBytes()73 	public byte[] getImageBytes() throws IOException {
74 		if (imageBytesRef==null) {
75 			ByteArrayOutputStream baos = new ByteArrayOutputStream();
76 			ImageIO.write(imageRef.get(), format, baos);
77 			return baos.toByteArray();
78 		}
79 		byte[] ret = imageBytesRef.get();
80 		if (ret==null) {
81 			buffer.load();
82 			buffer.rewind();
83 			ret = new byte[buffer.remaining()];
84 			buffer.get(ret);
85 			if (bytesLength != ret.length) {
86 				throw new IllegalStateException("Invalid image bytes length, expected "+bytesLength+", got "+ret.length);
87 			}
88 
89 			Adler32 adler = new Adler32();
90 			adler.update(ret);
91 			if (checksum != adler.getValue()) {
92 				throw new IllegalStateException("Invalid image bytes checksum");
93 			}
94 			imageBytesRef = new SoftReference<byte[]>(ret);
95 		}
96 		return ret;
97 	}
98 
99 	/**
100 	 * Reads from reference, if reference was cleared, loads from the mapped buffer.
101 	 * @return
102 	 * @throws IOException
103 	 */
getImage()104 	public BufferedImage getImage() throws IOException {
105 		BufferedImage ret = imageRef.get();
106 		if (ret==null) {
107 			ret = ImageIO.read(new ByteArrayInputStream(getImageBytes()));
108 			imageRef = new SoftReference<BufferedImage>(ret);
109 		}
110 		return ret;
111 	}
112 
getHeight()113 	public int getHeight() {
114 		return height;
115 	}
116 
getWidth()117 	public int getWidth() {
118 		return width;
119 	}
120 
121 }
122