diff --git a/src/main/java/fr/moribus/imageonmap/ImageOnMap.java b/src/main/java/fr/moribus/imageonmap/ImageOnMap.java index 6f22ccd..30eea7d 100644 --- a/src/main/java/fr/moribus/imageonmap/ImageOnMap.java +++ b/src/main/java/fr/moribus/imageonmap/ImageOnMap.java @@ -124,6 +124,7 @@ public final class ImageOnMap extends QuartzPlugin RenameCommand.class, DeleteCommand.class, DeleteOtherCommand.class, + GiveCommand.class, GetRemainingCommand.class, ExploreCommand.class, ExploreOtherCommand.class, @@ -133,6 +134,7 @@ public final class ImageOnMap extends QuartzPlugin Commands.registerShortcut("maptool", NewCommand.class, "tomap"); Commands.registerShortcut("maptool", ExploreCommand.class, "maps"); + Commands.registerShortcut("maptool", GiveCommand.class, "mapgive"); if (PluginConfiguration.CHECK_FOR_UPDATES.get()) { diff --git a/src/main/java/fr/moribus/imageonmap/Permissions.java b/src/main/java/fr/moribus/imageonmap/Permissions.java index c6c63ec..c3f867d 100644 --- a/src/main/java/fr/moribus/imageonmap/Permissions.java +++ b/src/main/java/fr/moribus/imageonmap/Permissions.java @@ -53,9 +53,8 @@ public enum Permissions UPDATE("imageonmap.update"), UPDATEOTHER("imageonmap.updateother"), ADMINISTRATIVE("imageonmap.administrative"), - BYPASS_SIZE("imageonmap.bypasssize") - - + BYPASS_SIZE("imageonmap.bypasssize"), + GIVE("imageonmap.give") ; private final String permission; diff --git a/src/main/java/fr/moribus/imageonmap/commands/maptool/GiveCommand.java b/src/main/java/fr/moribus/imageonmap/commands/maptool/GiveCommand.java new file mode 100644 index 0000000..3921d87 --- /dev/null +++ b/src/main/java/fr/moribus/imageonmap/commands/maptool/GiveCommand.java @@ -0,0 +1,141 @@ +/* + * Copyright or © or Copr. Moribus (2013) + * Copyright or © or Copr. ProkopyL (2015) + * Copyright or © or Copr. Amaury Carrade (2016 – 2020) + * Copyright or © or Copr. Vlammar (2019 – 2020) + * + * This software is a computer program whose purpose is to allow insertion of + * custom images in a Minecraft world. + * + * This software is governed by the CeCILL-B license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL-B + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL-B license and that you accept its terms. + */ + +package fr.moribus.imageonmap.commands.maptool; + +import fr.moribus.imageonmap.Permissions; +import fr.moribus.imageonmap.commands.IoMCommand; +import fr.moribus.imageonmap.map.ImageMap; +import fr.moribus.imageonmap.map.MapManager; +import fr.zcraft.zlib.components.commands.CommandException; +import fr.zcraft.zlib.components.commands.CommandInfo; +import fr.zcraft.zlib.components.i18n.I; +import fr.zcraft.zlib.tools.PluginLogger; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.io.IOException; +import java.util.UUID; +import java.util.concurrent.ExecutionException; + + +@CommandInfo(name = "give", usageParameters = " or ") +public class GiveCommand extends IoMCommand { + /** + * Parse an argument given at a specific index, it will return a player depending on the given prefixe. Can be player: or uuid: + * + * @param index The index. + * @return The retrieved player. + * @throws CommandException If the value is invalid. + * @throws InterruptedException . + * @throws IOException + */ + private OfflinePlayer parse(int index) throws CommandException { + + String s = args[index].trim(); + String[] subs = s.split(":"); + try { + // + if (subs.length == 1) { + return getOfflinePlayerParameter(index); + } + + switch (subs[0]) { + case "player": + return getOfflinePlayerParameter(subs[1]); + + case "uuid": + StringBuffer string = new StringBuffer(subs[1].toLowerCase()); + //if there are no '-' + if (string.length() == 32) { + //we try to fix it by adding - at pos 8,12,16,20 + Integer[] pos={20,16,12,8}; + for(int i:pos) + string = string.insert(i, "-"); + } + + //if the given uuid is well formed with 8-4-4-4-12 = 36 chars in length (including '-') + if (string.length() == 36) + return Bukkit.getOfflinePlayer(UUID.fromString(string.toString())); + + throwInvalidArgument(I.t("Invalid uuid, please provide an uuid of this form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx or xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")); + + case "bank": + throwInvalidArgument(I.t("Not supported yet")); + break; + + default: + throwInvalidArgument(I.t("Invalid prefix, valid one are: player | uuid")); + } + } catch (InterruptedException | ExecutionException e) { + PluginLogger.warning(I.t("Can't access to mojang API to check the player UUID")); + } + return null; + } + + @Override + protected void run() throws CommandException { + + if (args.length < 2) throwInvalidArgument(I.t("You must give a valid player name and a map name.")); + + final Player p = getPlayerParameter(0); + + ImageMap map; + //TODO add support for map name with spaces "cool name" or name or "name" "cool name with a \" and some stuff" should work + OfflinePlayer player = null; + + if (args.length < 4) { + if (args.length == 2) { + player = playerSender(); + } + if (args.length == 3) { + player = parse(2); + } + map = MapManager.getMap(player.getUniqueId(), args[1]); + if (map == null) { + throwInvalidArgument(I.t("Map not found")); + } + map.give(p); + } + } + + @Override + public boolean canExecute(CommandSender sender) { + return Permissions.GIVE.grantedTo(sender); + } +} diff --git a/src/main/java/fr/moribus/imageonmap/image/ImageIOExecutor.java b/src/main/java/fr/moribus/imageonmap/image/ImageIOExecutor.java index bb3ccf7..aad9d6c 100644 --- a/src/main/java/fr/moribus/imageonmap/image/ImageIOExecutor.java +++ b/src/main/java/fr/moribus/imageonmap/image/ImageIOExecutor.java @@ -61,6 +61,7 @@ public class ImageIOExecutor extends Worker { BufferedImage image = ImageIO.read(file); mapRenderer.setImage(image); + image.flush();//Safe to free return null; } }); @@ -88,7 +89,9 @@ public class ImageIOExecutor extends Worker { for(int i = 0, c = mapsIDs.length; i < c; i++) { - ImageIOExecutor.saveImage(ImageOnMap.getPlugin().getImageFile(mapsIDs[i]), image.getImageAt(i)); + BufferedImage img=image.getImageAt(i); + ImageIOExecutor.saveImage(ImageOnMap.getPlugin().getImageFile(mapsIDs[i]), img); + img.flush();//Safe to free } } diff --git a/src/main/java/fr/moribus/imageonmap/image/ImageRendererExecutor.java b/src/main/java/fr/moribus/imageonmap/image/ImageRendererExecutor.java index b481953..b8c8a03 100644 --- a/src/main/java/fr/moribus/imageonmap/image/ImageRendererExecutor.java +++ b/src/main/java/fr/moribus/imageonmap/image/ImageRendererExecutor.java @@ -59,20 +59,16 @@ import java.util.concurrent.Callable; import java.util.concurrent.Future; @WorkerAttributes(name = "Image Renderer", queriesMainThread = true) -public class ImageRendererExecutor extends Worker -{ - - private static URLConnection connecting(URL url)throws IOException{ +public class ImageRendererExecutor extends Worker { + private static URLConnection connecting(URL url) throws IOException { final URLConnection connection = url.openConnection(); connection.addRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:25.0) Gecko/20100101 Firefox/25.0"); connection.connect(); - if (connection instanceof HttpURLConnection) - { + if (connection instanceof HttpURLConnection) { final HttpURLConnection httpConnection = (HttpURLConnection) connection; final int httpCode = httpConnection.getResponseCode(); - if ((httpCode / 100) != 2) - { + if ((httpCode / 100) != 2) { throw new IOException(I.t("HTTP error: {0} {1}", httpCode, httpConnection.getResponseMessage())); } } @@ -106,13 +102,12 @@ public class ImageRendererExecutor extends Worker @Override public ImageMap run() throws Throwable { - BufferedImage image=null; + BufferedImage image = null; //If the link is an imgur one - if (url.toString().contains("https://imgur.com/")) { + if (url.toString().toLowerCase().startsWith("https://imgur.com/")) { //Not handled, can't with the hash only access the image in i.imgur.com/. - if (url.toString().contains("gallery/")) { throw new IOException("We do not support imgur gallery yet, please use direct link to image instead. Right click on the picture you want to use then select copy picture link:) "); } @@ -133,7 +128,6 @@ public class ImageRendererExecutor extends Worker } - } //If not an Imgur link else { @@ -148,16 +142,17 @@ public class ImageRendererExecutor extends Worker // Limits are in place and the player does NOT have rights to avoid them. checkSizeLimit(playerUUID, image); if (scaling != ImageUtils.ScalingType.NONE && height <= 1 && width <= 1) { - return renderSingle(scaling.resize(image, ImageMap.WIDTH, ImageMap.HEIGHT), playerUUID); + ImageMap ret = renderSingle(scaling.resize(image, ImageMap.WIDTH, ImageMap.HEIGHT), playerUUID); + image.flush();//Safe to free + return ret; } final BufferedImage resizedImage = scaling.resize(image, ImageMap.WIDTH * width, ImageMap.HEIGHT * height); - image.flush(); + image.flush();//Safe to free return renderPoster(resizedImage, playerUUID); } }, callback); } - public static void update(final URL url, final ImageUtils.ScalingType scaling, final UUID playerUUID, final ImageMap map, final int width, final int height, WorkerCallback callback) { submitQuery(new WorkerRunnable() { @@ -210,42 +205,32 @@ public class ImageRendererExecutor extends Worker static private ImageMap renderSingle(final BufferedImage image, final UUID playerUUID) throws Throwable { MapManager.checkMapLimit(1, playerUUID); - final Future futureMapID = submitToMainThread(new Callable() - { + final Future futureMapID = submitToMainThread(new Callable() { @Override - public Integer call() throws Exception - { + public Integer call() throws Exception { return MapManager.getNewMapsIds(1)[0]; } }); final int mapID = futureMapID.get(); ImageIOExecutor.saveImage(mapID, image); - - submitToMainThread(new Callable() - { + submitToMainThread(new Callable() { @Override - public Void call() throws Exception - { + public Void call() throws Exception { Renderer.installRenderer(image, mapID); - image.flush(); return null; } }); - image.flush(); return MapManager.createMap(playerUUID, mapID); } - static private ImageMap renderPoster(final BufferedImage image, final UUID playerUUID) throws Throwable - { + static private ImageMap renderPoster(final BufferedImage image, final UUID playerUUID) throws Throwable { final PosterImage poster = new PosterImage(image); final int mapCount = poster.getImagesCount(); MapManager.checkMapLimit(mapCount, playerUUID); - final Future futureMapsIds = submitToMainThread(new Callable() - { + final Future futureMapsIds = submitToMainThread(new Callable() { @Override - public int[] call() throws Exception - { + public int[] call() throws Exception { return MapManager.getNewMapsIds(mapCount); } }); @@ -253,27 +238,26 @@ public class ImageRendererExecutor extends Worker final int[] mapsIDs = futureMapsIds.get(); ImageIOExecutor.saveImage(mapsIDs, poster); + final int[] mapsIDs = futureMapsIds.get(); - if (PluginConfiguration.SAVE_FULL_IMAGE.get()) - { + ImageIOExecutor.saveImage(mapsIDs, poster); + if (PluginConfiguration.SAVE_FULL_IMAGE.get()) { ImageIOExecutor.saveImage(ImageMap.getFullImageFile(mapsIDs[0], mapsIDs[mapsIDs.length - 1]), image); } - submitToMainThread(new Callable() - { + submitToMainThread(new Callable() { @Override - public Void call() throws Exception - { + public Void call() throws Exception { Renderer.installRenderer(poster, mapsIDs); return null; } }); - - image.flush(); - + poster.getImage().flush();//Safe to free return MapManager.createMap(poster, playerUUID, mapsIDs); } - + private enum extension { + png, jpg, jpeg, gif + } } diff --git a/src/main/java/fr/moribus/imageonmap/image/ImageUtils.java b/src/main/java/fr/moribus/imageonmap/image/ImageUtils.java index 560c657..94b6237 100644 --- a/src/main/java/fr/moribus/imageonmap/image/ImageUtils.java +++ b/src/main/java/fr/moribus/imageonmap/image/ImageUtils.java @@ -36,6 +36,8 @@ package fr.moribus.imageonmap.image; +import fr.zcraft.zlib.tools.PluginLogger; + import java.awt.*; import java.awt.image.BufferedImage; @@ -44,45 +46,25 @@ import java.awt.image.BufferedImage; */ public class ImageUtils { - public enum ScalingType { - NONE, - CONTAINED, - COVERED, - STRETCHED, - ; - - public BufferedImage resize(BufferedImage source, int destinationW, int destinationH) { - switch(this) { - case CONTAINED: return ImageUtils.resize(source, destinationW, destinationH, false); - case COVERED: return ImageUtils.resize(source, destinationW, destinationH, true); - case STRETCHED: return resizeStretched(source, destinationW, destinationH); - default: return source; - } - } - } - /** * Generates a resized buffer of the given source + * * @param source * @param destinationW * @param destinationH * @return */ - static private BufferedImage resize(BufferedImage source, int destinationW, int destinationH, boolean covered) - { - float ratioW = (float)destinationW / (float)source.getWidth(); - float ratioH = (float)destinationH / (float)source.getHeight(); + static private BufferedImage resize(BufferedImage source, int destinationW, int destinationH, boolean covered) { + float ratioW = (float) destinationW / (float) source.getWidth(); + float ratioH = (float) destinationH / (float) source.getHeight(); int finalW, finalH; int x, y; - if(covered ? ratioW > ratioH : ratioW < ratioH) - { + if (covered ? ratioW > ratioH : ratioW < ratioH) { finalW = destinationW; - finalH = (int)(source.getHeight() * ratioW); - } - else - { - finalW = (int)(source.getWidth() * ratioH); + finalH = (int) (source.getHeight() * ratioW); + } else { + finalW = (int) (source.getWidth() * ratioH); finalH = destinationH; } @@ -95,7 +77,6 @@ public class ImageUtils { } /** - * * @param source * @param destinationW * @param destinationH @@ -110,24 +91,57 @@ public class ImageUtils { /** * Draws the source image on a new buffer, and returns it. * The source buffer can be drawn at any size and position in the new buffer. - * @param source The source buffer to draw + * + * @param source The source buffer to draw * @param bufferW The width of the new buffer * @param bufferH The height of the new buffer - * @param posX The X position of the source buffer - * @param posY The Y position of the source buffer + * @param posX The X position of the source buffer + * @param posY The Y position of the source buffer * @param sourceW The width of the source buffer * @param sourceH The height of the source buffer * @return The new buffer, with the source buffer drawn on it */ static private BufferedImage drawImage(BufferedImage source, - int bufferW, int bufferH, - int posX, int posY, - int sourceW, int sourceH) { - BufferedImage newImage = new BufferedImage(bufferW, bufferH, BufferedImage.TYPE_INT_ARGB); + int bufferW, int bufferH, + int posX, int posY, + int sourceW, int sourceH) { + Graphics graphics = null; + BufferedImage newImage = null; + try { + newImage = new BufferedImage(bufferW, bufferH, BufferedImage.TYPE_INT_ARGB); - Graphics graphics = newImage.getGraphics(); - graphics.drawImage(source, posX, posY, sourceW, sourceH, null); - graphics.dispose(); - return newImage; + graphics = newImage.getGraphics(); + graphics.drawImage(source, posX, posY, sourceW, sourceH, null); + + return newImage; + } catch (final Throwable e) { + PluginLogger.warning("Exception/error at drawImage"); + if (newImage != null) + newImage.flush();//Safe to free + throw e; + } + + } + + public enum ScalingType { + NONE, + CONTAINED, + COVERED, + STRETCHED, + ; + + public BufferedImage resize(BufferedImage source, int destinationW, int destinationH) { + switch (this) { + case CONTAINED: + return ImageUtils.resize(source, destinationW, destinationH, false); + case COVERED: + return ImageUtils.resize(source, destinationW, destinationH, true); + case STRETCHED: + return resizeStretched(source, destinationW, destinationH); + default: + return source; + + } + } } } \ No newline at end of file diff --git a/src/main/java/fr/moribus/imageonmap/image/PosterImage.java b/src/main/java/fr/moribus/imageonmap/image/PosterImage.java index 60589c0..9ede73e 100644 --- a/src/main/java/fr/moribus/imageonmap/image/PosterImage.java +++ b/src/main/java/fr/moribus/imageonmap/image/PosterImage.java @@ -36,156 +36,150 @@ package fr.moribus.imageonmap.image; + import java.awt.Graphics; import java.awt.image.BufferedImage; /** * This class represents an image split into pieces */ -public class PosterImage -{ +public class PosterImage { static private final int WIDTH = 128; static private final int HEIGHT = 128; - + private BufferedImage originalImage; private BufferedImage[] cutImages; private int lines; private int columns; private int cutImagesCount; private int remainderX, remainderY; - + /** * Creates a new Poster from an entire image + * * @param originalImage the original image */ - public PosterImage(BufferedImage originalImage) - { + public PosterImage(BufferedImage originalImage) { this.originalImage = originalImage; calculateDimensions(); } - - private void calculateDimensions() - { + + private void calculateDimensions() { int originalWidth = originalImage.getWidth(); int originalHeight = originalImage.getHeight(); - + columns = (int) Math.ceil(originalWidth / WIDTH); lines = (int) Math.ceil(originalHeight / HEIGHT); - + remainderX = originalWidth % WIDTH; remainderY = originalHeight % HEIGHT; - - if(remainderX > 0) columns++; - if(remainderY > 0) lines++; - + + if (remainderX > 0) columns++; + if (remainderY > 0) lines++; + cutImagesCount = columns * lines; } - - public void splitImages() - { - cutImages = new BufferedImage[cutImagesCount]; - - int imageX; - int imageY = remainderY == 0 ? 0 :(remainderY - HEIGHT) / 2; - for(int i = 0; i < lines; i++) - { - imageX = remainderX == 0 ? 0 :(remainderX - WIDTH) / 2; - for(int j = 0; j < columns; j++) - { - cutImages[i * columns + j] = makeSubImage(originalImage, imageX, imageY); - imageX += WIDTH; + + public void splitImages() { + try { + cutImages = new BufferedImage[cutImagesCount]; + + int imageX; + int imageY = remainderY == 0 ? 0 : (remainderY - HEIGHT) / 2; + for (int i = 0; i < lines; i++) { + imageX = remainderX == 0 ? 0 : (remainderX - WIDTH) / 2; + for (int j = 0; j < columns; j++) { + cutImages[i * columns + j] = makeSubImage(originalImage, imageX, imageY); + imageX += WIDTH; + } + imageY += HEIGHT; } - imageY += HEIGHT; + } catch (final Throwable e) { + if (cutImages != null) + for (BufferedImage bi : cutImages) { + if (bi != null) { + bi.flush();//Safe to free + } + } + throw e; } - - originalImage = null; + } - + /** * Generates the subimage that intersects with the given map rectangle. + * * @param x X coordinate of top-left point of the map. * @param y Y coordinate of top-left point of the map. * @return the requested subimage. */ - private BufferedImage makeSubImage(BufferedImage originalImage, int x, int y) - { + private BufferedImage makeSubImage(BufferedImage originalImage, int x, int y) { + BufferedImage newImage = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB); - + Graphics graphics = newImage.getGraphics(); - + graphics.drawImage(originalImage, -x, -y, null); - graphics.dispose(); return newImage; } - + /** - * * @return the split images */ - public BufferedImage[] getImages() - { + public BufferedImage[] getImages() { return cutImages; } - - public BufferedImage getImageAt(int i) - { + + public BufferedImage getImageAt(int i) { return cutImages[i]; } - - public int getColumnAt(int i) - { + + public BufferedImage getImage() { + return originalImage; + } + + public int getColumnAt(int i) { return i % columns; } - - public int getLineAt(int i) - { + + public int getLineAt(int i) { return i / columns; } - + /** - * * @return the number of lines of the poster */ - public int getLines() - { + public int getLines() { return lines; } - + /** - * * @return the number of columns of the poster */ - public int getColumns() - { + public int getColumns() { return columns; } - + /** - * * @return the number of split images */ - public int getImagesCount() - { + public int getImagesCount() { return cutImagesCount; } - public int getRemainderX() - { + public int getRemainderX() { return remainderX; } - public void setRemainderX(int remainderX) - { + public void setRemainderX(int remainderX) { this.remainderX = remainderX; } - public int getRemainderY() - { + public int getRemainderY() { return remainderY; } - public void setRemainderY(int remainderY) - { + public void setRemainderY(int remainderY) { this.remainderY = remainderY; } } diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 05600f9..0f70dee 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -12,6 +12,9 @@ commands: description: Manage maps maps: description: Manage maps through a GUI + mapgive: + description: give a map to a player from a player map store, by default take from the player that make the command + usage: / player_to map_name [player_from] permissions: imageonmap.*: @@ -31,6 +34,7 @@ permissions: imageonmap.delete: true imageonmap.deleteother: false imageonmap.bypasssize: false + imageonmap.give: false imageonmap.update: true imageonmap.updateother: false @@ -74,6 +78,10 @@ permissions: description: "Allows you to delete a map you rendered in the past." default: true + imageonmap.give: + description: "Allows you to give a map to a specified player." + default: op + imageonmap.deleteother: description: "Allows you to delete a map a player rendered in the past." default: false