diff --git a/pom.xml b/pom.xml index 41ca467..78f09c3 100644 --- a/pom.xml +++ b/pom.xml @@ -19,6 +19,16 @@ + + org.apache.maven.plugins + maven-compiler-plugin + 3.7.0 + + 1.8 + 1.8 + + + org.apache.maven.plugins maven-surefire-plugin diff --git a/src/main/java/com/pragone/jphash/image/DisposingSimpleGrayscaleImage.java b/src/main/java/com/pragone/jphash/image/DisposingSimpleGrayscaleImage.java deleted file mode 100644 index 12f95aa..0000000 --- a/src/main/java/com/pragone/jphash/image/DisposingSimpleGrayscaleImage.java +++ /dev/null @@ -1,346 +0,0 @@ -package com.pragone.jphash.image; - -import sun.misc.Cleaner; - -import javax.imageio.ImageIO; -import java.awt.image.BufferedImage; -import java.io.File; -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.nio.ByteBuffer; -import java.util.HashMap; -import java.util.Map; - -public class DisposingSimpleGrayscaleImage { - private static final int BYTE_SIZE = 8; - private static Map, Method> cleanerMethods = new HashMap, Method>(); - private int width; - private int height; - private ByteBuffer data; - private int numPixels; - - static { - ByteBuffer temp = ByteBuffer.allocateDirect(16); - if (temp != null) { - Method cleanerMethod = null; - try { - cleanerMethod = temp.getClass().getMethod("cleaner"); - cleanerMethod.setAccessible(true); - cleanerMethods.put(temp.getClass(), cleanerMethod); - - try { - Cleaner cleaner = (Cleaner) cleanerMethod.invoke(temp); - cleaner.clean(); - } catch (Exception e) { - System.err.println("Couldnt clean up temporary Direct Byte Buffer. Possible memory leaks"); - } - } catch (NoSuchMethodException e) { - e.printStackTrace(); - } - } - } - - private static Method getCleanerMethod(Class byteBufferClass) { - Method temp = cleanerMethods.get(byteBufferClass); - if (temp == null) { - synchronized (DisposingSimpleGrayscaleImage.class) { - if (!cleanerMethods.containsKey(byteBufferClass)) { - try { - Method cleanerMethod = temp.getClass().getMethod("cleaner"); - cleanerMethod.setAccessible(true); - cleanerMethods.put(temp.getClass(), cleanerMethod); - return cleanerMethod; - } catch (NoSuchMethodException e) { - System.err.println("Couldnt clean up temporary Direct Byte Buffer. Possible memory leaks"); - } - } - } - temp = cleanerMethods.get(byteBufferClass); - } - return temp; - } -// private static final int[] NORMALIZATION_APROX; -// private static final int NORMALIZATION_DENOMINATOR_POWER = 11; // 2048 -// -// static { -// NORMALIZATION_APROX = new int[256]; -// double denominator = 1 << NORMALIZATION_DENOMINATOR_POWER; -// for (int i = 1; i < 256; i++) { -// double toAprox = ((double) 256)/i; -// NORMALIZATION_APROX[i] = (int) Math.round(toAprox * denominator); -// } -// } - - public DisposingSimpleGrayscaleImage(int width, int height) { - this.width = width; - this.height = height; - this.numPixels = width*height; - this.data = ByteBuffer.allocateDirect(width * height); - -// this.pointer = Unsafe.getUnsafe().allocateMemory(width*height); -// Unsafe.getUnsafe().setMemory(this.pointer, width * height, (byte) 0); - } - - public DisposingSimpleGrayscaleImage(BufferedImage image) { - this(image.getWidth(), image.getHeight()); - loadImage(image); - resizeToNextSize(); - blur(); - } - - public void blur() { - if (width <= 6 || height <= 6) { - return; - } - // First a horizontal pass - int[] buffer = new int[width]; - int[] buffer_1 = new int[width]; - int[] buffer_2 = new int[width]; - this.data.rewind(); - for (int y = 0; y < height; y++) { - // Copy this horizontal line - for (int i = 0; i < width; i++) { - buffer[i] = this.data.get(width*y + i) & 0xFF; - buffer_1[i] = (byte) (buffer[i] >> 1); // buffer * 0.5 - buffer_2[i] = (byte) (buffer[i] >> 2); // buffer * 0.25 - } - // idx: 0 - long t = ((buffer[0] + buffer_1[1] + buffer_2[2])*585)>>10; // 1024/585 = 1.75042 - this.data.put(width*y,(byte) (t > 255 ? 255 : t)); - t = ((buffer_1[0] + buffer[1] + buffer_1[2] + buffer_2[3])*455)>>10; // 1024/455 = 2.250549 - this.data.put(width*y+1,(byte) (t > 255 ? 255 : t)); - for (int x = 2; x < width-2; x++) { - t = (int) (((long) (buffer_2[x-2] + buffer_1[x-1] + buffer[x] + buffer_1[x+1] + buffer_2[x+2]))*409)>>10; // 1024/409 = 2.503 ~ 1 + 2*0.5 + 2*0.25 - this.data.put(width*y+x, (byte) (t > 255 ? 255 : t)); - } - int x = width-2; - t = ((buffer_2[x-2] + buffer_1[x-1] + buffer[x] + buffer_1[x+1])*455)>>10; // 1024/455 = 2.250549 - this.data.put(width*y+x, (byte) (t > 255 ? 255 : t)); - x++; - t = ((buffer_2[x-2] + buffer_1[x-1] + buffer[x])*585)>>10; // 1024/585 = 1.75042 - this.data.put(width*y+x, (byte) (t > 255 ? 255 : t)); - } - - // Now a vertical pass - buffer = new int[height]; - buffer_1 = new int[height]; - buffer_2 = new int[height]; - for (int x = 0; x < width; x++) { - // Copy this vertical line - for (int i = 0; i < height; i++) { - buffer[i] = this.data.get(width*i + x) & 0xFF; - buffer_1[i] = (byte) (buffer[i] >> 1); // buffer * 0.5 - buffer_2[i] = (byte) (buffer[i] >> 2); // buffer * 0.25 - } - // y = 0 - this.data.put(x,(byte) (((buffer[0] + buffer_1[1] + buffer_2[2])*585)>>10)); // 1024/585 = 1.75042 - // y = 1 - this.data.put(width+x,(byte) (((buffer_1[0] + buffer[1] + buffer_1[2] + buffer_2[3])*455)>>10)); // 1024/455 = 2.250549 - for (int y = 2; y < height-2; y++) { - long t = (((long) (buffer_2[y-2] + buffer_1[y-1] + buffer[y] + buffer_1[y+1] + buffer_2[y+2]))*409)>>10; - this.data.put(width*y+x, (byte) (t > 255 ? 255 : t)); // 1024/409 = 2.503 ~ 1 + 2*0.5 + 2*0.25 - } - int y = height-2; - this.data.put(width*y+x, (byte) (((buffer_2[y-2] + buffer_1[y-1] + buffer[y] + buffer_1[y+1])*455)>>10)); // 1024/455 = 2.250549 - y++; - this.data.put(width*y+x, (byte) (((buffer_2[y-2] + buffer_1[y-1] + buffer[y])*585)>>10)); // 1024/585 = 1.75042 - } - } - - private void resizeToNextSize() { - int min = (width < height) ? width : height; - min = getClosestSmallerPowerOf2(min); - this.resize(min,min); - } - - private int getClosestSmallerPowerOf2(int value) { - int i = 0; - int v = 1; - while (v < value && i < 24) { - i++; - v=v<<1; - } - return v >> 1; - } - - public void resize(int dest_width, int dest_height) { - ByteBuffer newData = ByteBuffer.allocateDirect(dest_width * dest_height); - - double tx = ((double) width) / dest_width; - double ty = ((double) height) / dest_height; - - int Cc; - int C[] = new int[5]; - int d0, d2, d3, a0, a1, a2, a3; - - for (int i = 0; i < dest_height; ++i) { - for (int j = 0; j < dest_width; ++j) { - int x = (int) (tx * j); - int y = (int) (ty * i); - double dx = tx * j - x; - double dy = ty * i - y; - - for (int jj = 0; jj <= 3; ++jj) { - d0 = safeGet((y - 1 + jj) * width + (x - 1)) - - safeGet((y - 1 + jj) * width + (x)); - d2 = safeGet((y - 1 + jj) * width + (x + 1)) - - safeGet((y - 1 + jj) * width + (x)); - d3 = safeGet((y - 1 + jj) * width + (x + 2)) - - safeGet((y - 1 + jj) * width + (x)); - a0 = safeGet((y - 1 + jj) * width + (x)); - a1 = (int) (-1.0 / 3 * d0 + d2 - 1.0 / 6 * d3); - a2 = (int) (1.0 / 2 * d0 + 1.0 / 2 * d2); - a3 = (int) (-1.0 / 6 * d0 - 1.0 / 2 * d2 + 1.0 / 6 * d3); - C[jj] = (int) (a0 + a1 * dx + a2 * dx * dx + a3 * dx * dx * dx); - - d0 = C[0] - C[1]; - d2 = C[2] - C[1]; - d3 = C[3] - C[1]; - a0 = C[1]; - a1 = (int) (-1.0 / 3 * d0 + d2 -1.0 / 6 * d3); - a2 = (int) (1.0 / 2 * d0 + 1.0 / 2 * d2); - a3 = (int) (-1.0 / 6 * d0 - 1.0 / 2 * d2 + 1.0 / 6 * d3); - Cc = (int) (a0 + a1 * dy + a2 * dy * dy + a3* dy * dy * dy); - newData.put(i * dest_width + j, (byte) (Cc & 0xFF)); - } - } - } - dispose(); - this.data = newData; - this.width = dest_width; - this.height = dest_height; - this.numPixels = width*height; - } - - - private int safeGet(int index) { - if (index < 0) { - return 0; - } - if (index >= numPixels) { - return 0; - } - return this.data.get(index) & 0xFF; - } - - public void loadImage(BufferedImage image) { - int numPixels = width*height; - int numComponents = image.getColorModel().getNumComponents(); - int maxPixel = 0; - if (image.getColorModel().getComponentSize(0) == BYTE_SIZE) { - // Components are byte sized - int bufferSize = numPixels*numComponents; - byte[] tempBuffer ; - try{ - tempBuffer = new byte[bufferSize]; - }catch (OutOfMemoryError e) { - System.out.println("Died trying to allocate a buffer of size: " + bufferSize + "!!"); - while (true) { - try { - Thread.sleep(1000); - } catch (InterruptedException e1) { - e1.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. - } - } - } - image.getRaster().getDataElements(0,0,width,height,tempBuffer); - - if (numComponents == 1) { - // Already byte gray - this.data.put(tempBuffer, 0, numPixels); - } else if (image.getType() == BufferedImage.TYPE_3BYTE_BGR) { - int j = 0; - for (int i = 0; i< bufferSize; i+= numComponents) { - // V1 -// long yTemp = ((tempBuffer[i]& 0xFF)*BLUE_Y_COEFF + -// (tempBuffer[i+1]& 0xFF)*GREEN_Y_COEFF + -// (tempBuffer[i+2]& 0xFF)*RED_Y_COEFF); -// int y = (int) (((yTemp >> 10) & 0xFF) + ((yTemp >> 9) & 0x1)); -// where: -// private static final long BLUE_Y_COEFF = (long) Math.floor(0.114d * 1024); -// private static final long GREEN_Y_COEFF = (long) Math.floor(0.587d * 1024); -// private static final long RED_Y_COEFF = (long) Math.floor(0.299d * 1024); - - // V2: CImg version - int y = (((tempBuffer[i+2]& 0xFF) * 66 + (tempBuffer[i+1]& 0xFF) * 129 + - (tempBuffer[i]& 0xFF) *25) >> 8) + 16; - - // if (y < 0) y = 0; - // else - - if (y>255) y = 255; - - if (y > maxPixel) { - maxPixel = y; - } - this.data.put(j++,(byte) y); - } - } else { - throw new IllegalArgumentException("Can't work with this type of byte image: " + image.getType()); - } - } else { - throw new IllegalArgumentException("Can't work with non-byte image buffers"); - } - if (maxPixel > 0) { - // Let's normalize amount of light - // V1: with double math - for (int i =0; i < numPixels; i++) { - long temp = (this.data.get(i) << 8) / maxPixel; - this.data.put(i, (byte) (temp & 0xFF)); - } - // V2: with int only math -// for (int i =0; i < numPixels; i++) { -// long temp = (this.data.get(i) * NORMALIZATION_APROX[this.maxPixel]) >> NORMALIZATION_DENOMINATOR_POWER; -// this.data.put(i, (byte) (temp & 0xFF)); -// } - } - - } - - public void save(String path) { - BufferedImage temp = new BufferedImage(width,height,BufferedImage.TYPE_BYTE_GRAY); - byte[] buffer = new byte[width*height]; - this.data.rewind(); - this.data.get(buffer); - temp.getRaster().setDataElements(0,0,width,height,buffer); - try { - ImageIO.write(temp, "jpg", new File(path)); - } catch (IOException e) { - } - } - - public int getWidth() { - return width; - } - - public int getHeight() { - return height; - } - - public int get(int x, int y) { - return this.data.get(width*y + x) & 0xFF; - } - - public void dispose() { - if (this.data != null && this.data.isDirect()) { - - Method cleanerMethod = getCleanerMethod(this.data.getClass()); - try { - Cleaner cleaner = (Cleaner) cleanerMethod.invoke(this.data); - cleaner.clean(); - } catch (InvocationTargetException e) { - e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. - } catch (IllegalAccessException e) { - e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. - } - - } - } - -// @Override -// protected void finalize() throws Throwable { -// Unsafe.getUnsafe().freeMemory(this.pointer); -// super.finalize(); -// } -} \ No newline at end of file diff --git a/src/main/java/com/pragone/jphash/image/SimpleGrayscaleImage.java b/src/main/java/com/pragone/jphash/image/SimpleGrayscaleImage.java index 7444274..34866c3 100644 --- a/src/main/java/com/pragone/jphash/image/SimpleGrayscaleImage.java +++ b/src/main/java/com/pragone/jphash/image/SimpleGrayscaleImage.java @@ -1,36 +1,21 @@ package com.pragone.jphash.image; -import sun.jvm.hotspot.debugger.Debugger; -import sun.misc.Cleaner; - -import javax.imageio.ImageIO; +import java.awt.*; import java.awt.image.BufferedImage; -import java.io.File; -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.nio.ByteBuffer; -import java.util.HashMap; -import java.util.Map; public class SimpleGrayscaleImage { - private static final int BYTE_SIZE = 8; private int width; private int height; private ByteBuffer data; private int numPixels; - public SimpleGrayscaleImage(int width, int height) { - this.width = width; - this.height = height; - this.numPixels = width*height; - this.data = ByteBuffer.allocateDirect(width * height); - } - public SimpleGrayscaleImage(BufferedImage image) { - this(image.getWidth(), image.getHeight()); + this.width = image.getWidth(); + this.height = image.getHeight(); + this.numPixels = width * height; + loadImage(image); - resizeToNextSize(); blur(); } @@ -46,25 +31,25 @@ public void blur() { for (int y = 0; y < height; y++) { // Copy this horizontal line for (int i = 0; i < width; i++) { - buffer[i] = this.data.get(width*y + i) & 0xFF; + buffer[i] = this.data.get(width * y + i) & 0xFF; buffer_1[i] = (byte) (buffer[i] >> 1); // buffer * 0.5 buffer_2[i] = (byte) (buffer[i] >> 2); // buffer * 0.25 } // idx: 0 - long t = ((buffer[0] + buffer_1[1] + buffer_2[2])*585)>>10; // 1024/585 = 1.75042 - this.data.put(width*y,(byte) (t > 255 ? 255 : t)); - t = ((buffer_1[0] + buffer[1] + buffer_1[2] + buffer_2[3])*455)>>10; // 1024/455 = 2.250549 - this.data.put(width*y+1,(byte) (t > 255 ? 255 : t)); - for (int x = 2; x < width-2; x++) { - t = (int) (((long) (buffer_2[x-2] + buffer_1[x-1] + buffer[x] + buffer_1[x+1] + buffer_2[x+2]))*409)>>10; // 1024/409 = 2.503 ~ 1 + 2*0.5 + 2*0.25 - this.data.put(width*y+x, (byte) (t > 255 ? 255 : t)); + long t = ((buffer[0] + buffer_1[1] + buffer_2[2]) * 585) >> 10; // 1024/585 = 1.75042 + this.data.put(width * y, (byte) (t > 255 ? 255 : t)); + t = ((buffer_1[0] + buffer[1] + buffer_1[2] + buffer_2[3]) * 455) >> 10; // 1024/455 = 2.250549 + this.data.put(width * y + 1, (byte) (t > 255 ? 255 : t)); + for (int x = 2; x < width - 2; x++) { + t = (int) (((long) (buffer_2[x - 2] + buffer_1[x - 1] + buffer[x] + buffer_1[x + 1] + buffer_2[x + 2])) * 409) >> 10; // 1024/409 = 2.503 ~ 1 + 2*0.5 + 2*0.25 + this.data.put(width * y + x, (byte) (t > 255 ? 255 : t)); } - int x = width-2; - t = ((buffer_2[x-2] + buffer_1[x-1] + buffer[x] + buffer_1[x+1])*455)>>10; // 1024/455 = 2.250549 - this.data.put(width*y+x, (byte) (t > 255 ? 255 : t)); + int x = width - 2; + t = ((buffer_2[x - 2] + buffer_1[x - 1] + buffer[x] + buffer_1[x + 1]) * 455) >> 10; // 1024/455 = 2.250549 + this.data.put(width * y + x, (byte) (t > 255 ? 255 : t)); x++; - t = ((buffer_2[x-2] + buffer_1[x-1] + buffer[x])*585)>>10; // 1024/585 = 1.75042 - this.data.put(width*y+x, (byte) (t > 255 ? 255 : t)); + t = ((buffer_2[x - 2] + buffer_1[x - 1] + buffer[x]) * 585) >> 10; // 1024/585 = 1.75042 + this.data.put(width * y + x, (byte) (t > 255 ? 255 : t)); } // Now a vertical pass @@ -74,177 +59,48 @@ public void blur() { for (int x = 0; x < width; x++) { // Copy this vertical line for (int i = 0; i < height; i++) { - buffer[i] = this.data.get(width*i + x) & 0xFF; + buffer[i] = this.data.get(width * i + x) & 0xFF; buffer_1[i] = (byte) (buffer[i] >> 1); // buffer * 0.5 buffer_2[i] = (byte) (buffer[i] >> 2); // buffer * 0.25 } // y = 0 - this.data.put(x,(byte) (((buffer[0] + buffer_1[1] + buffer_2[2])*585)>>10)); // 1024/585 = 1.75042 + this.data.put(x, (byte) (((buffer[0] + buffer_1[1] + buffer_2[2]) * 585) >> 10)); // 1024/585 = 1.75042 // y = 1 - this.data.put(width+x,(byte) (((buffer_1[0] + buffer[1] + buffer_1[2] + buffer_2[3])*455)>>10)); // 1024/455 = 2.250549 - for (int y = 2; y < height-2; y++) { - long t = (((long) (buffer_2[y-2] + buffer_1[y-1] + buffer[y] + buffer_1[y+1] + buffer_2[y+2]))*409)>>10; - this.data.put(width*y+x, (byte) (t > 255 ? 255 : t)); // 1024/409 = 2.503 ~ 1 + 2*0.5 + 2*0.25 + this.data.put(width + x, (byte) (((buffer_1[0] + buffer[1] + buffer_1[2] + buffer_2[3]) * 455) >> 10)); // 1024/455 = 2.250549 + for (int y = 2; y < height - 2; y++) { + long t = (((long) (buffer_2[y - 2] + buffer_1[y - 1] + buffer[y] + buffer_1[y + 1] + buffer_2[y + 2])) * 409) >> 10; + this.data.put(width * y + x, (byte) (t > 255 ? 255 : t)); // 1024/409 = 2.503 ~ 1 + 2*0.5 + 2*0.25 } - int y = height-2; - this.data.put(width*y+x, (byte) (((buffer_2[y-2] + buffer_1[y-1] + buffer[y] + buffer_1[y+1])*455)>>10)); // 1024/455 = 2.250549 + int y = height - 2; + this.data.put(width * y + x, (byte) (((buffer_2[y - 2] + buffer_1[y - 1] + buffer[y] + buffer_1[y + 1]) * 455) >> 10)); // 1024/455 = 2.250549 y++; - this.data.put(width*y+x, (byte) (((buffer_2[y-2] + buffer_1[y-1] + buffer[y])*585)>>10)); // 1024/585 = 1.75042 - } - } - - private void resizeToNextSize() { - int min = (width < height) ? width : height; - min = getClosestSmallerPowerOf2(min); - this.resize(min,min); - } - - private int getClosestSmallerPowerOf2(int value) { - int i = 0; - int v = 1; - while (v < value && i < 24) { - i++; - v=v<<1; - } - return v >> 1; - } - - public void resize(int dest_width, int dest_height) { - ByteBuffer newData = ByteBuffer.allocateDirect(dest_width * dest_height); - - double tx = ((double) width) / dest_width; - double ty = ((double) height) / dest_height; - - int Cc; - int C[] = new int[5]; - int d0, d2, d3, a0, a1, a2, a3; - - for (int i = 0; i < dest_height; ++i) { - for (int j = 0; j < dest_width; ++j) { - int x = (int) (tx * j); - int y = (int) (ty * i); - double dx = tx * j - x; - double dy = ty * i - y; - - for (int jj = 0; jj <= 3; ++jj) { - d0 = safeGet((y - 1 + jj) * width + (x - 1)) - - safeGet((y - 1 + jj) * width + (x)); - d2 = safeGet((y - 1 + jj) * width + (x + 1)) - - safeGet((y - 1 + jj) * width + (x)); - d3 = safeGet((y - 1 + jj) * width + (x + 2)) - - safeGet((y - 1 + jj) * width + (x)); - a0 = safeGet((y - 1 + jj) * width + (x)); - a1 = (int) (-1.0 / 3 * d0 + d2 - 1.0 / 6 * d3); - a2 = (int) (1.0 / 2 * d0 + 1.0 / 2 * d2); - a3 = (int) (-1.0 / 6 * d0 - 1.0 / 2 * d2 + 1.0 / 6 * d3); - C[jj] = (int) (a0 + a1 * dx + a2 * dx * dx + a3 * dx * dx * dx); - - d0 = C[0] - C[1]; - d2 = C[2] - C[1]; - d3 = C[3] - C[1]; - a0 = C[1]; - a1 = (int) (-1.0 / 3 * d0 + d2 -1.0 / 6 * d3); - a2 = (int) (1.0 / 2 * d0 + 1.0 / 2 * d2); - a3 = (int) (-1.0 / 6 * d0 - 1.0 / 2 * d2 + 1.0 / 6 * d3); - Cc = (int) (a0 + a1 * dy + a2 * dy * dy + a3* dy * dy * dy); - newData.put(i * dest_width + j, (byte) (Cc & 0xFF)); - } - } + this.data.put(width * y + x, (byte) (((buffer_2[y - 2] + buffer_1[y - 1] + buffer[y]) * 585) >> 10)); // 1024/585 = 1.75042 } - this.data = newData; - this.width = dest_width; - this.height = dest_height; - this.numPixels = width*height; } - - private int safeGet(int index) { - if (index < 0) { - return 0; - } - if (index >= numPixels) { - return 0; - } - return this.data.get(index) & 0xFF; - } + private static final int RESIZE_WIDTH = 1024; + private static final int RESIZE_HEIGHT = 1024; public void loadImage(BufferedImage image) { - int numPixels = width*height; - int numComponents = image.getColorModel().getNumComponents(); - int maxPixel = 0; - if (image.getColorModel().getComponentSize(0) == BYTE_SIZE) { - // Components are byte sized - int bufferSize = numPixels*numComponents; - byte[] tempBuffer ; - try{ - tempBuffer = new byte[bufferSize]; - }catch (OutOfMemoryError e) { - System.out.println("Died trying to allocate a buffer of size: " + bufferSize + ". Please increas heap size!!"); - throw e; - } - image.getRaster().getDataElements(0,0,width,height,tempBuffer); - - if (numComponents == 1) { - // Already byte gray - this.data.put(tempBuffer, 0, numPixels); - } else if (image.getType() == BufferedImage.TYPE_3BYTE_BGR) { - int j = 0; - for (int i = 0; i< bufferSize; i+= numComponents) { - // V1 -// long yTemp = ((tempBuffer[i]& 0xFF)*BLUE_Y_COEFF + -// (tempBuffer[i+1]& 0xFF)*GREEN_Y_COEFF + -// (tempBuffer[i+2]& 0xFF)*RED_Y_COEFF); -// int y = (int) (((yTemp >> 10) & 0xFF) + ((yTemp >> 9) & 0x1)); -// where: -// private static final long BLUE_Y_COEFF = (long) Math.floor(0.114d * 1024); -// private static final long GREEN_Y_COEFF = (long) Math.floor(0.587d * 1024); -// private static final long RED_Y_COEFF = (long) Math.floor(0.299d * 1024); - - // V2: CImg version - int y = (((tempBuffer[i+2]& 0xFF) * 66 + (tempBuffer[i+1]& 0xFF) * 129 + - (tempBuffer[i]& 0xFF) *25) >> 8) + 16; - - // if (y < 0) y = 0; - // else + try { + BufferedImage resizedImage = new BufferedImage(RESIZE_WIDTH, RESIZE_HEIGHT, BufferedImage.TYPE_BYTE_GRAY); + Graphics2D g = resizedImage.createGraphics(); + g.drawImage(image, 0, 0, RESIZE_WIDTH, RESIZE_HEIGHT, null); + g.dispose(); - if (y>255) y = 255; + numPixels = (RESIZE_WIDTH * RESIZE_HEIGHT); + width = RESIZE_WIDTH; + height = RESIZE_HEIGHT; - if (y > maxPixel) { - maxPixel = y; - } - this.data.put(j++,(byte) y); - } - } else { - throw new IllegalArgumentException("Can't work with this type of byte image: " + image.getType()); - } - } else { - throw new IllegalArgumentException("Can't work with non-byte image buffers"); - } - if (maxPixel > 0) { - // Let's normalize amount of light - // V1: with double math - for (int i =0; i < numPixels; i++) { - long temp = (this.data.get(i) << 8) / maxPixel; - this.data.put(i, (byte) (temp & 0xFF)); - } - // V2: with int only math -// for (int i =0; i < numPixels; i++) { -// long temp = (this.data.get(i) * NORMALIZATION_APROX[this.maxPixel]) >> NORMALIZATION_DENOMINATOR_POWER; -// this.data.put(i, (byte) (temp & 0xFF)); -// } - } + int totalPixels = numPixels * resizedImage.getColorModel().getNumComponents(); - } + byte[] tempBuffer = new byte[totalPixels]; + resizedImage.getRaster().getDataElements(0, 0, RESIZE_WIDTH, RESIZE_HEIGHT, tempBuffer); - public void save(String path) { - BufferedImage temp = new BufferedImage(width,height,BufferedImage.TYPE_BYTE_GRAY); - byte[] buffer = new byte[width*height]; - this.data.rewind(); - this.data.get(buffer); - temp.getRaster().setDataElements(0,0,width,height,buffer); - try { - ImageIO.write(temp, "jpg", new File(path)); - } catch (IOException e) { + data = ByteBuffer.wrap(tempBuffer); + } catch (OutOfMemoryError e) { + System.err.println("Died trying to allocate a buffer for image. Please increase heap size!!"); + throw e; } } @@ -257,7 +113,7 @@ public int getHeight() { } public int get(int x, int y) { - return this.data.get(width*y + x) & 0xFF; + return this.data.get(width * y + x) & 0xFF; } } \ No newline at end of file diff --git a/src/main/java/com/pragone/jphash/image/UnsafeSimpleGrayscaleImage.java b/src/main/java/com/pragone/jphash/image/UnsafeSimpleGrayscaleImage.java deleted file mode 100644 index e299244..0000000 --- a/src/main/java/com/pragone/jphash/image/UnsafeSimpleGrayscaleImage.java +++ /dev/null @@ -1,307 +0,0 @@ -package com.pragone.jphash.image; - -import sun.misc.Unsafe; - -import java.awt.image.BufferedImage; -import java.lang.reflect.Field; - -public class UnsafeSimpleGrayscaleImage { - private static final int BYTE_SIZE = 8; - private static final Unsafe unsafeInstance = getUnsafe(); - private long pointer; - private int width; - private int height; - private int numPixels; - - private static Unsafe getUnsafe() { - try { - Field f = Unsafe.class.getDeclaredField("theUnsafe"); - f.setAccessible(true); - return (Unsafe) f.get(null); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - public UnsafeSimpleGrayscaleImage(int width, int height) { - this.width = width; - this.height = height; - this.numPixels = width*height; - this.pointer = unsafeInstance.allocateMemory(width*height); - unsafeInstance.setMemory(this.pointer, width * height, (byte) 0); - } - - public UnsafeSimpleGrayscaleImage(BufferedImage image) { - this(image.getWidth(), image.getHeight()); - loadImage(image); - resizeToNextSize(); - blur(); - } - - public void blur() { - if (width <= 6 || height <= 6) { - return; - } - // First a horizontal pass - int[] buffer = new int[width]; - int[] buffer_1 = new int[width]; - int[] buffer_2 = new int[width]; - for (int y = 0; y < height; y++) { - // Copy this horizontal line - for (int i = 0; i < width; i++) { - buffer[i] = getByte(width * y + i) & 0xFF; - buffer_1[i] = (byte) (buffer[i] >> 1); // buffer * 0.5 - buffer_2[i] = (byte) (buffer[i] >> 2); // buffer * 0.25 - } - // idx: 0 - long t = ((buffer[0] + buffer_1[1] + buffer_2[2])*585)>>10; // 1024/585 = 1.75042 - putByte(width * y, (byte) (t > 255 ? 255 : t)); - t = ((buffer_1[0] + buffer[1] + buffer_1[2] + buffer_2[3])*455)>>10; // 1024/455 = 2.250549 - putByte(width*y+1,(byte) (t > 255 ? 255 : t)); - for (int x = 2; x < width-2; x++) { - t = (int) (((long) (buffer_2[x-2] + buffer_1[x-1] + buffer[x] + buffer_1[x+1] + buffer_2[x+2]))*409)>>10; // 1024/409 = 2.503 ~ 1 + 2*0.5 + 2*0.25 - putByte(width*y+x, (byte) (t > 255 ? 255 : t)); - } - int x = width-2; - t = ((buffer_2[x-2] + buffer_1[x-1] + buffer[x] + buffer_1[x+1])*455)>>10; // 1024/455 = 2.250549 - putByte(width*y+x, (byte) (t > 255 ? 255 : t)); - x++; - t = ((buffer_2[x-2] + buffer_1[x-1] + buffer[x])*585)>>10; // 1024/585 = 1.75042 - putByte(width*y+x, (byte) (t > 255 ? 255 : t)); - } - - // Now a vertical pass - buffer = new int[height]; - buffer_1 = new int[height]; - buffer_2 = new int[height]; - for (int x = 0; x < width; x++) { - // Copy this vertical line - for (int i = 0; i < height; i++) { - buffer[i] = getByte(width*i + x) & 0xFF; - buffer_1[i] = (byte) (buffer[i] >> 1); // buffer * 0.5 - buffer_2[i] = (byte) (buffer[i] >> 2); // buffer * 0.25 - } - // y = 0 - putByte(x,(byte) (((buffer[0] + buffer_1[1] + buffer_2[2])*585)>>10)); // 1024/585 = 1.75042 - // y = 1 - putByte(width+x,(byte) (((buffer_1[0] + buffer[1] + buffer_1[2] + buffer_2[3])*455)>>10)); // 1024/455 = 2.250549 - for (int y = 2; y < height-2; y++) { - long t = (((long) (buffer_2[y-2] + buffer_1[y-1] + buffer[y] + buffer_1[y+1] + buffer_2[y+2]))*409)>>10; - putByte(width*y+x, (byte) (t > 255 ? 255 : t)); // 1024/409 = 2.503 ~ 1 + 2*0.5 + 2*0.25 - } - int y = height-2; - putByte(width*y+x, (byte) (((buffer_2[y-2] + buffer_1[y-1] + buffer[y] + buffer_1[y+1])*455)>>10)); // 1024/455 = 2.250549 - y++; - putByte(width*y+x, (byte) (((buffer_2[y-2] + buffer_1[y-1] + buffer[y])*585)>>10)); // 1024/585 = 1.75042 - } - } - - private byte getByte(long offset) { - return unsafeInstance.getByte(this.pointer + offset); - } - - private void putByte(long offset, byte value) { - unsafeInstance.putByte(this.pointer + offset, value); - } - - private void resizeToNextSize() { - int min = (width < height) ? width : height; - min = getClosestSmallerPowerOf2(min); - this.resize(min,min); - } - - private int getClosestSmallerPowerOf2(int value) { - int i = 0; - int v = 1; - while (v < value && i < 24) { - i++; - v=v<<1; - } - return v >> 1; - } - - public void resize(int dest_width, int dest_height) { - long newData = unsafeInstance.allocateMemory(dest_width * dest_height); - - double tx = ((double) width) / dest_width; - double ty = ((double) height) / dest_height; - - int Cc; - int C[] = new int[5]; - int d0, d2, d3, a0, a1, a2, a3; - - for (int i = 0; i < dest_height; ++i) { - for (int j = 0; j < dest_width; ++j) { - int x = (int) (tx * j); - int y = (int) (ty * i); - double dx = tx * j - x; - double dy = ty * i - y; - - for (int jj = 0; jj <= 3; ++jj) { - d0 = safeGet((y - 1 + jj) * width + (x - 1)) - - safeGet((y - 1 + jj) * width + (x)); - d2 = safeGet((y - 1 + jj) * width + (x + 1)) - - safeGet((y - 1 + jj) * width + (x)); - d3 = safeGet((y - 1 + jj) * width + (x + 2)) - - safeGet((y - 1 + jj) * width + (x)); - a0 = safeGet((y - 1 + jj) * width + (x)); - a1 = (int) (-1.0 / 3 * d0 + d2 - 1.0 / 6 * d3); - a2 = (int) (1.0 / 2 * d0 + 1.0 / 2 * d2); - a3 = (int) (-1.0 / 6 * d0 - 1.0 / 2 * d2 + 1.0 / 6 * d3); - C[jj] = (int) (a0 + a1 * dx + a2 * dx * dx + a3 * dx * dx * dx); - - d0 = C[0] - C[1]; - d2 = C[2] - C[1]; - d3 = C[3] - C[1]; - a0 = C[1]; - a1 = (int) (-1.0 / 3 * d0 + d2 -1.0 / 6 * d3); - a2 = (int) (1.0 / 2 * d0 + 1.0 / 2 * d2); - a3 = (int) (-1.0 / 6 * d0 - 1.0 / 2 * d2 + 1.0 / 6 * d3); - Cc = (int) (a0 + a1 * dy + a2 * dy * dy + a3* dy * dy * dy); - unsafeInstance.putByte(newData + i * dest_width + j, (byte) (Cc & 0xFF)); - } - } - } - long temp = this.pointer; - this.pointer = newData; - release(temp); - this.width = dest_width; - this.height = dest_height; - this.numPixels = width*height; - } - - - private int safeGet(int index) { - if (index < 0) { - return 0; - } - if (index >= numPixels) { - return 0; - } - return getByte(index) & 0xFF; - } - - public void loadImage(BufferedImage image) { - int numPixels = width*height; - int numComponents = image.getColorModel().getNumComponents(); - int maxPixel = 0; - if (image.getColorModel().getComponentSize(0) == BYTE_SIZE) { - // Components are byte sized - int bufferSize = numPixels*numComponents; - byte[] tempBuffer ; - try{ - tempBuffer = new byte[bufferSize]; - }catch (OutOfMemoryError e) { - System.out.println("Died trying to allocate a buffer of size: " + bufferSize + "!!"); - while (true) { - try { - Thread.sleep(1000); - } catch (InterruptedException e1) { - e1.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. - } - } - } - image.getRaster().getDataElements(0,0,width,height,tempBuffer); - - if (numComponents == 1) { - // Already byte gray - putBytes(tempBuffer, 0, numPixels,0); - } else if (image.getType() == BufferedImage.TYPE_3BYTE_BGR) { - int j = 0; - for (int i = 0; i< bufferSize; i+= numComponents) { - // V1 -// long yTemp = ((tempBuffer[i]& 0xFF)*BLUE_Y_COEFF + -// (tempBuffer[i+1]& 0xFF)*GREEN_Y_COEFF + -// (tempBuffer[i+2]& 0xFF)*RED_Y_COEFF); -// int y = (int) (((yTemp >> 10) & 0xFF) + ((yTemp >> 9) & 0x1)); -// where: -// private static final long BLUE_Y_COEFF = (long) Math.floor(0.114d * 1024); -// private static final long GREEN_Y_COEFF = (long) Math.floor(0.587d * 1024); -// private static final long RED_Y_COEFF = (long) Math.floor(0.299d * 1024); - - // V2: CImg version - int y = (((tempBuffer[i+2]& 0xFF) * 66 + (tempBuffer[i+1]& 0xFF) * 129 + - (tempBuffer[i]& 0xFF) *25) >> 8) + 16; - - // if (y < 0) y = 0; - // else - - if (y>255) y = 255; - - if (y > maxPixel) { - maxPixel = y; - } - putByte(j++,(byte) y); - } - } else { - throw new IllegalArgumentException("Can't work with this type of byte image: " + image.getType()); - } - } else { - throw new IllegalArgumentException("Can't work with non-byte image buffers"); - } - if (maxPixel > 0) { - // Let's normalize amount of light - // V1: with double math - for (int i =0; i < numPixels; i++) { - long temp = (getByte(i) << 8) / maxPixel; - putByte(i, (byte) (temp & 0xFF)); - } - // V2: with int only math -// for (int i =0; i < numPixels; i++) { -// long temp = (getByte(i) * NORMALIZATION_APROX[this.maxPixel]) >> NORMALIZATION_DENOMINATOR_POWER; -// putByte(i, (byte) (temp & 0xFF)); -// } - } - - } - - private void putBytes(byte[] buffer, long bufferOffset, long numBytes, long toMemoryOffset) { - for (long i = 0; i < numBytes; i++) { - putByte(toMemoryOffset + i, buffer[(int) (bufferOffset+i)]); - } - } - -// public void save(String path) { -// BufferedImage temp = new BufferedImage(width,height,BufferedImage.TYPE_BYTE_GRAY); -// byte[] buffer = new byte[width*height]; -// this.data.rewind(); -// getByte(buffer); -// temp.getRaster().setDataElements(0,0,width,height,buffer); -// try { -// ImageIO.write(temp, "jpg", new File(path)); -// } catch (IOException e) { -// } -// } - - private static long allocate(long size) { - long temp = unsafeInstance.allocateMemory(size); - unsafeInstance.setMemory(temp, size, (byte) 0); - return temp; - } - - private static void release(long pointer) { - unsafeInstance.freeMemory(pointer); - } - - public int getWidth() { - return width; - } - - public int getHeight() { - return height; - } - - public int get(int x, int y) { - return getByte(width*y + x) & 0xFF; - } - - public void dispose() { - release(this.pointer); - } - -// @Override -// protected void finalize() throws Throwable { -// Unsafe.getUnsafe().freeMemory(this.pointer); -// super.finalize(); -// } -} \ No newline at end of file diff --git a/src/main/java/com/pragone/jphash/image/radial/RadialHashAlgorithm.java b/src/main/java/com/pragone/jphash/image/radial/RadialHashAlgorithm.java index 5b95264..4f290eb 100644 --- a/src/main/java/com/pragone/jphash/image/radial/RadialHashAlgorithm.java +++ b/src/main/java/com/pragone/jphash/image/radial/RadialHashAlgorithm.java @@ -1,7 +1,6 @@ package com.pragone.jphash.image.radial; import com.pragone.jphash.image.SimpleGrayscaleImage; -import com.pragone.jphash.image.UnsafeSimpleGrayscaleImage; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; @@ -25,7 +24,7 @@ public class RadialHashAlgorithm { THETA_180 = new double[180]; TAN_THETA_180 = new double[180]; for (int i = 0; i < 180; i++) { - THETA_180[i] = i* Math.PI/180; + THETA_180[i] = i * Math.PI / 180; TAN_THETA_180[i] = Math.tan(THETA_180[i]); } } @@ -35,17 +34,13 @@ public static RadialHash getHash(String file) throws IOException { } public static RadialHash getHash(File file) throws IOException { - FileInputStream is = new FileInputStream(file); - try { + try (FileInputStream is = new FileInputStream(file)) { return getHash(is); - } finally { - is.close();; } } public static RadialHash getHash(InputStream inputStream) throws IOException { - BufferedImage img = ImageIO.read(inputStream); - return getHash(img); + return getHash(ImageIO.read(inputStream)); } public static RadialHash getHash(BufferedImage img) throws IOException { @@ -53,8 +48,7 @@ public static RadialHash getHash(BufferedImage img) throws IOException { Projections projections = calculate180Projections(grayscaleImage); // grayscaleImage.dispose(); Features features = calculateFeatures(projections); - RadialHash temp = calculateHash(features); - return temp; + return calculateHash(features); } private static RadialHash calculateHash(Features features) { @@ -70,25 +64,25 @@ private static RadialHash calculateHash(Features features) { double D_temp[] = new double[nb_coeffs]; double max = 0.0; double min = 0.0; - for (int k = 0;k max) max = D_temp[k]; if (D_temp[k] < min) min = D_temp[k]; } - for (int i=0;i height)?width:height; + int D = (width > height) ? width : height; int x_off = (width >> 1) + (width & 0x1); // round(width/2) but only with integer operations int y_off = (height >> 1) + (height & 0x1); // round(height/2) but only with integer operations - Projections projections = new Projections(N,D); + Projections projections = new Projections(N, D); int[][] ptr_radon_map = projections.projections; int[] nb_per_line = projections.nb_pix_perline; - for (int k=0;k= 0 ? 0.5 : -0.5)); - if ((yd + y_off >= 0)&&(yd + y_off < height) && (x < width)) { + for (int x = 0; x < D; x++) { + double y = alpha * (x - x_off); + int yd = (int) Math.floor(y + (y >= 0 ? 0.5 : -0.5)); + if ((yd + y_off >= 0) && (yd + y_off < height) && (x < width)) { ptr_radon_map[k][x] = img.get(x, yd + y_off); nb_per_line[k] += 1; } - if ((yd + x_off >= 0) && (yd + x_off < width) && (k != N/4) && (x < height)) { - ptr_radon_map[N/2-k][x] = img.get(yd + x_off, x); - nb_per_line[N/2-k] += 1; + if ((yd + x_off >= 0) && (yd + x_off < width) && (k != N / 4) && (x < height)) { + ptr_radon_map[N / 2 - k][x] = img.get(yd + x_off, x); + nb_per_line[N / 2 - k] += 1; } } } - int j= 0; - for (int k=3*N/4;k= 0 ? 0.5 : -0.5)); - if ((yd + y_off >= 0)&&(yd + y_off < height) && (x < width)){ + for (int x = 0; x < D; x++) { + double y = alpha * (x - x_off); + int yd = (int) Math.floor(y + (y >= 0 ? 0.5 : -0.5)); + if ((yd + y_off >= 0) && (yd + y_off < height) && (x < width)) { ptr_radon_map[k][x] = img.get(x, yd + y_off); nb_per_line[k] += 1; } - if ((y_off - yd >= 0)&&(y_off - yd=0)&&(2*y_off-x= 0) && (y_off - yd < width) && (2 * y_off - x >= 0) && (2 * y_off - x < height) && (k != 3 * N / 4)) { + ptr_radon_map[k - j][x] = img.get(-yd + y_off, -(x - y_off) + y_off); + nb_per_line[k - j] += 1; } } @@ -178,36 +172,42 @@ private static Projections calculate180Projections(SimpleGrayscaleImage img) { } public static double getSimilarity(RadialHash hash1, RadialHash hash2) { - - int N = hash1.getCoefficients().length; - byte[] x_coeffs = hash1.getCoefficients(); byte[] y_coeffs = hash2.getCoefficients(); - double r[] = new double[N]; + int N = x_coeffs.length; + double sumx = 0.0; double sumy = 0.0; - for (int i=0;i < N;i++){ + for (int i = 0; i < N; i++) { sumx += x_coeffs[i] & 0xFF; sumy += y_coeffs[i] & 0xFF; } - double meanx = sumx/N; - double meany = sumy/N; + + double meanx = sumx / N; + double meany = sumy / N; + double[] r = new double[N]; double max = 0; - for (int d=0;d max) max = r[d]; } - return max; //To change body of created methods use File | Settings | File Templates. + return max; } private static class Projections { diff --git a/src/main/java/com/pragone/jphash/pruningconetree/PruningConeTree2.java b/src/main/java/com/pragone/jphash/pruningconetree/PruningConeTree2.java index 0451060..5ed5c62 100644 --- a/src/main/java/com/pragone/jphash/pruningconetree/PruningConeTree2.java +++ b/src/main/java/com/pragone/jphash/pruningconetree/PruningConeTree2.java @@ -5,7 +5,9 @@ import com.pragone.jphash.index.SearchIndex; import com.pragone.jphash.index.Vector; -import java.util.*; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; diff --git a/src/test/java/com/pragone/jphash/image/radial/RadialHashAlgorithmTest.java b/src/test/java/com/pragone/jphash/image/radial/RadialHashAlgorithmTest.java index 2ee3e3c..430dda0 100644 --- a/src/test/java/com/pragone/jphash/image/radial/RadialHashAlgorithmTest.java +++ b/src/test/java/com/pragone/jphash/image/radial/RadialHashAlgorithmTest.java @@ -58,6 +58,15 @@ public void testEarthResizedDistance() throws IOException { ), 0.000001); } + @Test + public void testNotEarthDistance() throws IOException { + Assert.assertEquals(0.42, + RadialHashAlgorithm.getSimilarity( + getHashFor("earth1.jpg"), + getHashFor("not_earth.jpg") + ), 0.000001); + } + @Test public void testEarthCroppedDistance() throws IOException { Assert.assertEquals(EARTH_CROPPED_DISTANCE, diff --git a/src/test/resources/not_earth.jpg b/src/test/resources/not_earth.jpg new file mode 100644 index 0000000..9d9a5e0 Binary files /dev/null and b/src/test/resources/not_earth.jpg differ