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

com.github.bloodshura.x.assets.image.process.ImageProcessor Maven / Gradle / Ivy

/*
 * Copyright (c) 2013-2018, João Vitor Verona Biazibetti - All Rights Reserved
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see .
 *
 * https://www.github.com/BloodShura
 */

package com.github.bloodshura.x.assets.image.process;

import com.github.bloodshura.x.assets.image.Image;
import com.github.bloodshura.x.assets.image.exception.ImageException;
import com.github.bloodshura.x.assets.image.process.filter.Filter;
import com.github.bloodshura.x.assets.image.process.filter.FilterMask;
import com.github.bloodshura.x.assets.image.process.filter.MaskedFilter;
import com.github.bloodshura.x.assets.util.ImageWorker;
import com.github.bloodshura.x.color.Color;
import com.github.bloodshura.x.geometry.bounds.IBounds;
import com.github.bloodshura.x.geometry.position.IPosition;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.awt.Graphics2D;
import java.awt.color.ColorSpace;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.awt.image.ColorConvertOp;

public class ImageProcessor {
	protected BufferedImage buffer;
	protected boolean needsReferenceUpdate;
	private boolean hasChanges;
	private final Image image;
	private boolean isProcessing;

	public ImageProcessor(@Nonnull Image image) {
		this.hasChanges = false;
		this.isProcessing = false;
		this.image = image;
	}

	@Nonnull
	public ImageProcessor applyFilter(@Nonnull Filter filter) {
		filter.applyFilter(getBuffer());

		this.hasChanges = true;

		return this;
	}

	@Nonnull
	public ImageProcessor applyFilter(@Nonnull MaskedFilter filter, @Nullable FilterMask mask) {
		filter.applyFilter(getBuffer(), mask != null ? mask : (x, y) -> true);

		this.hasChanges = true;

		return this;
	}

	@Nonnull
	public ImageProcessor finish() throws ImageException {
		if (!isProcessing()) {
			throw new ImageException("Failed to end image processor: Not processing");
		}

		if (needsReferenceUpdate && hasChanges()) {
			getImage().setImage(getBuffer());
		}

		this.buffer = null;
		this.hasChanges = false;
		this.isProcessing = false;
		this.needsReferenceUpdate = false;

		return this;
	}

	@Nonnull
	public ImageProcessor flip(@Nonnull FlipMode mode) {
		AffineTransform transform;

		switch (mode.ordinal()) {
			case 0:
				transform = AffineTransform.getScaleInstance(-1, 1);
				transform.translate(-getBuffer().getWidth(), 0);

				break;
			case 1:
				transform = AffineTransform.getScaleInstance(1, -1);
				transform.translate(0, -getBuffer().getHeight());

				break;
			default:
				transform = AffineTransform.getScaleInstance(-1, -1);
				transform.translate(-getBuffer().getWidth(), -getBuffer().getHeight());

				break;
		}

		AffineTransformOp operation = new AffineTransformOp(transform, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
		BufferedImage image = ImageWorker.cloneImage(getBuffer());

		operation.filter(getBuffer(), image);

		this.buffer = image;
		this.hasChanges = true;
		this.needsReferenceUpdate = true;

		return this;
	}

	@Nonnull
	public BufferedImage getBuffer() {
		return buffer;
	}

	@Nonnull
	public Image getImage() {
		return image;
	}

	public boolean hasChanges() {
		return hasChanges;
	}

	@Nonnull
	public ImageProcessor insert(@Nonnull PaintImage... images) {
		if (images.length > 0) {
			Graphics2D graphics = getBuffer().createGraphics();

			for (PaintImage target : images) {
				Image image = target.getImage();

				graphics.drawImage(image.getImage(), target.getX(), target.getY(), target.getWidth(), target.getHeight(), null);
			}

			graphics.dispose();

			this.hasChanges = true;
		}

		return this;
	}

	public boolean isProcessing() {
		return isProcessing;
	}

	@Nonnull
	public ImageProcessor resize(double scale) {
		return resize((int) (getBuffer().getWidth() * scale), (int) (getBuffer().getHeight() * scale));
	}

	@Nonnull
	public ImageProcessor resize(@Nonnull IBounds bounds) {
		return resize(ResizeMode.NORMAL, bounds);
	}

	@Nonnull
	public ImageProcessor resize(int width, int height) {
		return resize(ResizeMode.NORMAL, width, height);
	}

	@Nonnull
	public ImageProcessor resize(@Nonnull ResizeMode mode, @Nonnull IBounds bounds) {
		return resize(mode, (int) bounds.getWidth(), (int) bounds.getHeight());
	}

	@Nonnull
	public ImageProcessor resize(@Nonnull ResizeMode mode, int width, int height) {
		if (width != getBuffer().getWidth() || height != getBuffer().getHeight()) {
			this.buffer = ImageWorker.asBufferedImage(getBuffer().getScaledInstance(width, height, mode.getId()), getBuffer().getType());
			this.hasChanges = true;
			this.needsReferenceUpdate = true;
		}

		return this;
	}

	@Nonnull
	public ImageProcessor rotate(double radius) {
		int width = getBuffer().getWidth();
		int height = getBuffer().getHeight();
		BufferedImage image = new BufferedImage(width, height, getBuffer().getType());
		AffineTransform tx = AffineTransform.getRotateInstance(Math.toRadians(radius), width / 2, height / 2);
		AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_BILINEAR);
		Graphics2D graphics = image.createGraphics();

		graphics.drawImage(op.filter(getBuffer(), null), 0, 0, null);
		graphics.dispose();

		this.buffer = image;
		this.hasChanges = true;
		this.needsReferenceUpdate = true;

		return this;
	}

