colorfingerprint.java

来自「dump3 morpheus 0.2.9 src」· Java 代码 · 共 420 行

JAVA
420
字号
/**
 * DuMP3 version morpheus_0.2.9 - a duplicate/similar file finder in Java<BR>
 * Copyright 2005 Alexander Gr&auml;sser<BR>
 * All Rights Reserved, http://dump3.sourceforge.net/<BR>
 * <BR>
 * This file is part of DuMP3.<BR>
 * <BR>
 * DuMP3 is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any later version.<BR>
 * <BR>
 * DuMP3 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. See the GNU General Public License for more details.<BR>
 * <BR>
 * You should have received a copy of the GNU General Public License along with DuMP3; if not, write to the Free Software Foundation, Inc., 51 Franklin St,
 * Fifth Floor, Boston, MA 02110-1301 USA
 */
package net.za.grasser.duplicate.fingerprint;

import java.awt.Transparency;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.imageio.ImageIO;
import net.za.grasser.duplicate.file.FingerprintFile;
import net.za.grasser.duplicate.file.Status;
import net.za.grasser.duplicate.fingerprint.configure.ColorFingerprintConfig;
import net.za.grasser.duplicate.fingerprint.configure.ConfigFactory;
import net.za.grasser.duplicate.util.HexArray;
import org.apache.log4j.Logger;

/**
 * REF: <a href="http://www.vldb.org/conf/2001/P221.pdf">An Extendible Hash for Multi-Precision Similarity Querying of Image Databases</a>, S. Lin, M. T. Ozsu,
 * V. Oria, R. Ng, 2001<BR>
 * 
 * @author <a href="http://sourceforge.net/sendmessage.php?touser=733840">pyropunk at sourceforge dot net</a>
 * @version $Revision: 1.13 $
 * @modelguid {AF9A68AD-CFA1-4F86-AF1F-CE02CB6EAC57}
 */
public class ColorFingerprint extends AbstractFingerprint {
  /**
   * <code>log</code> ColorFingerprint -
   */
  private static final Logger log = Logger.getLogger(ColorFingerprint.class);

  /**
   * This class defines an entry in the rgb picture list
   * 
   * @author <a href="http://sourceforge.net/sendmessage.php?touser=733840">pyropunk at sourceforge dot net</a>
   */
  static class Entry {
    /**
     * TODO greyscale comparison
     * 
     * @param rgb1
     * @param rgb2
     * @return distance between 2 RGB colors
     */
    static double distance(final int rgb1, final int rgb2) {
      final int r = (rgb1 >> 16 & 0xFF) - (rgb2 >> 16 & 0xFF);
      final int g = (rgb1 >> 8 & 0xFF) - (rgb2 >> 8 & 0xFF);
      final int b = (rgb1 & 0xFF) - (rgb2 & 0xFF);
      return Math.sqrt(r * r + g * g + b * b);
    }

    /**
     * <code>width</code> Entry -
     */
    byte width;
    /**
     * <code>height</code> Entry -
     */
    byte height;
    /**
     * <code>rgb</code> Entry -
     */
    int[] rgb;

    /**
     * @param pW
     * @param pH
     * @param pRgb
     */
    Entry(final int pW, final int pH, final int[] pRgb) {
      width = (byte)pW;
      height = (byte)pH;
      rgb = pRgb;
    }

    /**
     * @param arg0
     * @return average distance between 2 color matixes
     */
    public double compareTo(final Object arg0) {
      if (arg0 instanceof Entry) {
        final Entry ent = (Entry)arg0;
        if (ent.height == height && ent.width == width || ent.height == width && ent.width == height) {
          if (width == 1 && height == 1) {
            return distance(rgb[0], ent.rgb[0]);
          }
          // test 2 rotations
          double r1 = 0.0;
          double r2 = 0.0;
          if (ent.height == height && ent.width == width) {
            for (int i = 0; i < rgb.length; i++) {
              r1 += distance(rgb[i], ent.rgb[i]);
              r2 += distance(rgb[i], ent.rgb[rgb.length - i - 1]);
            }
          } else {
            for (int y = 0; y < height; y++) {
              for (int x = 0; x < width; x++) {
                r1 += distance(rgb[y * width + x], ent.rgb[x * height + height - y - 1]);
                r2 += distance(rgb[y * width + x], ent.rgb[(width - x - 1) * height + y]);
              }
            }
          }
          r1 = Math.min(r1, r2);
          if (width == height) {
            // 2 more rotations must be tested
            double r3 = 0.0;
            double r4 = 0.0;
            for (int y = 0; y < width; y++) {
              for (int x = 0; x < width; x++) {
                r3 += distance(rgb[y * width + x], ent.rgb[x * width + width - 1 - y]);
                r4 += distance(rgb[y * width + x], ent.rgb[(width - x - 1) * width + y]);
              }
            }
            r1 = Math.min(r1, Math.min(r3, r4));
          }
          // average
          return r1 / rgb.length;
        }
        throw new IllegalArgumentException("Invalid width and height");
      }
      throw new IllegalArgumentException("Incorrect Type");
    }
  }

