View Javadoc

1   package com.atlassian.selenium.visualcomparison.utils;
2   
3   import com.atlassian.selenium.visualcomparison.VisualComparableClient;
4   
5   import javax.imageio.ImageIO;
6   import java.awt.image.BufferedImage;
7   import java.io.File;
8   import java.io.IOException;
9   import java.util.ArrayList;
10  import java.util.List;
11  
12  public class Screenshot implements Comparable<Screenshot>
13  {
14      private ScreenResolution resolution;
15      private String id;
16      private File file;
17  
18      public Screenshot(VisualComparableClient client, String id, String imageDir, ScreenResolution resolution) throws IOException
19      {
20          final String filePath = imageDir + "/" + id + "." + resolution + ".png";
21          client.captureEntirePageScreenshot(filePath);
22          init(filePath, id, resolution);
23      }
24  
25      public Screenshot(File file) throws IOException
26      {
27          String[] fileNameParts = file.getName().split("\\.");
28          if (fileNameParts.length != 3)
29          {
30              throw new IOException("Invalid screenshot name - " + file.getName());
31          }
32          init(file.getAbsolutePath(), fileNameParts[0], new ScreenResolution(fileNameParts[1]));
33      }
34  
35      public static String generateFileName(String id, ScreenResolution resolution)
36      {
37          return id.replace('.', '-') + "." + resolution + ".png";
38      }
39  
40      private void init(String filePath, String id, ScreenResolution resolution) throws IOException
41      {
42          this.id = id;
43          this.resolution = resolution;
44          this.file = new File(filePath);
45      }
46  
47      public String getFileName()
48      {
49          return file.getName();
50      }
51  
52      public BufferedImage getImage() throws IOException
53      {
54          return ImageIO.read(file);
55      }
56  
57      public int compareTo(Screenshot other)
58      {
59          int result = this.id.compareTo(other.id);
60          if (result != 0) {
61              return result;
62          }
63          return this.resolution.compareTo(other.resolution);
64      }
65  
66      public ScreenshotDiff getDiff(Screenshot other) throws IOException
67      {
68          return getDiff (other, null, false);
69      }
70  
71      public ScreenshotDiff getDiff(Screenshot other, List<BoundingBox> ignoreAreas, boolean ignoreSingleLines) throws IOException
72      {
73          final BufferedImage thisImage = getImage();
74          final BufferedImage otherImage = other.getImage();
75          final int thisWidth = thisImage.getWidth();
76          final int thisHeight = thisImage.getHeight();
77          final int otherWidth = otherImage.getWidth();
78          final int otherHeight = otherImage.getHeight();
79          final int maxWidth = Math.max(thisWidth, otherWidth);
80          final int maxHeight = Math.max(thisHeight, otherHeight);
81  
82          // Iterate through the pixels in both images and create a diff image.
83          BufferedImage diffImage = new BufferedImage(maxWidth, maxHeight, BufferedImage.TYPE_INT_RGB);
84          ArrayList<BoundingBox> boxes = new ArrayList<BoundingBox>();
85          for (int x = 0; x < maxWidth; x++)
86          {
87              for (int y = 0; y < maxHeight; y++)
88              {
89                  // The images aren't necessarily the same size, only compare the pixels if they're
90                  // present in both.
91                  if ((x < thisWidth) && (x < otherWidth) && (y < thisHeight) && (y < otherHeight))
92                  {
93                      int thisPixel = thisImage.getRGB(x, y);
94                      int otherPixel = otherImage.getRGB(x, y);
95                      if (shouldIgnorePixel (x, y, ignoreAreas) || thisPixel == otherPixel)
96                      {
97                          // The pixels are the same or we don't care if they're different, draw it as-is in the diff.
98                          diffImage.setRGB(x, y, thisPixel);
99                      }
100                     else
101                     {
102                         // The pixels are different, set the pixel to red in the diff.
103                         diffImage.setRGB(x, y, 0xFF0000);
104 
105                         // If this pixel is near any bounding boxes, expand them to include it.
106                         boolean foundBox = false;
107                         for (BoundingBox box : boxes)
108                         {
109                             if (box.isNear(x, y))
110                             {
111                                 box.merge(x, y);
112                                 foundBox = true;
113                             }
114                         }
115                         // Otherwise, start a new bounding box.
116                         if (!foundBox)
117                         {
118                             boxes.add(new BoundingBox(x, y));
119                         }
120                     }
121                 }
122                 else
123                 {
124                     // The two images are different sizes and this pixel is out of bounds in one of them.
125                     // Set the pixel to black in the diff.
126                     diffImage.setRGB(x, y, 0x000000);
127                 }
128             }
129         }
130         if (ignoreSingleLines)
131         {
132             BoundingBox.deleteSingleLineBoxes(boxes);
133         }
134 
135         BoundingBox.mergeOverlappingBoxes(boxes);
136 
137         thisImage.flush();
138         otherImage.flush();
139 
140         return new ScreenshotDiff(this, other, this.id, this.resolution, diffImage, boxes, ignoreAreas);
141     }
142 
143     private boolean shouldIgnorePixel (int x, int y, List<BoundingBox> ignoreAreas)
144     {
145         if (ignoreAreas == null)
146         {
147             return false;
148         }
149         for (BoundingBox ignoreArea : ignoreAreas)
150         {
151             if (ignoreArea.contains (x,y))
152             {
153                 return true;
154             }
155         }
156         return false;
157     }
158 }