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