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