@@ -1682,8 +1682,8 @@ private boolean hasColor(int pixel, int red, int green, int blue){
16821682 //**************************************************************************
16831683 //** equals
16841684 //**************************************************************************
1685- /** Used to compare this image to another. If the ARGB values match, this
1686- * method will return true .
1685+ /** Used to compare this image to another. Returns true if the all the
1686+ * pixels (ARGB values) match. See isSimilarTo() to compare similar images .
16871687 */
16881688 public boolean equals (Object obj ){
16891689 if (obj !=null ){
@@ -1713,9 +1713,110 @@ public boolean equals(Object obj){
17131713 }
17141714
17151715
1716+ //**************************************************************************
1717+ //** isSimilarTo
1718+ //**************************************************************************
1719+ /** Returns true if a given image is similar to (or equal to) this image.
1720+ * Unlike the equals() method which performs an exact match, this method
1721+ * performs a fuzzy match using PHash values for the images.
1722+ */
1723+ public boolean isSimilarTo (Image image ){
1724+ if (image ==null ) return false ;
1725+ return this .getPHash ().equals (image .getPHash ());
1726+ }
1727+
1728+
1729+ //**************************************************************************
1730+ //** getPHash
1731+ //**************************************************************************
1732+ /** Returns a perceptual hash value for the image. Unlike traditional
1733+ * cryptographic hashes like SHA1, PHash is not sensitive to tiny
1734+ * variations in an image. This makes it ideal to find similar images.
1735+ * @return A String representing a 64-bit long value (PHash). Example:
1736+ * "1101001010111111010011111110111000011001001011110011110111001101"
1737+ */
1738+ public String getPHash (){
1739+
1740+ //Clone the image
1741+ BufferedImage bi = new BufferedImage (
1742+ bufferedImage .getWidth (), bufferedImage .getHeight (), getImageType ());
1743+ Graphics2D g = bi .createGraphics ();
1744+ g .drawImage (bufferedImage , 0 , 0 , bi .getWidth (), bi .getHeight (), null );
1745+ g .dispose ();
1746+ Image image = new Image (bi );
1747+
1748+
1749+ //Resize and desaturate
1750+ int size = 32 ;
1751+ image .resize (size , size , false );
1752+ image .desaturate ();
1753+ bi = image .getBufferedImage ();
1754+
1755+
1756+ //Perform DCT-II by row
1757+ double N = size ;
1758+ double [][] f = new double [size ][size ];
1759+ for (int y = 0 ; y < image .getHeight (); y ++) {
1760+ for (int x = 0 ; x < image .getWidth (); x ++) {
1761+
1762+ double k = x ;
1763+ double sum = 0.0 ;
1764+ for (double n =0 ; n <N ; n ++){
1765+ double v = (bi .getRGB ((int )n , y )) & 0xff ;
1766+ sum += (2.0 *v )*Math .cos (Math .PI * k * ((2.0 *n +1 )/(2.0 *N )));
1767+ }
1768+
1769+ f [x ][y ] = sum ;
1770+ }
1771+ }
17161772
17171773
17181774
1775+ //Perform DCT-II by column
1776+ double [][] F = new double [size ][size ];
1777+ for (int x = 0 ; x < image .getWidth (); x ++) {
1778+ for (int y = 0 ; y < image .getHeight (); y ++) {
1779+
1780+ double k = y ;
1781+ double sum = 0.0 ;
1782+ for (double n =0 ; n <N ; n ++){
1783+ double v = f [x ][(int )n ];
1784+ sum += (2.0 *v )*Math .cos (Math .PI * k * ((2.0 *n +1 )/(2.0 *N )));
1785+ }
1786+
1787+ F [x ][y ] = sum ;
1788+ }
1789+ }
1790+
1791+
1792+
1793+ //Compute the mean DCT value using an 8x8 sample from the top-left corner
1794+ //of the DCT, excluding the first term since the it can be significantly
1795+ //different from the other values.
1796+ double total = 0 ;
1797+ for (int x = 0 ; x < 8 ; x ++) {
1798+ for (int y = 0 ; y < 8 ; y ++) {
1799+ total += F [x ][y ];
1800+ }
1801+ }
1802+ total -= F [0 ][0 ];
1803+ double avg = total / (double ) ((8 * 8 ) - 1 );
1804+
1805+
1806+
1807+ //Set the 64 hash bits to 0 or 1 depending on whether a DCT value is
1808+ //above or below the average
1809+ long hashBits = 0 ;
1810+ for (int x = 0 ; x < 8 ; x ++) {
1811+ for (int y = 0 ; y < 8 ; y ++) {
1812+ hashBits = (F [x ][y ] > avg ? (hashBits << 1 ) | 0x01 : (hashBits << 1 ) & 0xFFFFFFFFFFFFFFFEl );
1813+ }
1814+ }
1815+
1816+ return Long .toBinaryString (hashBits );
1817+ }
1818+
1819+
17191820 //**************************************************************************
17201821 //** getIIOMetadata
17211822 //**************************************************************************
0 commit comments