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

org.omnifaces.utils.image.Images Maven / Gradle / Ivy

There is a newer version: 0.14
Show newest version
/*
 * Copyright 2021 OmniFaces
 *
 * 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
 *
 *     https://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.omnifaces.utils.image;

import static java.awt.RenderingHints.KEY_INTERPOLATION;
import static java.awt.RenderingHints.VALUE_INTERPOLATION_BILINEAR;
import static java.awt.Transparency.OPAQUE;
import static java.awt.image.BufferedImage.TYPE_INT_ARGB;
import static java.awt.image.BufferedImage.TYPE_INT_RGB;
import static java.lang.Math.max;
import static java.util.stream.IntStream.range;
import static javax.imageio.ImageIO.read;

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.util.Base64;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;

import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.stream.MemoryCacheImageOutputStream;

public final class Images {

	private Images() {
		//
	}

	public static BufferedImage toBufferedImage(byte[] content) throws IOException {
		return read(new ByteArrayInputStream(content));
	}

	public static byte[] toPng(BufferedImage image) throws IOException {
		var output = new ByteArrayOutputStream();
		ImageIO.write(image, "png", output);
		return output.toByteArray();
	}

	public static byte[] toJpg(BufferedImage image) throws IOException {
		// Start with a white layer to have images with an alpha layer handled correctly.
		var newBufferedImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB);
		newBufferedImage.createGraphics().drawImage(image, 0, 0, Color.WHITE, null);

		// Manually get the ImageWriter to be able to adjust quality
		var writer = ImageIO.getImageWritersBySuffix("jpg").next();
		var imageWriterParam = writer.getDefaultWriteParam();
		imageWriterParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
		imageWriterParam.setCompressionQuality(1f);

		var output = new ByteArrayOutputStream();
		writer.setOutput(new MemoryCacheImageOutputStream(output));
		writer.write(null, new IIOImage(newBufferedImage, null, null), imageWriterParam);
		writer.dispose();

		return output.toByteArray();
	}

	public static BufferedImage cropImage(BufferedImage image, int desiredWidth, int desiredHeight) {
		var cropHorizontally = image.getWidth() > desiredWidth;

		var x = cropHorizontally ? (image.getWidth() - desiredWidth) / 2 : 0;
		var y = cropHorizontally ? 0 : (image.getHeight() - desiredHeight) / 2;

		return image.getSubimage(x, y, desiredWidth, desiredHeight);
	}

	/*
	 * Examples of aspect ratios:
	 * 1:1 = 1.0 (will delegate to cropToSquareImage())
	 * 4:3 = 1.33333
	 * 3:2 = 1.5
	 * 16:9 = 1.77778
	 */
	public static BufferedImage cropImage(BufferedImage image, double desiredAspectRatio) {
		if (desiredAspectRatio == 1.0) {
			return cropToSquareImage(image);
		}

		var currentAspectRatio = image.getWidth() * 1.0 / image.getHeight();

		if (currentAspectRatio == desiredAspectRatio) {
			return image;
		}

		var cropHorizontally = currentAspectRatio > desiredAspectRatio;
		var desiredWidth = cropHorizontally ? (int) (image.getHeight() * desiredAspectRatio) : image.getWidth();
		var desiredHeight = cropHorizontally ? image.getHeight() : (int) (image.getWidth() / desiredAspectRatio);
		return cropImage(image, desiredWidth, desiredHeight);
	}

	public static BufferedImage cropToSquareImage(BufferedImage image) {
		var cropHorizontally = image.getWidth() > image.getHeight();
		var desiredSize = cropHorizontally ? image.getHeight() : image.getWidth();
		return cropImage(image, desiredSize, desiredSize);
	}

	public static BufferedImage progressiveBilinearDownscale(BufferedImage image, int desiredWidth, int desiredHeight) {
		var rescaledImage = image;

		while (rescaledImage.getWidth() > desiredWidth || rescaledImage.getHeight() > desiredHeight) {
			var nextWidth = max(rescaledImage.getWidth() / 2, desiredWidth);
			var nextHeight = max(rescaledImage.getHeight() / 2, desiredHeight);
			var nextScaledImage = new BufferedImage(nextWidth, nextHeight, image.getTransparency() == OPAQUE ? TYPE_INT_RGB : TYPE_INT_ARGB);
			var graphics = nextScaledImage.createGraphics();
			graphics.setRenderingHint(KEY_INTERPOLATION, VALUE_INTERPOLATION_BILINEAR);
			graphics.drawImage(rescaledImage, 0, 0, nextWidth, nextHeight, null);
			graphics.dispose();
			rescaledImage = nextScaledImage;
		}

		return rescaledImage;
	}

    public static BufferedImage grayscale(BufferedImage image) {
        var grayscale = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_BYTE_GRAY);
        var g = grayscale.getGraphics();
        g.drawImage(image, 0, 0, null);
        g.dispose();
        return grayscale;
    }

    /**
     * https://apiumhub.com/tech-blog-barcelona/introduction-perceptual-hashes-measuring-similarity/
     */
	public static String computePerceptualHash(BufferedImage image, int size, int base) {
        if (size < 8 || size > 32) {
            throw new IllegalArgumentException("size " + size + " must be between 8 and 32");
        }
        if (base != 2 && base != 10 && base != 16 && base != 32 && base != 36 && base != 64) {
            throw new IllegalArgumentException("base " + base + " must be 2, 10, 16, 32, 36 or 64");
        }

        var grayscale = grayscale(progressiveBilinearDownscale(image, size, size));
        Consumer> forEachPixel = perPixel -> range(0, size).forEach(x -> range(0, size).forEach(y -> perPixel.accept(grayscale.getRGB(x, y) & 0xFF)));
        var totalPixelValue = new AtomicInteger();
        forEachPixel.accept(pixel -> totalPixelValue.addAndGet(pixel));
        var averagePixelValue = totalPixelValue.get() / (size * size);
        var perceptualHash = new StringBuilder();
        forEachPixel.accept(pixel -> perceptualHash.append(pixel > averagePixelValue ? "1" : "0"));
        var hash = perceptualHash.toString();

        if (base == 2) {
            return hash;
        } else {
            var integer = new BigInteger(hash, 2);

            if (base == 64) {
                return Base64.getEncoder().encodeToString(integer.toByteArray());
            } else {
                return integer.toString(base);
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy