From 213c16c81d0e4bcdc57278402097049b904a07ee Mon Sep 17 00:00:00 2001 From: johni0702 Date: Sun, 4 Oct 2015 15:43:19 +0200 Subject: [PATCH] Rewrite thumbnail creation --- .../com/replaymod/replay/NoGuiScreenshot.java | 110 ++++++++++++++++++ .../com/replaymod/replay/ReplayModReplay.java | 32 +++++ .../replaymod/gui/online/GuiUploadFile.java | 7 +- .../replaymod/registry/KeybindRegistry.java | 1 - .../replaymod/video/ReplayScreenshot.java | 95 --------------- 5 files changed, 145 insertions(+), 100 deletions(-) create mode 100644 src/main/java/com/replaymod/replay/NoGuiScreenshot.java delete mode 100755 src/main/java/eu/crushedpixel/replaymod/video/ReplayScreenshot.java diff --git a/src/main/java/com/replaymod/replay/NoGuiScreenshot.java b/src/main/java/com/replaymod/replay/NoGuiScreenshot.java new file mode 100644 index 00000000..f3626baa --- /dev/null +++ b/src/main/java/com/replaymod/replay/NoGuiScreenshot.java @@ -0,0 +1,110 @@ +package com.replaymod.replay; + +import com.google.common.io.Files; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListenableFutureTask; +import com.google.common.util.concurrent.SettableFuture; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.util.ScreenShotHelper; +import org.apache.commons.io.FileUtils; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.File; +import java.util.Queue; +import java.util.concurrent.FutureTask; + +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +public class NoGuiScreenshot { + private final BufferedImage image; + private final int width; + private final int height; + + public static ListenableFuture take(final Minecraft mc, final int width, final int height) { + final SettableFuture future = SettableFuture.create(); + Runnable runnable = new Runnable() { + @Override + public void run() { + if (future.isCancelled()) { + return; + } + + final boolean guiHidden = mc.gameSettings.hideGUI; + try { + mc.gameSettings.hideGUI = true; + + // Render frame without GUI + GlStateManager.pushMatrix(); + GlStateManager.clear(16640); + mc.getFramebuffer().bindFramebuffer(true); + GlStateManager.enableTexture2D(); + + mc.entityRenderer.updateCameraAndRender(mc.timer.renderPartialTicks); + + mc.getFramebuffer().unbindFramebuffer(); + GlStateManager.popMatrix(); + GlStateManager.pushMatrix(); + mc.getFramebuffer().framebufferRender(mc.displayWidth, mc.displayHeight); + GlStateManager.popMatrix(); + } catch (Throwable t) { + future.setException(t); + return; + } finally { + // Reset GUI settings + mc.gameSettings.hideGUI = guiHidden; + } + + // The frame without GUI has been rendered + // Read it, create the screenshot and finish the future + // We're using Minecraft's ScreenShotHelper even though it writes the screenshot to + // disk for better maintainability + File tmpFolder = Files.createTempDir(); + try { + ScreenShotHelper.saveScreenshot(tmpFolder, "tmp", mc.displayWidth, mc.displayHeight, mc.getFramebuffer()); + File screenshotFile = new File(tmpFolder, "screenshots/tmp"); + BufferedImage image = ImageIO.read(screenshotFile); + int imageWidth = image.getWidth(); + int imageHeight = image.getHeight(); + + // First scale + float scaleFactor = Math.max((float) width / imageWidth, (float) height / imageHeight); + int scaledWidth = (int) (imageWidth * scaleFactor); + int scaledHeight = (int) (imageHeight * scaleFactor); + Image scaledImage = image.getScaledInstance(scaledWidth, scaledHeight, Image.SCALE_SMOOTH); + + // Then crop + int resultX = (scaledWidth - width) / 2; + int resultY = (scaledHeight - height) / 2; + BufferedImage resultImage = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR); + Graphics2D graphics = resultImage.createGraphics(); + graphics.drawImage(scaledImage, 0, 0, width, height, + resultX, resultY, resultX + width, resultY + height, null); + graphics.dispose(); + + // Finish + future.set(new NoGuiScreenshot(resultImage, width, height)); + } catch (Throwable t) { + future.setException(t); + } finally { + FileUtils.deleteQuietly(tmpFolder); + } + } + }; + + // Make sure we are not somewhere in the middle of the rendering process but always at the beginning + // of the game loop. We cannot use the addScheduledTask method as it'll run the task if called + // from the minecraft thread which is exactly what we want to avoid. + synchronized (mc.scheduledTasks) { + @SuppressWarnings("unchecked") + Queue queue = mc.scheduledTasks; + queue.add(ListenableFutureTask.create(runnable, null)); + } + return future; + } +} diff --git a/src/main/java/com/replaymod/replay/ReplayModReplay.java b/src/main/java/com/replaymod/replay/ReplayModReplay.java index 599a9c28..8adf4904 100644 --- a/src/main/java/com/replaymod/replay/ReplayModReplay.java +++ b/src/main/java/com/replaymod/replay/ReplayModReplay.java @@ -1,10 +1,15 @@ package com.replaymod.replay; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; import com.replaymod.core.ReplayMod; import com.replaymod.replay.handler.GuiHandler; import de.johni0702.replaystudio.replay.ReplayFile; import de.johni0702.replaystudio.replay.ZipReplayFile; import de.johni0702.replaystudio.studio.ReplayStudio; +import eu.crushedpixel.replaymod.chat.ChatMessageHandler; +import net.minecraft.client.Minecraft; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.event.FMLInitializationEvent; import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; @@ -44,6 +49,33 @@ public class ReplayModReplay { } }); + + core.getKeyBindingRegistry().registerKeyBinding("replaymod.input.thumbnail", Keyboard.KEY_N, new Runnable() { + @Override + public void run() { + if (replayHandler != null) { + Minecraft mc = Minecraft.getMinecraft(); + ListenableFuture future = NoGuiScreenshot.take(mc, 1280, 720); + Futures.addCallback(future, new FutureCallback() { + @Override + public void onSuccess(NoGuiScreenshot result) { + try { + replayHandler.getReplayFile().writeThumb(result.getImage()); + ReplayMod.chatMessageHandler.addLocalizedChatMessage("replaymod.chat.savedthumb", + ChatMessageHandler.ChatMessageType.INFORMATION); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public void onFailure(Throwable t) { + t.printStackTrace(); + } + }); + } + } + }); } @Mod.EventHandler diff --git a/src/main/java/eu/crushedpixel/replaymod/gui/online/GuiUploadFile.java b/src/main/java/eu/crushedpixel/replaymod/gui/online/GuiUploadFile.java index c5c95d4e..e7b636e3 100755 --- a/src/main/java/eu/crushedpixel/replaymod/gui/online/GuiUploadFile.java +++ b/src/main/java/eu/crushedpixel/replaymod/gui/online/GuiUploadFile.java @@ -1,18 +1,17 @@ package eu.crushedpixel.replaymod.gui.online; import com.google.common.base.Optional; +import com.replaymod.core.ReplayMod; +import com.replaymod.replay.gui.screen.GuiReplayViewer; import de.johni0702.replaystudio.replay.ReplayFile; import de.johni0702.replaystudio.replay.ReplayMetaData; import de.johni0702.replaystudio.replay.ZipReplayFile; import de.johni0702.replaystudio.studio.ReplayStudio; -import com.replaymod.core.ReplayMod; import eu.crushedpixel.replaymod.api.replay.FileUploader; import eu.crushedpixel.replaymod.api.replay.holders.Category; import eu.crushedpixel.replaymod.gui.GuiConstants; import eu.crushedpixel.replaymod.gui.elements.*; import eu.crushedpixel.replaymod.gui.elements.listeners.ProgressUpdateListener; -import com.replaymod.replay.gui.screen.GuiReplayViewer; -import eu.crushedpixel.replaymod.registry.KeybindRegistry; import eu.crushedpixel.replaymod.registry.ResourceHelper; import eu.crushedpixel.replaymod.utils.ImageUtils; import eu.crushedpixel.replaymod.utils.MouseUtils; @@ -359,7 +358,7 @@ public class GuiUploadFile extends GuiScreen implements ProgressUpdateListener { Gui.drawScaledCustomSizeModalRect(columnRight, 20, 0, 0, 1280, 720, columnWidth, height, 1280, 720); if (!hasThumbnail) { - KeyBinding keyBinding = KeybindRegistry.getKeyBinding(KeybindRegistry.KEY_THUMBNAIL); + KeyBinding keyBinding = ReplayMod.instance.getKeyBindingRegistry().getKeyBindings().get("replaymod.input.thumbnail"); String str = I18n.format("replaymod.gui.upload.nothumbnail", keyBinding == null ? "???" : GameSettings.getKeyDisplayString(keyBinding.getKeyCode())); int y = 20 + height + 10; diff --git a/src/main/java/eu/crushedpixel/replaymod/registry/KeybindRegistry.java b/src/main/java/eu/crushedpixel/replaymod/registry/KeybindRegistry.java index b7eea11e..6015a365 100755 --- a/src/main/java/eu/crushedpixel/replaymod/registry/KeybindRegistry.java +++ b/src/main/java/eu/crushedpixel/replaymod/registry/KeybindRegistry.java @@ -47,7 +47,6 @@ public class KeybindRegistry { replayModKeyBindings.add(new KeyBinding(KEY_SYNC_TIMELINE, Keyboard.KEY_V, "replaymod.title")); - replayModKeyBindings.add(new KeyBinding(KEY_THUMBNAIL, Keyboard.KEY_N, "replaymod.title")); replayModKeyBindings.add(new KeyBinding(KEY_PLAYER_OVERVIEW, Keyboard.KEY_B, "replaymod.title")); replayModKeyBindings.add(new KeyBinding(KEY_LIGHTING, Keyboard.KEY_Z, "replaymod.title")); diff --git a/src/main/java/eu/crushedpixel/replaymod/video/ReplayScreenshot.java b/src/main/java/eu/crushedpixel/replaymod/video/ReplayScreenshot.java deleted file mode 100755 index fc1c95d0..00000000 --- a/src/main/java/eu/crushedpixel/replaymod/video/ReplayScreenshot.java +++ /dev/null @@ -1,95 +0,0 @@ -package eu.crushedpixel.replaymod.video; - -import com.replaymod.core.ReplayMod; -import eu.crushedpixel.replaymod.chat.ChatMessageHandler.ChatMessageType; -import eu.crushedpixel.replaymod.events.handlers.TickAndRenderListener; -import eu.crushedpixel.replaymod.utils.ImageUtils; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiScreen; - -import java.awt.*; -import java.awt.image.BufferedImage; - -public class ReplayScreenshot { - - private static Minecraft mc = Minecraft.getMinecraft(); - - private static boolean before; - private static double beforeSpeed; - private static GuiScreen beforeScreen; - private static boolean locked = false; - - public static void prepareScreenshot() { - before = mc.gameSettings.hideGUI; - beforeSpeed = ReplayMod.replaySender.getReplaySpeed(); - beforeScreen = mc.currentScreen; - } - - public static void saveScreenshot() { - - if(locked) return; - locked = true; - - try { - mc.gameSettings.hideGUI = true; - mc.currentScreen = null; - - mc.entityRenderer.updateCameraAndRender(0); - ReplayMod.replaySender.setReplaySpeed(0); - - final BufferedImage fbi = ScreenCapture.captureScreen(); - - mc.gameSettings.hideGUI = before; - mc.currentScreen = beforeScreen; - ReplayMod.replaySender.setReplaySpeed(beforeSpeed); - - //The actual cropping and saving should be executed in a separate thread - Thread ioThread = new Thread(new Runnable() { - - @Override - public void run() { - try { - float aspect = 16f/9f; - - Rectangle rect; - if((float) fbi.getWidth() / (float) fbi.getHeight() <= aspect) { - int h = Math.round(fbi.getWidth() / aspect); - int y = (fbi.getHeight() / 2) - (h / 2); - rect = new Rectangle(0, y, fbi.getWidth(), h); - } else { - int w = Math.round(fbi.getHeight() * aspect); - int x = (fbi.getWidth() / 2) - (w / 2); - rect = new Rectangle(x, 0, w, fbi.getHeight()); - } - - final BufferedImage nbi = ImageUtils.cropImage(fbi, rect); - - int h = 720; - int w = 1280; - - BufferedImage img = ImageUtils.scaleImage(nbi, new Dimension(w, h)); - - // TODO -// ReplayHandler.getReplayFile().writeThumb(img); - - ReplayMod.chatMessageHandler.addLocalizedChatMessage("replaymod.chat.savedthumb", ChatMessageType.INFORMATION); - } catch(Exception e) { - e.printStackTrace(); - } finally { - locked = false; - TickAndRenderListener.finishScreenshot(); - } - } - }, "replaymod-screenshot-saver"); - - ioThread.start(); - } catch(Exception exception) { - exception.printStackTrace(); - mc.gameSettings.hideGUI = before; - mc.currentScreen = beforeScreen; - ReplayMod.replaySender.setReplaySpeed(beforeSpeed); - exception.printStackTrace(); - ReplayMod.chatMessageHandler.addLocalizedChatMessage("replaymod.chat.failedthumb", ChatMessageType.WARNING); - } - } -}