diff --git a/src/one/webp/PHash.java b/src/one/webp/PHash.java new file mode 100644 index 0000000..928f917 --- /dev/null +++ b/src/one/webp/PHash.java @@ -0,0 +1,33 @@ +/* + * Copyright 2016 Odnoklassniki Ltd, Mail.Ru Group + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package one.webp; + +import java.nio.file.Files; +import java.nio.file.Paths; + +public class PHash { + + public static void main(String[] args) throws Exception { + if (args.length == 0) { + throw new IllegalArgumentException("Missing input file name"); + } + + byte[] src = Files.readAllBytes(Paths.get(args[0])); + long hash = WebP.phash(src); + System.out.println(Long.toHexString(hash)); + } +} diff --git a/src/one/webp/WebP.java b/src/one/webp/WebP.java index 52de173..241587d 100644 --- a/src/one/webp/WebP.java +++ b/src/one/webp/WebP.java @@ -26,8 +26,13 @@ public static byte[] convert(byte[] src, Params params) throws WebPException { return convert1(src, params.longValue()); } + public static long phash(byte[] src) { + return phash0(src); + } + private static native int convert0(byte[] src, byte[] dst, long options); private static native byte[] convert1(byte[] src, long options); + private static native long phash0(byte[] src); static { System.loadLibrary("onewebp"); diff --git a/src/one/webp/native/jniwrapper.c b/src/one/webp/native/jniwrapper.c index 73e1678..19d8dc1 100644 --- a/src/one/webp/native/jniwrapper.c +++ b/src/one/webp/native/jniwrapper.c @@ -94,3 +94,14 @@ Java_one_webp_WebP_convert1(JNIEnv* env, jobject cls, jbyteArray srcArray, jlong return array; } } + +JNIEXPORT jlong JNICALL +Java_one_webp_WebP_phash0(JNIEnv* env, jobject cls, jbyteArray srcArray) { + jint srcSize = (*env)->GetArrayLength(env, srcArray); + jbyte* src = (*env)->GetPrimitiveArrayCritical(env, srcArray, NULL); + + jlong result = image_phash(src, srcSize); + + (*env)->ReleasePrimitiveArrayCritical(env, srcArray, src, JNI_ABORT); + return result; +} diff --git a/src/one/webp/native/onewebp.c b/src/one/webp/native/onewebp.c index 7ef7737..e10e288 100644 --- a/src/one/webp/native/onewebp.c +++ b/src/one/webp/native/onewebp.c @@ -14,14 +14,19 @@ * limitations under the License. */ +#define _USE_MATH_DEFINES + #include #include +#include #include "turbojpeg.h" #include "png.h" #include "webp/encode.h" #include "onewebp.h" +#define HASH_SIZE 64 + typedef struct { unsigned char* ptr; const unsigned char* limit; @@ -53,7 +58,7 @@ static int webp_writer(const uint8_t* data, size_t dataSize, const WebPPicture* } -int decompress_jpeg(unsigned char* src, unsigned long srcSize, RawImage* rawImage, Params params) { +static int decompress_jpeg(unsigned char* src, unsigned long srcSize, RawImage* rawImage, Params params) { tjhandle handle = tjInitDecompress(); int width, height, subsamp, colorspace; @@ -84,7 +89,7 @@ int decompress_jpeg(unsigned char* src, unsigned long srcSize, RawImage* rawImag return 0; } -int decompress_png(unsigned char* src, unsigned long srcSize, RawImage* rawImage, Params params) { +static int decompress_png(unsigned char* src, unsigned long srcSize, RawImage* rawImage, Params params) { png_image png = { NULL }; png.version = PNG_IMAGE_VERSION; @@ -105,19 +110,27 @@ int decompress_png(unsigned char* src, unsigned long srcSize, RawImage* rawImage return 0; } +int decompress_image(unsigned char* src, unsigned long srcSize, RawImage* rawImage, Params params) { + if (srcSize >= 4 && src[1] == 'P' && src[2] == 'N' && src[3] == 'G') { + return decompress_png(src, srcSize, rawImage, params); + } else { + return decompress_jpeg(src, srcSize, rawImage, params); + } +} + int compress_webp(unsigned char* dst, unsigned long dstSize, RawImage* rawImage, Params params) { - WebPConfig config; - WebPPicture picture; Buffer buffer = { dst, dst + dstSize }; int maxWidth = params.maxWidth ? params.maxWidth : WEBP_MAX_DIMENSION; int maxHeight = params.maxHeight ? params.maxHeight : WEBP_MAX_DIMENSION; + WebPConfig config; WebPConfigInit(&config); config.quality = (float)params.quality; config.method = params.compression; config.lossless = params.lossless; config.thread_level = params.multithreaded; + WebPPicture picture; WebPPictureInit(&picture); picture.use_argb = 1; picture.colorspace = WEBP_YUV420; @@ -135,14 +148,17 @@ int compress_webp(unsigned char* dst, unsigned long dstSize, RawImage* rawImage, maxWidth = 0; } if (!WebPPictureRescale(&picture, maxWidth, maxHeight)) { + WebPPictureFree(&picture); return ERR_TRANSFORM; } } if (!WebPEncode(&config, &picture)) { + WebPPictureFree(&picture); return ERR_COMPRESS; } + WebPPictureFree(&picture); return (int)(buffer.ptr - dst); } @@ -150,13 +166,7 @@ int convert_to_webp(unsigned char* src, unsigned long srcSize, unsigned char* dst, unsigned long dstSize, Params params) { RawImage rawImage; - int result; - - if (srcSize >= 4 && src[1] == 'P' && src[2] == 'N' && src[3] == 'G') { - result = decompress_png(src, srcSize, &rawImage, params); - } else { - result = decompress_jpeg(src, srcSize, &rawImage, params); - } + int result = decompress_image(src, srcSize, &rawImage, params); if (result == 0) { result = compress_webp(dst, dstSize, &rawImage, params); @@ -165,3 +175,102 @@ int convert_to_webp(unsigned char* src, unsigned long srcSize, return result; } + +static void argb_to_grayscale(unsigned int* src, int stride, float* grayscale) { + int i, j; + for (i = 0; i < HASH_SIZE; i++) { + for (j = 0; j < HASH_SIZE; j++) { + unsigned int argb = src[i * stride + j]; + unsigned int r = (argb >> 16) & 0xff; + unsigned int g = (argb >> 8) & 0xff; + unsigned int b = (argb) & 0xff; + grayscale[i * HASH_SIZE + j] = r * 0.299f + g * 0.587f + b * 0.114f; + } + } +} + +static void dct_vector(float* vector) { + float transformed[HASH_SIZE]; + int i, j; + for (i = 0; i < HASH_SIZE; i++) { + float sum = 0; + for (j = 0; j < HASH_SIZE; j++) { + sum += vector[j] * cos(i * M_PI * (j + 0.5) / HASH_SIZE); + } + sum *= sqrt(2.0f / HASH_SIZE); + transformed[i] = (i == 0) ? sum * M_SQRT1_2 : sum; + } + memcpy(vector, transformed, sizeof(transformed)); +} + +static void dct_8x8(float* pixels, float* transformed) { + int i, j; + for (i = 0; i < HASH_SIZE; i++) { + float* row = &pixels[i * HASH_SIZE]; + dct_vector(row); + } + + for (i = 0; i < 8; i++) { + float col[HASH_SIZE]; + for (j = 0; j < HASH_SIZE; j++) { + col[j] = pixels[j * HASH_SIZE + i]; + } + dct_vector(col); + for (j = 0; j < 8; j++) { + transformed[i * 8 + j] = col[j]; + } + } +} + +static float median(float* pixels) { + float sum = 0; + int i; + for (i = 0; i < 64; i++) { + sum += pixels[i]; + } + return sum / 64; +} + +static unsigned long long bitmask(float* pixels, float median) { + unsigned long long mask = 0; + int i; + for (i = 0; i < 64; i++) { + if (pixels[i] > median) { + mask |= 1ULL << i; + } + } + return mask; +} + +unsigned long long image_phash(unsigned char* src, unsigned long srcSize) { + RawImage rawImage; + Params params = {HASH_SIZE, HASH_SIZE, 0, 0, 1, 0, 0}; + if (decompress_image(src, srcSize, &rawImage, params)) { + return 0; + } + + WebPPicture picture; + WebPPictureInit(&picture); + picture.use_argb = 1; + picture.width = rawImage.width; + picture.height = rawImage.height; + picture.argb = (uint32_t*)rawImage.argb; + picture.argb_stride = rawImage.width; + + if (!WebPPictureRescale(&picture, HASH_SIZE, HASH_SIZE)) { + WebPPictureFree(&picture); + free(rawImage.argb); + return 0; + } + + float grayscale[HASH_SIZE * HASH_SIZE]; + argb_to_grayscale(picture.argb, picture.argb_stride, grayscale); + WebPPictureFree(&picture); + free(rawImage.argb); + + float transformed[64]; + dct_8x8(grayscale, transformed); + + float m = median(transformed); + return bitmask(transformed, m); +} diff --git a/src/one/webp/native/onewebp.h b/src/one/webp/native/onewebp.h index 0ca15f9..d02a1f3 100644 --- a/src/one/webp/native/onewebp.h +++ b/src/one/webp/native/onewebp.h @@ -38,10 +38,11 @@ typedef struct { } RawImage; -int decompress_jpeg(unsigned char* src, unsigned long srcSize, RawImage* rawImage, Params params); -int decompress_png(unsigned char* src, unsigned long srcSize, RawImage* rawImage, Params params); +int decompress_image(unsigned char* src, unsigned long srcSize, RawImage* rawImage, Params params); int compress_webp(unsigned char* dst, unsigned long dstSize, RawImage* rawImage, Params params); int convert_to_webp(unsigned char* src, unsigned long srcSize, unsigned char* dst, unsigned long dstSize, Params params); + +unsigned long long image_phash(unsigned char* src, unsigned long srcSize);