1 package com.hammurapi.jcapture;
2 
3 import java.awt.AlphaComposite;
4 import java.awt.Dimension;
5 import java.awt.Graphics2D;
6 import java.awt.Point;
7 import java.awt.RenderingHints;
8 import java.awt.image.BufferedImage;
9 import java.io.File;
10 import java.io.IOException;
11 import java.nio.channels.FileChannel;
12 import java.util.ArrayList;
13 import java.util.List;
14 import java.util.ListIterator;
15 import java.util.concurrent.Callable;
16 
17 import javax.imageio.ImageIO;
18 
19 public class ScreenShot implements Callable<ScreenShot> {
20 
21 	private final ScreenShot prev;
22 	private final int secNo;
23 	private ScreenShot next;
24 	final private long timeStamp;
25 	private int grabRange;
26 	private boolean transparency;
27 	private MappedImage image;
28 	private Point mousePosition;
29 	private double scale;
30 	private boolean border;
31 	private Dimension size;
32 	private FileChannel imageChannel;
33 	private String imageFormat;
34 
ScreenShot( BufferedImage image, Point mousePosition, ScreenShot prev, long timeStamp, int grabRange, boolean transparency, boolean border, double scale, FileChannel imageChannel, String imageFormat)35 	public ScreenShot(
36 			BufferedImage image,
37 			Point mousePosition,
38 			ScreenShot prev,
39 			long timeStamp,
40 			int grabRange,
41 			boolean transparency,
42 			boolean border,
43 			double scale,
44 			FileChannel imageChannel,
45 			String imageFormat) throws IOException {
46 
47 		this.image = new MappedImage(image, imageFormat, imageChannel);
48 		this.mousePosition = mousePosition;
49 		this.prev = prev;
50 		if (prev==null) {
51 			secNo=0;
52 		} else {
53 			prev.next = this;
54 			secNo = prev.secNo+1;
55 		}
56 		this.timeStamp = timeStamp;
57 		this.grabRange = grabRange;
58 		this.transparency = transparency;
59 		this.scale = scale;
60 		this.border = border;
61 		this.imageChannel = imageChannel;
62 		this.imageFormat = imageFormat;
63 	}
64 
getMousePosition()65 	public Point getMousePosition() {
66 		return mousePosition;
67 	}
68 
69 	/**
70 	 * Calculates actual FPS.
71 	 * @return
72 	 */
getFramesPerSecond()73 	public float getFramesPerSecond() {
74 		long start = timeStamp;
75 		long end = 0;
76 		int length = 0;
77 		for (ScreenShot sibling = next; sibling!=null; sibling=sibling.next) {
78 			++length;
79 			end = sibling.timeStamp;
80 		}
81 		if (length==0) {
82 			return -1; // No way to tell.
83 		}
84 		return (float) (length * 1000.0)/(end - start);
85 	}
86 
87 	private List<Region> regions;
88 
89 	private long totalPixels;
90 	private long differentPixels;
91 
getDiffLevel()92 	public double getDiffLevel() {
93 		return (double) differentPixels/(double) totalPixels;
94 	}
95 
96 	/**
97 	 * If images are different more than diffThreshold, then the
98 	 * entire screenshot shall be taken.
99 	 */
100 	private double diffThreshold = 0.7;
101 
102 	/**
103 	 * Performs processing and returns self.
104 	 * Screenshot is structured as Callable to simplify live processing in a background thread.
105 	 */
106 	@Override
call()107 	public ScreenShot call() throws Exception {
108 		BufferedImage img = image.getImage();
109     	// No petty scaling.
110     	if (scale<0.99 || scale > 1.01) {
111 	    	BufferedImage scaled = new BufferedImage((int) (img.getWidth()*scale), (int) (img.getHeight()*scale), img.getType());
112 	    	Graphics2D g = scaled.createGraphics();
113 	    	g.setComposite(AlphaComposite.Src);
114 	    	g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,RenderingHints.VALUE_INTERPOLATION_BILINEAR);
115 	    	g.setRenderingHint(RenderingHints.KEY_RENDERING,RenderingHints.VALUE_RENDER_QUALITY);
116 	    	g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
117 	    	g.drawImage(img, 0, 0, scaled.getWidth(), scaled.getHeight(), null);
118 	    	g.dispose();
119 	    	img = scaled;
120 
121 	    	if (mousePosition!=null) {
122 	    		mousePosition = new Point((int) (mousePosition.x*scale), (int) (mousePosition.y*scale));
123 	    	}
124     	}
125 
126 		if (border) {
127 			Graphics2D ssg = img.createGraphics();
128 			ssg.setColor(java.awt.Color.GRAY);
129 			ssg.drawRect(0, 0, img.getWidth()-1, img.getHeight()-1);
130 		}
131 
132 		size = new Dimension(image.getWidth(), image.getHeight());
133 
134 		regions = new ArrayList<Region>();
135 		if (prev==null) {
136 			regions.add(new Region(image));
137 		} else {
138 			BufferedImage pimg = prev.image.getImage();
139 			for (int x=0, w=img.getWidth(); x<w; ++x) {
140 				Y: for (int y=0, h=img.getHeight(); y<h; ++y) {
141 					++totalPixels;
142 					int newPixel = img.getRGB(x, y);
143 					int oldPixel = pimg.getRGB(x, y);
144 					if (newPixel!=oldPixel) {
145 						++differentPixels;
146 						for (Region region: regions) {
147 							if (region.merge(x, y)) {
148 								continue Y;
149 							}
150 						}
151 						regions.add(new Region(img, imageFormat, imageChannel, pimg, transparency, x, y, grabRange));
152 					}
153 				}
154 			}
155 
156 			if (getDiffLevel()>diffThreshold) {
157 				regions.clear();
158 				regions.add(new Region(image));
159 			} else {
160 				// Merging adjacent regions
161 				for (int i=0; i<regions.size()-1; ++i) {
162 					ListIterator<Region> lit = regions.listIterator(i+1);
163 					Region master = regions.get(i);
164 					while (lit.hasNext()) {
165 						if (master.merge(lit.next())) {
166 							lit.remove();
167 						}
168 					}
169 				}
170 
171 				for (Region region: regions) {
172 					region.grabImage();
173 				}
174 			}
175 
176 			// Eligible for garbage collection
177 			if (prev!=null) {
178 				prev.image=null;
179 			}
180 		}
181 
182 		// De-dup
183 		ListIterator<Region> oit = regions.listIterator();
184 		R: while (oit.hasNext()) {
185 			Region or = oit.next();
186 
187 			if (oit.hasPrevious()) {
188 				ListIterator<Region> iit = regions.listIterator(oit.previousIndex());
189 				while (iit.hasPrevious()) {
190 					if (or.dedup(iit.previous())) {
191 						continue R;
192 					}
193 				}
194 			}
195 
196 			for (ScreenShot sibling=prev; sibling!=null; sibling=sibling.prev) {
197 				for (Region sr: sibling.regions) {
198 					if (or.dedup(sr)) {
199 						continue R;
200 					}
201 				}
202 			}
203 		}
204 		return this;
205 	}
206 
dump(File dir, String imageFormat)207 	public void dump(File dir, String imageFormat) throws IOException {
208 		for (int i=0; i<regions.size(); ++i) {
209 			BufferedImage img = regions.get(i).getImage().getImage();
210 			if (img!=null) {
211 				ImageIO.write(img, imageFormat, new File(dir, "s_"+secNo+"_"+i+"."+imageFormat));
212 			}
213 		}
214 	}
215 
getRegions()216 	public List<Region> getRegions() {
217 		return regions;
218 	}
219 
getSecNo()220 	public int getSecNo() {
221 		return secNo;
222 	}
223 
isActive()224 	public boolean isActive() {
225 		if (!regions.isEmpty()) {
226 			return true;
227 		}
228 		if (mousePosition==null) {
229 			if (prev==null) {
230 				return false;
231 			}
232 			if (prev.getMousePosition()!=null) {
233 				return true;
234 			}
235 			return false;
236 		}
237 
238 		if (prev==null) {
239 			return true;
240 		}
241 		if (!mousePosition.equals(prev.getMousePosition())) {
242 			return true;
243 		}
244 		return false;
245 	}
246 
getSize()247 	public Dimension getSize() {
248 		return size;
249 	}
250 
251 }
252