  static {
    final Set<String> set = new HashSet<String>();
    final String[] as = ImageIO.getWriterFormatNames();
    for (final String element : as) {
      set.add(element.toUpperCase());
    }
    String s = "[";
    for (final String lString : set) {
      s += lString + ", ";
    }
    if (s.endsWith(", ")) {
      s = s.substring(0, s.length() - 2);
    }
    s += ']';
    log.info("Supported file formats:" + s);
  }
  /**
   * <code>config</code> ColorFingerprint -
   */
  private static final ColorFingerprintConfig config = (ColorFingerprintConfig)ConfigFactory.getConfig(ColorFingerprint.class);
  /**
   * <code>MAX_DIST</code> ColorFingerprint - maximum distance in 24bit RGB is from black to white
   */
  private static final double MAX_DIST = Math.sqrt(255 * 255 * 3);

  /**
   * @param pIn
   * @return ArrayList of Entry
   */
  private static ArrayList<Entry> makeFingerlistFromPrint(final byte[] pIn) {
    final ArrayList<Entry> ret = new ArrayList<Entry>(4);
    int j = 0;
    while (j < pIn.length) {
      final int w = pIn[j++];
      final int h = pIn[j++];
      if (w == 0 || h == 0) {
        break;
      }
      ret.add(new Entry(w, h, HexArray.makeInts(pIn, j, w * h * 3, null, 0, 3)));
      j += w * h * 3;
    }
    return ret;
  }

  /**
   * @param pIn
   * @return fingerprint
   */
  private static byte[] makeFingerprintFromList(final ArrayList<Entry> pIn) {
    int j = 0;
    final byte[] fp = new byte[((int)Math.pow(4.0, config.getSubdivide()) - 1 + (config.getSubdivide() << 1))];
    for (int i = 0; i < config.getSubdivide(); i++) {
      final Entry e = pIn.get(i);
      fp[j++] = e.width;
      fp[j++] = e.height;
      HexArray.makeBytes(e.rgb, 0, e.rgb.length, fp, j, 3);
      j += e.rgb.length * 3;
    }
    return fp;
  }

  /**
   * <code>scaled_rgbs</code> ColorFingerprint - ArrayList of Entry
   */
  private ArrayList<Entry> scaled_rgbs = null;

  /**
   * @param fi FingerprintFile
   * @modelguid {1953E44F-41DC-4AEF-84B8-13F9C64ACCFD}
   */
  public ColorFingerprint(final FingerprintFile fi) {
    super(fi);
  }

  /**
   * This method adds additional info to the FingerprintFile
   */
  @Override
  public void addInfo() {
    super.addInfo();
    try {
      final BufferedImage image = ImageIO.read(new File(getFile().getPath()));
      final Map<String, String> map = getFile().getInfo();
      map.put("width", String.valueOf(image.getWidth()));
      map.put("height", String.valueOf(image.getHeight()));
      map.put("color depth", String.valueOf(image.getColorModel().getPixelSize()));
      switch (image.getTransparency()) {
        case Transparency.BITMASK:
          map.put("transparency", "bit mask");
          break;
        case Transparency.TRANSLUCENT:
          map.put("transparency", "translucent");
          break;
        case Transparency.OPAQUE:
        default:
          map.put("transparency", "opaque");
          break;
      }
      final String[] props = image.getPropertyNames();
      if (props != null) {
        for (final String lString : props) {
          map.put(lString, image.getProperty(lString).toString());
        }
      }
    } catch (final Throwable t) {
      log.error("Could not get info", t);
    }
  }

  /**
   * @param pPF
   * @return ratio of similarity
   */
  private float compare(final ColorFingerprint pPF) {
    try {
      double dist = 0.0;
      for (int i = 0; i < config.getSubdivide(); i++) {
        final Entry a = scaled_rgbs.get(i);
        final Entry b = pPF.scaled_rgbs.get(i);
        // different size fingerprints are ok - return last calculated distance
        if (a == null || b == null) {
          return (float)(100.0 - dist * 100.0 / MAX_DIST);
        }
        dist = a.compareTo(b);
        if (dist > (100.0f - config.getSimilarityThreshhold()) / 100.0f * MAX_DIST) {
          return (float)(100.0 - dist * 100.0 / MAX_DIST);
          // return 0;
        }
      }
      // return (float)(100 - (dist * (distance / 100.0)));
      return (float)(100.0 - dist * 100.0 / MAX_DIST);
    } catch (final IllegalArgumentException iae) {
      return 0.0f;
    }
  }

