Rewrite thumbnail creation
This commit is contained in:
110
src/main/java/com/replaymod/replay/NoGuiScreenshot.java
Normal file
110
src/main/java/com/replaymod/replay/NoGuiScreenshot.java
Normal file
@@ -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<NoGuiScreenshot> take(final Minecraft mc, final int width, final int height) {
|
||||
final SettableFuture<NoGuiScreenshot> 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<FutureTask> queue = mc.scheduledTasks;
|
||||
queue.add(ListenableFutureTask.create(runnable, null));
|
||||
}
|
||||
return future;
|
||||
}
|
||||
}
|
||||
@@ -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<NoGuiScreenshot> future = NoGuiScreenshot.take(mc, 1280, 720);
|
||||
Futures.addCallback(future, new FutureCallback<NoGuiScreenshot>() {
|
||||
@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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user