/* * Copyright 2011 Benny Colyn * * 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 org.github.bcolyn.jmediahash.images; import com.mortennobel.imagescaling.ResampleOp; import org.ejml.data.DenseMatrix64F; import org.ejml.ops.CommonOps; import org.github.bcolyn.jmediahash.util.lucene.OpenBitSet; import java.awt.color.ColorSpace; import java.awt.image.BufferedImage; import java.awt.image.ColorConvertOp; import java.awt.image.Raster; import java.util.Arrays; import static java.lang.Math.*; import static java.util.Arrays.fill; import static org.ejml.ops.CommonOps.mult; import static org.ejml.ops.CommonOps.multTransB; public class DCTImageHashAlgorithm implements ImageHashAlgorithm { public static final int INTERNAL_SIZE = 32; private final static DenseMatrix64F dct = dctMatrix(INTERNAL_SIZE); public static final int INTERNAL_IMAGE_TYPE = BufferedImage.TYPE_USHORT_GRAY; @Override public BufferedImage createResizedCopy(BufferedImage originalImage) { ResampleOp resampleOp = new ResampleOp(INTERNAL_SIZE, INTERNAL_SIZE); ColorConvertOp colorConvertOp = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), null); BufferedImage result = new BufferedImage(INTERNAL_SIZE, INTERNAL_SIZE, INTERNAL_IMAGE_TYPE); colorConvertOp.filter(resampleOp.filter(originalImage, null), result); return result; } @Override public OpenBitSet getHash(BufferedImage image) { final BufferedImage smallSize; if (image.getWidth() == INTERNAL_SIZE && image.getHeight() == INTERNAL_SIZE && image.getType() == INTERNAL_IMAGE_TYPE) { // client has prepared the image, we can use this directly smallSize = image; } else { // do out own prepping smallSize = createResizedCopy(image); } DenseMatrix64F imageMatrix = getImageMatrix(smallSize); return doHash(imageMatrix); } @Override public OpenBitSet getHash(double[][] imageMatrix) { assert (imageMatrix.length == INTERNAL_SIZE); assert (imageMatrix[0].length == INTERNAL_SIZE); return doHash(new DenseMatrix64F(imageMatrix)); } private OpenBitSet doHash(DenseMatrix64F imageMatrix) { DenseMatrix64F result = doDct(imageMatrix); double[] sample = extractSample(result, 1, 8); return hashSample(sample); } DenseMatrix64F doDct(DenseMatrix64F imageMatrix) { DenseMatrix64F intermediate = new DenseMatrix64F(INTERNAL_SIZE, INTERNAL_SIZE); mult(dct, imageMatrix, intermediate); DenseMatrix64F result = new DenseMatrix64F(INTERNAL_SIZE, INTERNAL_SIZE); multTransB(intermediate, dct, result); return result; } double[] extractSample(DenseMatrix64F matrix, int offset, int size) { DenseMatrix64F extract = CommonOps.extract(matrix, offset, offset + size, offset, offset + size); return extract.data; } private DenseMatrix64F getImageMatrix(BufferedImage smallSize) { Raster data = smallSize.getData(); int width = data.getWidth(); int height = data.getHeight(); double[] pixels = data.getPixels(0, 0, width, height, new double[width * height]); return new DenseMatrix64F(height, width, true, pixels); } private OpenBitSet hashSample(double[] sample) { double median = findMedian(sample); OpenBitSet hash = new OpenBitSet(); for (int i = 0, sampleLength = sample.length; i < sampleLength; i++) { double current = sample[i]; if (current > median) { hash.set(i); } } return hash; } private double findMedian(double[] sample) { double[] copy = Arrays.copyOf(sample, sample.length); Arrays.sort(copy); double m1 = copy[copy.length / 2]; double m2 = copy[(copy.length / 2) - 1]; return (m2 + m1) / 2; } private static DenseMatrix64F dctMatrix(final int N) { DenseMatrix64F dctMatrix = new DenseMatrix64F(N, N); fill(dctMatrix.data, 1 / sqrt(N)); final double c1 = sqrt(2.0 / N); for (int x = 0; x < N; x++) { for (int y = 1; y < N; y++) { double value = c1 * cos((PI / 2 / N) * y * (2 * x + 1)); dctMatrix.set(x, y, value); } } return dctMatrix; } }