  /**
   * @see net.za.grasser.duplicate.fingerprint.AbstractFingerprint#getClassName()
   */
  @Override
  public String getClassName() {
    String cls = super.getClassName();
    switch (config.getScalingAlgorithm()) {
      default:
      case AffineTransformOp.TYPE_NEAREST_NEIGHBOR:
        cls += "-NN";
        break;
      case AffineTransformOp.TYPE_BILINEAR:
        cls += "-BL";
        break;
      case AffineTransformOp.TYPE_BICUBIC:
        cls += "-BC";
        break;
    }
    return cls + '-' + (config.isKeepaspect() ? "ASP" : "STR");
  }

  /**
   * @see net.za.grasser.duplicate.fingerprint.AbstractFingerprint#getSimilarityThreshhold()
   */
  @Override
  public float getSimilarityThreshhold() {
    return config.getSimilarityThreshhold();
  }

  /**
   * @see net.za.grasser.duplicate.fingerprint.AbstractFingerprint#makeFingerprintFromFile()
   */
  @Override
  protected void makeFingerprintFromFile() {
    try {
      final BufferedImage image = ImageIO.read(new File(getFile().getPath()));
      preRead();
      int parts = 0;
      for (int i = 0; i < config.getSubdivide(); i++) {
        parts = (int)Math.pow(2.0, i);
        final BufferedImage sc = scale(image, parts);
        final int[] argbs = new int[(sc.getWidth() * sc.getHeight())];
        sc.getRGB(0, 0, sc.getWidth(), sc.getHeight(), argbs, 0, sc.getWidth());
        scaled_rgbs.add(new Entry(sc.getWidth(), sc.getHeight(), argbs));
      }
      // make the byte [] fingerprint
      postRead();
    } catch (final Exception e) {
      getFile().setStatus(Status.FILE_CORRUPT);
      fingerprint = null;
      log.warn("error while reading file [" + getFile().getPath() + "]", e);
    }
  }

  /**
   * @param fi AbstractFingerprint
   * @return boolean
   * @modelguid {BC0953BE-AD94-4207-A1B8-E5F7FAD5DCE7}
   */
  @Override
  public float matches(final AbstractFingerprint fi) {
    final FingerprintFile ff1 = getFile();
    final FingerprintFile ff2 = fi.getFile();
    if (ff1.getStatus() != Status.FILE_OK && ff1.getStatus() != Status.FILE_SIGNATURE_MISMATCH || ff2.getStatus() != Status.FILE_OK
        && ff2.getStatus() != Status.FILE_SIGNATURE_MISMATCH) {
      return 0.0f;
    }
    if (ff1.getLength() == 0L && ff2.getLength() == 0L) {
      return 100.0f;
    }
    if (fi instanceof ColorFingerprint) {
      final ColorFingerprint pf = (ColorFingerprint)fi;
      // try to read the files
      getFingerprint();
      pf.getFingerprint();
      if ((ff1.getStatus() == Status.FILE_OK || ff1.getStatus() == Status.FILE_SIGNATURE_MISMATCH)
          && (ff2.getStatus() == Status.FILE_OK || ff2.getStatus() == Status.FILE_SIGNATURE_MISMATCH)) {
        if (scaled_rgbs == null) {
          scaled_rgbs = makeFingerlistFromPrint(getFingerprint());
        }
        if (pf.scaled_rgbs == null) {
          pf.scaled_rgbs = makeFingerlistFromPrint(pf.getFingerprint());
        }
        return compare(pf);
      }
    }
    return 0.0f;
  }

  /**
   * @see net.za.grasser.duplicate.fingerprint.AbstractFingerprint#postRead()
   */
  @Override
  protected void postRead() {
    fingerprint = makeFingerprintFromList(scaled_rgbs);
  }

  /**
   * @see net.za.grasser.duplicate.fingerprint.AbstractFingerprint#preRead()
   */
  @Override
  protected void preRead() {
    scaled_rgbs = new ArrayList<Entry>(config.getSubdivide());
  }

  /**
   * @param bufiSource
   * @param parts
   * @return a scaled images
   */
  private BufferedImage scale(final BufferedImage bufiSource, final int parts) {
    double scaleX = 0.0;
    double scaleY = 0.0;
    final int w = bufiSource.getWidth();
    final int h = bufiSource.getHeight();
    if (config.isKeepaspect() && parts > 2) {
      if (w < h) {
        scaleY = (double)parts / (double)h;
        scaleX = scaleY;
      } else {
        scaleX = (double)parts / (double)w;
        scaleY = scaleX;
      }
    } else {
      scaleX = (double)parts / (double)w;
      scaleY = (double)parts / (double)h;
    }
    final AffineTransform tx = new AffineTransform();
    tx.scale(scaleX, scaleY);
    final AffineTransformOp op = new AffineTransformOp(tx, config.getScalingAlgorithm());
    return op.filter(bufiSource, null);
  }

  /**
   * @see net.za.grasser.duplicate.fingerprint.AbstractFingerprint#update(byte[], int)
   */
  @Override
  protected void update(final byte[] pBuffer, final int pLength) {
    // not used since we are doing our own reading
  }
}

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?