/*
* This file is part of HoloAPI.
*
* HoloAPI 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 3 of the License, or
* (at your option) any later version.
*
* HoloAPI 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.
*
* You should have received a copy of the GNU General Public License
* along with HoloAPI. If not, see <http://www.gnu.org/licenses/>.
*/
package com.dsh105.holoapi.image;
import com.dsh105.holoapi.HoloAPI;
import com.dsh105.holoapi.config.Settings;
import org.bukkit.ChatColor;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.URI;
/**
* Represents a generator used to produce images
* <p>
* Original code by @bobacadodl
* https://forums.bukkit.org/threads/204902/
*/
public class ImageGenerator implements Generator {
private String lines[];
private String imageKey;
private String imageUrl;
private int imageHeight;
private ImageChar imageChar = ImageChar.BLOCK;
private boolean requiresBorder;
private boolean hasLoaded;
protected ImageGenerator(BufferedImage image, int height, ImageChar imgChar) {
this.imageHeight = height;
this.imageChar = imgChar;
this.lines = this.generate(generateColours(image, height), imgChar.getImageChar(), false);
}
/**
* Constructs an ImageGenerator for use in a Hologram
*
* @param image {@link java.awt.image.BufferedImage} used to generate the image
* @param height height of the display
* @param imgChar {@link com.dsh105.holoapi.image.ImageChar} of the display
* @param requiresBorder whether the display requires a border
*/
public ImageGenerator(BufferedImage image, int height, ImageChar imgChar, boolean requiresBorder) {
this.imageHeight = height;
this.imageChar = imgChar;
this.requiresBorder = requiresBorder;
this.lines = this.generate(generateColours(image, height), imgChar.getImageChar(), requiresBorder);
}
/**
* Constructs an ImageGenerator for use in a Hologram
*
* @param imageUrl URL to search the image for
* @param height height of the display
* @param imgChar {@link com.dsh105.holoapi.image.ImageChar} of the display
* @param requiresBorder whether the display requires a border
*/
public ImageGenerator(String imageUrl, int height, ImageChar imgChar, boolean requiresBorder) {
this(imageUrl, height, imgChar, true, requiresBorder);
}
/**
* Constructs an ImageGenerator for use in a Hologram
*
* @param imageUrl URL to search the image for
* @param height height of the display
* @param imgChar {@link com.dsh105.holoapi.image.ImageChar} of the display
* @param loadUrl whether to automatically load the image from the specified URL
* @param requiresBorder whether the display requires a border
*/
public ImageGenerator(String imageUrl, int height, ImageChar imgChar, boolean loadUrl, boolean requiresBorder) {
this.imageUrl = imageUrl;
this.imageHeight = height;
this.imageChar = imgChar;
this.requiresBorder = requiresBorder;
if (loadUrl) {
this.loadUrlImage();
}
}
/**
* Constructs an ImageGenerator for use in a Hologram
*
* @param imageFile file used to generate the image
* @param height height of the display
* @param imgChar {@link com.dsh105.holoapi.image.ImageChar} of the display
* @param requiresBorder whether the display requires a border
* @throws java.lang.RuntimeException if the image cannot be read
*/
public ImageGenerator(File imageFile, int height, ImageChar imgChar, boolean requiresBorder) {
this.imageHeight = height;
this.imageChar = imgChar;
this.requiresBorder = requiresBorder;
BufferedImage image;
try {
image = ImageIO.read(imageFile);
} catch (IOException e) {
throw new RuntimeException("Cannot read image " + imageFile.getPath(), e);
}
this.lines = this.generate(generateColours(image, height), imgChar.getImageChar(), requiresBorder);
}
/**
* Constructs an ImageGenerator for use in a Hologram
*
* @param imageKey key to store the generator under. Only used for recognition of stored image generators
* @param image {@link java.awt.image.BufferedImage} used to generate the image
* @param height height of the display
* @param imgChar {@link com.dsh105.holoapi.image.ImageChar} of the display
* @param requiresBorder whether the display requires a border
*/
public ImageGenerator(String imageKey, BufferedImage image, int height, ImageChar imgChar, boolean requiresBorder) {
this(image, height, imgChar, requiresBorder);
this.imageKey = imageKey;
}
/**
* Constructs an ImageGenerator for use in a Hologram
*
* @param imageKey key to store the generator under. Only used for recognition of stored image generators
* @param imageUrl URL to search the image for
* @param height height of the display
* @param imgChar {@link com.dsh105.holoapi.image.ImageChar} of the display
* @param requiresBorder whether the display requires a border
*/
public ImageGenerator(String imageKey, String imageUrl, int height, ImageChar imgChar, boolean requiresBorder) {
this(imageKey, imageUrl, height, imgChar, true, requiresBorder);
}
/**
* Constructs an ImageGenerator for use in a Hologram
*
* @param imageKey key to store the generator under. Only used for recognition of stored image generators
* @param imageUrl URL to search the image for
* @param height height of the display
* @param imgChar {@link com.dsh105.holoapi.image.ImageChar} of the display
* @param loadUrl whether to automatically load the image from the specified URL
* @param requiresBorder whether the display requires a border
*/
public ImageGenerator(String imageKey, String imageUrl, int height, ImageChar imgChar, boolean loadUrl, boolean requiresBorder) {
this(imageUrl, height, imgChar, loadUrl, requiresBorder);
this.imageKey = imageKey;
}
/**
* Constructs an ImageGenerator for use in a Hologram
*
* @param imageKey key to store the generator under. Only used for recognition of stored image generators
* @param imageFile file used to generate the image
* @param height height of the display
* @param imgChar {@link com.dsh105.holoapi.image.ImageChar} of the display
* @param requiresBorder whether the display requires a border
* @throws java.lang.RuntimeException if the image cannot be read
*/
public ImageGenerator(String imageKey, File imageFile, int height, ImageChar imgChar, boolean requiresBorder) {
this(imageFile, height, imgChar, requiresBorder);
this.imageKey = imageKey;
}
@Override
public String getKey() {
return imageKey;
}
/**
* Loads the stored URL data to form a generated image
*
* @throws java.lang.IllegalArgumentException if the URL is not initiated
* @throws java.lang.RuntimeException if the image cannot be read
*/
public void loadUrlImage() {
if (this.hasLoaded) {
return;
}
if (this.imageUrl == null) {
throw new IllegalArgumentException("URL not initiated for ImageGenerator.");
}
URI uri = URI.create(this.imageUrl);
BufferedImage image;
try {
image = ImageIO.read(uri.toURL());
} catch (IOException e) {
throw new RuntimeException("Cannot read image " + uri, e);
}
this.lines = this.generate(generateColours(image, this.imageHeight), this.imageChar.getImageChar(), requiresBorder);
this.hasLoaded = true;
}
/**
* Gets the generated lines
*
* @return generated lines
*/
public String[] getLines() {
return lines;
}
private String[] generate(ChatColor[][] colors, char imgchar, boolean border) {
String[] lines = new String[colors[0].length];
for (int y = 0; y < colors[0].length; y++) {
String line = "";
for (ChatColor[] color : colors) {
ChatColor colour = color[y];
if (colour == null) {
line += border ? Settings.TRANSPARENCY_WITH_BORDER.getValue() : Settings.TRANSPARENCY_WITHOUT_BORDER.getValue();
} else {
line += colour.toString() + imgchar + ChatColor.RESET;
}
}
if (!border) {
line = line.trim();
}
lines[y] = line + ChatColor.RESET;
}
return lines;
}
private ChatColor[][] generateColours(BufferedImage image, int height) {
double ratio = (double) image.getHeight() / image.getWidth();
BufferedImage resized = resize(image, (int) (height / ratio), height);
ChatColor[][] chatImg = new ChatColor[resized.getWidth()][resized.getHeight()];
for (int x = 0; x < resized.getWidth(); x++) {
for (int y = 0; y < resized.getHeight(); y++) {
int rgb = resized.getRGB(x, y);
ChatColor closest = ColourMap.getClosest(new Color(rgb, true));
chatImg[x][y] = closest;
}
}
return chatImg;
}
private BufferedImage resize(BufferedImage originalImage, int width, int height) {
AffineTransform af = new AffineTransform();
af.scale(width / (double) originalImage.getWidth(), height / (double) originalImage.getHeight());
AffineTransformOp operation = new AffineTransformOp(af, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
return operation.filter(originalImage, null);
}
}