	@Nonnull
	public ImageProcessor scale(double widthScale, double heightScale) {
		AffineTransform transform = new AffineTransform();

		transform.scale(widthScale, heightScale);

		AffineTransformOp op = new AffineTransformOp(transform, AffineTransformOp.TYPE_BILINEAR);

		op.filter(getBuffer(), getBuffer());

		this.hasChanges = true;

		return this;
	}

	@Nonnull
	public ImageProcessor setColorModel(@Nonnull ColorModel model) {
		ColorSpace colorSpace = ColorSpace.getInstance(model.getId());
		ColorConvertOp op = new ColorConvertOp(colorSpace, null);

		op.filter(getBuffer(), getBuffer());

		this.hasChanges = true;

		return this;
	}

	@Nonnull
	public ImageProcessor setPixel(int x, int y, @Nonnull Color color) {
		return setPixel(x, y, color.hashCode());
	}

	@Nonnull
	public ImageProcessor setPixel(int x, int y, int pixel) {
		getBuffer().setRGB(x, y, pixel);

		this.hasChanges = true;

		return this;
	}

	@Nonnull
	public ImageProcessor setPixel(@Nonnull IPosition position, @Nonnull Color color) {
		return setPixel((int) position.getX(), (int) position.getY(), color);
	}

	@Nonnull
	public ImageProcessor setPixel(@Nonnull IPosition position, int pixel) {
		return setPixel((int) position.getX(), (int) position.getY(), pixel);
	}

	@Nonnull
	public ImageProcessor setPixels(@Nonnull int[] data) {
		getBuffer().setRGB(0, 0, getBuffer().getWidth(), getBuffer().getHeight(), data, 0, getBuffer().getWidth());

		this.hasChanges = true;

		return this;
	}

	@Nonnull
	public ImageProcessor start() throws ImageException {
		if (isProcessing()) {
			throw new ImageException("Failed to start image processor; already processing");
		}

		this.buffer = image.getImage();
		this.hasChanges = false;
		this.isProcessing = true;
		this.needsReferenceUpdate = false;

		return this;
	}

	public static enum ColorModel {
		CIEXYZ(ColorSpace.CS_CIEXYZ),
		GRAY(ColorSpace.CS_GRAY),
		LINEAR_RGB(ColorSpace.CS_LINEAR_RGB),
		PYCC(ColorSpace.CS_PYCC),
		SRGB(ColorSpace.CS_sRGB);

		private final int id;

		private ColorModel(int id) {
			this.id = id;
		}

		public int getId() {
			return id;
		}
	}

	public static enum FlipMode {
		HORIZONTAL,
		HORIZONTAL_VERTICAL,
		VERTICAL
	}

	public static enum ResizeMode {
		FAST(java.awt.Image.SCALE_FAST),
		NORMAL(java.awt.Image.SCALE_DEFAULT),
		REPLICATE(java.awt.Image.SCALE_REPLICATE),
		SMOOTH(java.awt.Image.SCALE_SMOOTH);

		private final int id;

		private ResizeMode(int id) {
			this.id = id;
		}

		public int getId() {
			return id;
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy