All Downloads are FREE. Search and download functionalities are using the official Maven repository.

eu.ciechanowiec.sling.rocket.asset.image.ComparableImage Maven / Gradle / Ivy

package eu.ciechanowiec.sling.rocket.asset.image;

import dev.brachtendorf.jimagehash.hash.Hash;
import dev.brachtendorf.jimagehash.hashAlgorithms.HashingAlgorithm;
import dev.brachtendorf.jimagehash.hashAlgorithms.PerceptiveHash;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.IOException;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;

/**
 * Represents an image that can be compared with other images.
 */
@Slf4j
@ToString
@EqualsAndHashCode
@SuppressWarnings("WeakerAccess")
public class ComparableImage {

    @SuppressWarnings({"FieldNamingConvention", "PMD.LongVariable"})
    private static final double DEFAULT_MAX_NORMALIZED_HAMMING_DISTANCE = 0.1; // Optimal after tests

    @Getter
    private final File fileWithImage;
    @ToString.Exclude
    @EqualsAndHashCode.Exclude
    private final CompletableFuture> futureHash;

    /**
     * Constructs an instance of this class.
     * @param fileWithImage {@link File} with the underlying image; the client code must ensure that the {@link File}
     *                      is a valid image file since for other files there is no guarantee that they will be
     *                      processed correctly
     */
    public ComparableImage(File fileWithImage) {
        this.fileWithImage = fileWithImage;
        futureHash = CompletableFuture.supplyAsync(() -> computeHash(fileWithImage));
    }

    private Optional computeHash(File file) {
        int bitResolution = BitResolution.MAX_256.value();
        HashingAlgorithm hashingAlgorithm = new PerceptiveHash(bitResolution);
        log.trace("Trying to hash {}", file);
        try {
            Hash hash = hashingAlgorithm.hash(file);
            log.trace("Hashed {} to {}", file, hash);
            return Optional.of(hash);
        } catch (IOException | IllegalArgumentException exception) {
            String errorMessage = String.format("Unable to hash %s", file);
            log.trace(errorMessage, exception);
            return Optional.empty();
        }
    }

    private Optional lastComputedHash() {
        return futureHash.join();
    }

    ComparableImage biggerOrSame(ComparableImage comparedImage) {
        long thisSize = fileWithImage.length();
        @SuppressWarnings("AccessingNonPublicFieldOfAnotherObject")
        long thatSize = comparedImage.fileWithImage.length();
        if (thatSize > thisSize) {
            return comparedImage;
        } else {
            return this;
        }
    }

    boolean isSimilar(ComparableImage comparedImage) {
        log.trace("Comparing if {} and {} are similar", this, comparedImage);
        Optional firstHashNullable = this.lastComputedHash();
        Optional secondHashNullable = comparedImage.lastComputedHash();
        boolean bothHashesExist = firstHashNullable.isPresent() && secondHashNullable.isPresent();
        if (!bothHashesExist) {
            log.trace("{} and {} are not similar", this, comparedImage);
            return false;
        }
        Hash firstHash = firstHashNullable.get();
        Hash secondHash = secondHashNullable.get();
        double normalizedHammingDistance = firstHash.normalizedHammingDistance(secondHash);
        boolean areSimilar = normalizedHammingDistance <= DEFAULT_MAX_NORMALIZED_HAMMING_DISTANCE;
        log.trace(
                "Distance between {} and {} is {}. Max allowed distance for similarity is {}. "
              + "Are images similar? Answer: {}", this, comparedImage, normalizedHammingDistance,
                DEFAULT_MAX_NORMALIZED_HAMMING_DISTANCE, areSimilar
        );
        return areSimilar;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy