AWT is forbidden on 1.13+ (this includes BufferedImage)

Instead use NativeImage on 1.13+ via newly introduced Image class.
See Image docs for details.

Also fixes an issue with thumbnail taking on 1.13+: The original
ScreenshotHelper method is now async, luckily there's a sync
variant and as a bonus it directly returns a NativeImage so we can
skip having to read the screenshot from disk.
This commit is contained in:
Jonas Herzig
2019-06-30 11:11:02 +02:00
parent 732e1ab32e
commit 7241036e61
14 changed files with 132 additions and 134 deletions

View File

@@ -1,27 +1,27 @@
package com.replaymod.replay;
import com.google.common.io.Files;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import com.replaymod.core.ReplayMod;
import com.replaymod.core.versions.MCVer;
import de.johni0702.minecraft.gui.versions.Image;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import net.minecraft.client.MinecraftClient;
import com.mojang.blaze3d.platform.GlStateManager;
import net.minecraft.client.util.ScreenshotUtils;
import org.apache.commons.io.FileUtils;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
//#if MC<11300
//$$ import com.google.common.io.Files;
//$$ import org.apache.commons.io.FileUtils;
//$$ import java.io.File;
//#endif
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
@Getter
public class NoGuiScreenshot {
private final BufferedImage image;
private final Image image;
private final int width;
private final int height;
@@ -80,42 +80,37 @@ public class NoGuiScreenshot {
// 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 {
//#if MC>=11300
ScreenshotUtils.method_1662(tmpFolder, "tmp", frameWidth, frameHeight, mc.getFramebuffer(), (msg) ->
ReplayMod.instance.runLater(() -> mc.inGameHud.getChatHud().addMessage(msg)));
Image image = new Image(ScreenshotUtils.method_1663(frameWidth, frameHeight, mc.getFramebuffer()));
//#else
//$$ ScreenShotHelper.saveScreenshot(tmpFolder, "tmp", frameWidth, frameHeight, mc.getFramebuffer());
// We're using Minecraft's ScreenShotHelper even though it writes the screenshot to
// disk for better maintainability
//$$ File tmpFolder = Files.createTempDir();
//$$ Image image;
//$$ try {
//$$ ScreenShotHelper.saveScreenshot(tmpFolder, "tmp", frameWidth, frameHeight, mc.getFramebuffer());
//$$ File screenshotFile = new File(tmpFolder, "screenshots/tmp");
//$$ image = Image.read(screenshotFile.toPath());
//$$ } finally {
//$$ FileUtils.deleteQuietly(tmpFolder);
//$$ }
//#endif
File screenshotFile = new File(tmpFolder, "screenshots/tmp");
BufferedImage image = ImageIO.read(screenshotFile);
int imageWidth = image.getWidth();
int imageHeight = image.getHeight();
// First scale
// Scale & crop
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();
int croppedWidth = Math.min(Math.max(0, (int) (width / scaleFactor)), imageWidth);
int croppedHeight = Math.min(Math.max(0, (int) (height / scaleFactor)), imageHeight);
int offsetX = (imageWidth - croppedWidth) / 2;
int offsetY = (imageHeight - croppedHeight) / 2;
image = image.scaledSubRect(offsetX, offsetY, croppedWidth, croppedHeight, width, height);
// Finish
future.set(new NoGuiScreenshot(resultImage, width, height));
future.set(new NoGuiScreenshot(image, width, height));
} catch (Throwable t) {
future.setException(t);
} finally {
FileUtils.deleteQuietly(tmpFolder);
}
}
};

View File

@@ -27,6 +27,8 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.annotation.Nullable;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Optional;
@@ -87,7 +89,14 @@ public class ReplayModReplay implements Module {
public void onSuccess(NoGuiScreenshot result) {
try {
core.printInfoToChat("replaymod.chat.savingthumb");
replayHandler.getReplayFile().writeThumb(result.getImage());
@SuppressWarnings("deprecation") // there's no easy way to produce jpg images from NativeImage
BufferedImage image = result.getImage().toBufferedImage();
// Encoding with alpha fails on OpenJDK and produces broken image on Sun JDK.
BufferedImage bgrImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_3BYTE_BGR);
Graphics graphics = bgrImage.getGraphics();
graphics.drawImage(image, 0, 0, null);
graphics.dispose();
replayHandler.getReplayFile().writeThumb(bgrImage);
core.printInfoToChat("replaymod.chat.savedthumb");
} catch (IOException e) {
e.printStackTrace();

View File

@@ -1,10 +1,10 @@
package com.replaymod.replay.gui.screen;
import com.google.common.base.Optional;
import com.google.common.base.Supplier;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.SettableFuture;
import de.johni0702.minecraft.gui.versions.Image;
import net.minecraft.util.Formatting;
import com.replaymod.core.ReplayMod;
import com.replaymod.core.SettingsRegistry;
@@ -44,15 +44,16 @@ import org.apache.commons.io.IOCase;
import org.apache.commons.io.filefilter.SuffixFileFilter;
import org.apache.commons.lang3.StringUtils;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import static com.replaymod.replay.ReplayModReplay.LOGGER;
@@ -324,28 +325,23 @@ public class GuiReplayViewer extends GuiScreen {
for (final File file : files) {
if (Thread.interrupted()) break;
try (ReplayFile replayFile = new ZipReplayFile(new ReplayStudio(), file)) {
Optional<BufferedImage> thumb = replayFile.getThumb();
// Make sure that to int[] conversion doesn't have to occur in main thread
final BufferedImage theThumb;
if (thumb.isPresent()) {
BufferedImage buf = thumb.get();
// This is the same way minecraft calls this method, we cache the result and hand
// minecraft a BufferedImage with way simpler logic using the precomputed values
final int[] theIntArray = buf.getRGB(0, 0, buf.getWidth(), buf.getHeight(), null, 0, buf.getWidth());
theThumb = new BufferedImage(buf.getWidth(), buf.getHeight(), BufferedImage.TYPE_INT_ARGB) {
@Override
public int[] getRGB(int startX, int startY, int w, int h, int[] rgbArray, int offset, int scansize) {
System.arraycopy(theIntArray, 0, rgbArray, 0, theIntArray.length);
return null; // Minecraft doesn't use the return value
// TODO add a getThumbBytes method to ReplayStudio
final Image thumb = Optional.ofNullable(replayFile.get("thumb").orNull()).flatMap(stream -> {
try (InputStream in = stream) {
int i = 7;
while (i > 0) {
i -= in.skip(i);
}
};
} else {
theThumb = null;
}
return Optional.of(Image.read(in));
} catch (IOException e) {
e.printStackTrace();
return Optional.empty();
}
}).orElse(null);
final ReplayMetaData metaData = replayFile.getMetaData();
if (metaData != null) {
results.consume(() -> new GuiReplayEntry(file, metaData, theThumb));
results.consume(() -> new GuiReplayEntry(file, metaData, thumb));
}
} catch (Exception e) {
LOGGER.error("Could not load Replay File {}", file.getName(), e);
@@ -402,7 +398,7 @@ public class GuiReplayViewer extends GuiScreen {
private final long dateMillis;
private final boolean incompatible;
public GuiReplayEntry(File file, ReplayMetaData metaData, BufferedImage thumbImage) {
public GuiReplayEntry(File file, ReplayMetaData metaData, Image thumbImage) {
this.file = file;
name.setText(Formatting.UNDERLINE + Utils.fileNameToReplayName(file.getName()));