From 7241036e617906f2e572a14abd33fb9c98360701 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sun, 30 Jun 2019 11:11:02 +0200 Subject: [PATCH] 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. --- jGui | 2 +- .../java/com/replaymod/core/utils/Utils.java | 11 ++-- .../com/replaymod/core/versions/MCVer.java | 21 +++++++ .../GuiUploadScreenshot.java | 19 +------ .../advancedscreenshots/ScreenshotWriter.java | 14 ++--- .../extras/youtube/GuiYoutubeUpload.java | 21 +++---- .../com/replaymod/online/api/ApiClient.java | 7 +-- .../replaymod/online/gui/GuiReplayCenter.java | 18 ++---- .../replaymod/online/gui/GuiUploadReplay.java | 21 +++++-- .../render/blend/BlendMaterials.java | 24 ++++---- .../com/replaymod/replay/NoGuiScreenshot.java | 57 +++++++++---------- .../com/replaymod/replay/ReplayModReplay.java | 11 +++- .../replay/gui/screen/GuiReplayViewer.java | 38 ++++++------- versions/1.14.3/mapping.txt | 2 +- 14 files changed, 132 insertions(+), 134 deletions(-) diff --git a/jGui b/jGui index 86ab8ba6..4abf70a1 160000 --- a/jGui +++ b/jGui @@ -1 +1 @@ -Subproject commit 86ab8ba61d1627732e52b05fcc0a20304a57697e +Subproject commit 4abf70a15aa7f5609109bcb8ce7646d8250f7745 diff --git a/src/main/java/com/replaymod/core/utils/Utils.java b/src/main/java/com/replaymod/core/utils/Utils.java index 0a57ca59..3a55c329 100644 --- a/src/main/java/com/replaymod/core/utils/Utils.java +++ b/src/main/java/com/replaymod/core/utils/Utils.java @@ -21,6 +21,7 @@ import de.johni0702.minecraft.gui.popup.GuiInfoPopup; import de.johni0702.minecraft.gui.utils.Colors; import de.johni0702.minecraft.gui.utils.lwjgl.Dimension; import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension; +import de.johni0702.minecraft.gui.versions.Image; import de.johni0702.minecraft.gui.versions.MCVer; import net.minecraft.client.gui.screen.Screen; import net.minecraft.util.crash.CrashReport; @@ -48,11 +49,9 @@ import net.minecraft.client.util.DefaultSkinHelper; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import javax.imageio.ImageIO; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManagerFactory; -import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -89,14 +88,14 @@ public class Utils { //#endif } - public static final BufferedImage DEFAULT_THUMBNAIL; + public static final Image DEFAULT_THUMBNAIL; static { - BufferedImage thumbnail; + Image thumbnail; try { - thumbnail = ImageIO.read(getResourceAsStream("/default_thumb.jpg")); + thumbnail = Image.read(getResourceAsStream("/default_thumb.jpg")); } catch (Exception e) { - thumbnail = new BufferedImage(1, 1, BufferedImage.TYPE_3BYTE_BGR); + thumbnail = new Image(1, 1); e.printStackTrace(); } DEFAULT_THUMBNAIL = thumbnail; diff --git a/src/main/java/com/replaymod/core/versions/MCVer.java b/src/main/java/com/replaymod/core/versions/MCVer.java index bf020c34..6b0df8f8 100644 --- a/src/main/java/com/replaymod/core/versions/MCVer.java +++ b/src/main/java/com/replaymod/core/versions/MCVer.java @@ -19,6 +19,8 @@ import net.minecraft.util.SystemUtil; import net.minecraft.util.math.MathHelper; import net.minecraft.world.World; import net.minecraft.world.chunk.WorldChunk; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; //#if MC>=11400 import com.replaymod.core.mixin.AbstractButtonWidgetAccessor; @@ -90,6 +92,7 @@ import net.minecraft.SharedConstants; //#endif import java.io.File; +import java.net.URI; import java.util.Collection; import java.util.List; import java.util.concurrent.Callable; @@ -99,6 +102,8 @@ import java.util.function.Consumer; * Abstraction over things that have changed between different MC versions. */ public class MCVer { + private static Logger LOGGER = LogManager.getLogger(); + //#if MC<11400 //$$ public static IEventBus FORGE_BUS = MinecraftForge.EVENT_BUS; //#if MC>=10809 @@ -578,6 +583,22 @@ public class MCVer { //#endif } + public static void openURL(URI url) { + //#if MC>=11300 + //#if MC>=11400 + SystemUtil.getOperatingSystem().open(url); + //#else + //$$ Util.getOSType().openURI(url); + //#endif + //#else + //$$ try { + //$$ Desktop.getDesktop().browse(url); + //$$ } catch (Throwable e) { + //$$ LOGGER.error("Failed to open URL: ", e); + //$$ } + //#endif + } + //#if MC>=11300 private static Boolean hasOptifine; public static boolean hasOptifine() { diff --git a/src/main/java/com/replaymod/extras/advancedscreenshots/GuiUploadScreenshot.java b/src/main/java/com/replaymod/extras/advancedscreenshots/GuiUploadScreenshot.java index 593f20b9..1e5a1abb 100644 --- a/src/main/java/com/replaymod/extras/advancedscreenshots/GuiUploadScreenshot.java +++ b/src/main/java/com/replaymod/extras/advancedscreenshots/GuiUploadScreenshot.java @@ -2,6 +2,7 @@ package com.replaymod.extras.advancedscreenshots; import com.replaymod.core.ReplayMod; import com.replaymod.core.SettingsRegistry; +import com.replaymod.core.versions.MCVer; import com.replaymod.extras.Setting; import com.replaymod.render.RenderSettings; import de.johni0702.minecraft.gui.container.GuiContainer; @@ -14,8 +15,6 @@ import de.johni0702.minecraft.gui.layout.VerticalLayout; import de.johni0702.minecraft.gui.popup.AbstractGuiPopup; import de.johni0702.minecraft.gui.utils.lwjgl.ReadableColor; -import java.awt.*; -import java.io.IOException; import java.net.URI; public class GuiUploadScreenshot extends AbstractGuiPopup { @@ -68,22 +67,10 @@ public class GuiUploadScreenshot extends AbstractGuiPopup { } if (veer) { - veerUploadButton.onClick(() -> { - try { - Desktop.getDesktop().browse(URI.create("https://veer.tv/upload")); - } catch (IOException e) { - e.printStackTrace(); - } - }); + veerUploadButton.onClick(() -> MCVer.openURL(URI.create("https://veer.tv/upload"))); } - showOnDiskButton.onClick(() -> { - try { - Desktop.getDesktop().browse(renderSettings.getOutputFile().toURI()); - } catch (IOException e) { - e.printStackTrace(); - } - }); + showOnDiskButton.onClick(() -> MCVer.openFile(renderSettings.getOutputFile().getParentFile())); closeButton.onClick(() -> { if (neverOpenCheckbox.isChecked()) { diff --git a/src/main/java/com/replaymod/extras/advancedscreenshots/ScreenshotWriter.java b/src/main/java/com/replaymod/extras/advancedscreenshots/ScreenshotWriter.java index 185efc4c..56a93987 100644 --- a/src/main/java/com/replaymod/extras/advancedscreenshots/ScreenshotWriter.java +++ b/src/main/java/com/replaymod/extras/advancedscreenshots/ScreenshotWriter.java @@ -8,10 +8,9 @@ import com.replaymod.render.frame.RGBFrame; import com.replaymod.render.rendering.FrameConsumer; import com.replaymod.replay.ReplayModReplay; import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension; +import de.johni0702.minecraft.gui.versions.Image; import net.minecraft.util.crash.CrashReport; -import javax.imageio.ImageIO; -import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; @@ -28,23 +27,20 @@ public class ScreenshotWriter implements FrameConsumer { // skip the first frame, in which not all chunks are properly loaded if (frame.getFrameId() == 0) return; - try { - final ReadableDimension frameSize = frame.getSize(); - - BufferedImage img = new BufferedImage(frameSize.getWidth(), frameSize.getHeight(), BufferedImage.TYPE_INT_RGB); + final ReadableDimension frameSize = frame.getSize(); + try (Image img = new Image(frameSize.getWidth(), frameSize.getHeight())) { for (int y = 0; y < frameSize.getHeight(); y++) { for (int x = 0; x < frameSize.getWidth(); x++) { byte r = frame.getByteBuffer().get(); byte g = frame.getByteBuffer().get(); byte b = frame.getByteBuffer().get(); - int color = ((r & 0xff) << 16) | ((g & 0xff) << 8) | (b & 0xff); - img.setRGB(x, y, color); + img.setRGBA(x, y, r, g, b, 0xff); } } outputFile.getParentFile().mkdirs(); - ImageIO.write(img, "PNG", outputFile); + img.writePNG(outputFile); } catch (OutOfMemoryError e) { e.printStackTrace(); CrashReport report = CrashReport.create(e, "Exporting frame"); diff --git a/src/main/java/com/replaymod/extras/youtube/GuiYoutubeUpload.java b/src/main/java/com/replaymod/extras/youtube/GuiYoutubeUpload.java index 37daf104..7c228d53 100644 --- a/src/main/java/com/replaymod/extras/youtube/GuiYoutubeUpload.java +++ b/src/main/java/com/replaymod/extras/youtube/GuiYoutubeUpload.java @@ -6,6 +6,7 @@ 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.utils.Utils; +import com.replaymod.core.versions.MCVer; import com.replaymod.render.RenderSettings; import de.johni0702.minecraft.gui.GuiRenderer; import de.johni0702.minecraft.gui.RenderInfo; @@ -19,6 +20,7 @@ import de.johni0702.minecraft.gui.layout.CustomLayout; import de.johni0702.minecraft.gui.layout.VerticalLayout; import de.johni0702.minecraft.gui.popup.GuiFileChooserPopup; import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension; +import de.johni0702.minecraft.gui.versions.Image; import joptsimple.internal.Strings; import lombok.RequiredArgsConstructor; import net.minecraft.client.resource.language.I18n; @@ -34,8 +36,6 @@ import javax.annotation.Nullable; import javax.imageio.ImageIO; import javax.imageio.ImageReader; import javax.imageio.stream.ImageInputStream; -import java.awt.*; -import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; @@ -43,6 +43,7 @@ import java.io.IOException; import java.net.URL; import java.security.GeneralSecurityException; +import static com.replaymod.extras.ReplayModExtras.LOGGER; import static java.util.Arrays.asList; @RequiredArgsConstructor @@ -125,13 +126,13 @@ public class GuiYoutubeUpload extends GuiScreen { public void onSuccess(@Nullable File result) { if (result != null) { thumbnailButton.setLabel(result.getName()); - BufferedImage image; + Image image; try { thumbnailImage = IOUtils.toByteArray(new FileInputStream(result)); ImageInputStream in = ImageIO.createImageInputStream(new ByteArrayInputStream(thumbnailImage)); ImageReader reader = ImageIO.getImageReaders(in).next(); thumbnailFormat = reader.getFormatName().toLowerCase(); - image = ImageIO.read(new ByteArrayInputStream(thumbnailImage)); + image = Image.read(new ByteArrayInputStream(thumbnailImage)); } catch (Throwable t) { t.printStackTrace(); thumbnailImage = null; @@ -216,17 +217,9 @@ public class GuiYoutubeUpload extends GuiScreen { public void onSuccess(Video result) { String url = "https://youtu.be/" + result.getId(); try { - Desktop.getDesktop().browse(new URL(url).toURI()); + MCVer.openURL(new URL(url).toURI()); } catch(Throwable throwable) { - //#if MC>=11400 - SystemUtil.getOperatingSystem().open(url); - //#else - //#if MC>=11300 - //$$ Util.getOSType().openURI(url); - //#else - //$$ Sys.openURL(url); - //#endif - //#endif + LOGGER.error("Failed to open video URL \"{}\":", url, throwable); } upload = null; progressBar.setLabel(I18n.translate("replaymod.gui.ytuploadprogress.done", url)); diff --git a/src/main/java/com/replaymod/online/api/ApiClient.java b/src/main/java/com/replaymod/online/api/ApiClient.java index be3a0b7f..7b0e8315 100755 --- a/src/main/java/com/replaymod/online/api/ApiClient.java +++ b/src/main/java/com/replaymod/online/api/ApiClient.java @@ -11,13 +11,12 @@ import com.replaymod.online.AuthenticationHash; import com.replaymod.online.api.replay.ReplayModApiMethods; import com.replaymod.online.api.replay.SearchQuery; import com.replaymod.online.api.replay.holders.*; +import de.johni0702.minecraft.gui.versions.Image; import net.minecraft.client.MinecraftClient; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; -import javax.imageio.ImageIO; import javax.net.ssl.HttpsURLConnection; -import java.awt.image.BufferedImage; import java.io.BufferedInputStream; import java.io.File; import java.io.FileOutputStream; @@ -133,14 +132,14 @@ public class ApiClient { return SimpleApiClient.invokeUrl(builder.toString()); } - public BufferedImage downloadThumbnail(int file) throws IOException { + public Image downloadThumbnail(int file) throws IOException { QueryBuilder builder = new QueryBuilder(ReplayModApiMethods.get_thumbnail); builder.put("id", file); URL url = new URL(builder.toString()); HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); connection.setSSLSocketFactory(SSL_SOCKET_FACTORY); try (InputStream in = connection.getInputStream()) { - return ImageIO.read(in); + return Image.read(in); } } diff --git a/src/main/java/com/replaymod/online/gui/GuiReplayCenter.java b/src/main/java/com/replaymod/online/gui/GuiReplayCenter.java index 0dd5cfeb..c57b7700 100644 --- a/src/main/java/com/replaymod/online/gui/GuiReplayCenter.java +++ b/src/main/java/com/replaymod/online/gui/GuiReplayCenter.java @@ -39,10 +39,10 @@ import de.johni0702.minecraft.gui.utils.Colors; import de.johni0702.minecraft.gui.utils.Consumer; import de.johni0702.minecraft.gui.utils.lwjgl.Dimension; import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension; +import de.johni0702.minecraft.gui.versions.Image; import org.apache.commons.lang3.StringUtils; import net.minecraft.util.Formatting; -import java.awt.image.BufferedImage; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Collections; @@ -311,19 +311,9 @@ public class GuiReplayCenter extends GuiScreen { if (Thread.interrupted()) return; try { // Make sure that to int[] conversion doesn't have to occur in main thread - final BufferedImage theThumb; + final Image theThumb; if (fileInfo.hasThumbnail()) { - BufferedImage buf = apiClient.downloadThumbnail(fileInfo.getId()); - // 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 - } - }; + theThumb = apiClient.downloadThumbnail(fileInfo.getId()); } else { theThumb = null; } @@ -412,7 +402,7 @@ public class GuiReplayCenter extends GuiScreen { private final boolean downloaded; private final boolean incompatible; - public GuiReplayEntry(FileInfo fileInfo, BufferedImage thumbImage, int sortId, boolean downloaded) { + public GuiReplayEntry(FileInfo fileInfo, Image thumbImage, int sortId, boolean downloaded) { this.fileInfo = fileInfo; this.sortId = sortId; this.downloaded = downloaded; diff --git a/src/main/java/com/replaymod/online/gui/GuiUploadReplay.java b/src/main/java/com/replaymod/online/gui/GuiUploadReplay.java index 5bb13cc3..d2486deb 100644 --- a/src/main/java/com/replaymod/online/gui/GuiUploadReplay.java +++ b/src/main/java/com/replaymod/online/gui/GuiUploadReplay.java @@ -1,6 +1,5 @@ package com.replaymod.online.gui; -import com.google.common.base.Optional; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.replaymod.core.KeyBindingRegistry; @@ -32,6 +31,7 @@ import de.johni0702.minecraft.gui.layout.VerticalLayout; import de.johni0702.minecraft.gui.popup.AbstractGuiPopup; import de.johni0702.minecraft.gui.popup.GuiYesNoPopup; import de.johni0702.minecraft.gui.utils.Colors; +import de.johni0702.minecraft.gui.versions.Image; import net.minecraft.client.resource.language.I18n; import net.minecraft.client.options.KeyBinding; import net.minecraft.util.crash.CrashReport; @@ -45,9 +45,10 @@ import org.apache.commons.lang3.StringUtils; //#endif import javax.annotation.Nullable; -import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; +import java.io.InputStream; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -127,12 +128,24 @@ public class GuiUploadReplay extends GuiScreen { this.uploader = new FileUploader(mod.getApiClient()); ReplayMetaData metaData; - Optional optThumbnail; + Optional optThumbnail; // Read from replay file try (ReplayFile replayFile = new ZipReplayFile(new ReplayStudio(), file)) { metaData = replayFile.getMetaData(); - optThumbnail = replayFile.getThumb(); + // TODO add a getThumbBytes method to ReplayStudio + optThumbnail = Optional.ofNullable(replayFile.get("thumb").orNull()).flatMap(stream -> { + try (InputStream in = stream) { + int i = 7; + while (i > 0) { + i -= in.skip(i); + } + return Optional.of(Image.read(in)); + } catch (IOException e) { + e.printStackTrace(); + return Optional.empty(); + } + }); } catch (IOException e) { throw new CrashException(CrashReport.create(e, "Read replay file " + file.getName())); } diff --git a/src/main/java/com/replaymod/render/blend/BlendMaterials.java b/src/main/java/com/replaymod/render/blend/BlendMaterials.java index b19fe709..1248352f 100644 --- a/src/main/java/com/replaymod/render/blend/BlendMaterials.java +++ b/src/main/java/com/replaymod/render/blend/BlendMaterials.java @@ -4,13 +4,11 @@ import com.replaymod.render.blend.data.DImage; import com.replaymod.render.blend.data.DMaterial; import com.replaymod.render.blend.data.DPackedFile; import com.replaymod.render.blend.data.DTexture; +import de.johni0702.minecraft.gui.versions.Image; import net.minecraft.client.util.GlAllocationUtils; import org.apache.commons.lang3.tuple.Pair; import org.lwjgl.opengl.GL11; -import javax.imageio.ImageIO; -import java.awt.image.BufferedImage; -import java.awt.image.DataBufferByte; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.ByteBuffer; @@ -30,20 +28,22 @@ public class BlendMaterials { ByteBuffer buffer = GlAllocationUtils.allocateByteBuffer(width * height * 4); GL11.glGetTexImage(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, buffer); - // Convert to BufferedImage - BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR); - byte[] data = ((DataBufferByte) bufferedImage.getRaster().getDataBuffer()).getData(); - for (int i = 0; i < data.length; i += 4) { - data[i + 3] = buffer.get(); - data[i + 2] = buffer.get(); - data[i + 1] = buffer.get(); - data[i ] = buffer.get(); + // Convert to Image + Image bufImage = new Image(width, height); + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int a = buffer.get(); + int b = buffer.get(); + int g = buffer.get(); + int r = buffer.get(); + bufImage.setRGBA(x, y, r, b, g, a); + } } // Encode as png image ByteArrayOutputStream stream = new ByteArrayOutputStream(); try { - ImageIO.write(bufferedImage, "png", stream); + bufImage.writePNG(stream); } catch (IOException e) { throw new RuntimeException(e); // never happens unless ImageIO is bugged } diff --git a/src/main/java/com/replaymod/replay/NoGuiScreenshot.java b/src/main/java/com/replaymod/replay/NoGuiScreenshot.java index a29c767b..630f4bae 100644 --- a/src/main/java/com/replaymod/replay/NoGuiScreenshot.java +++ b/src/main/java/com/replaymod/replay/NoGuiScreenshot.java @@ -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); } } }; diff --git a/src/main/java/com/replaymod/replay/ReplayModReplay.java b/src/main/java/com/replaymod/replay/ReplayModReplay.java index bc0d297c..f565ebe4 100644 --- a/src/main/java/com/replaymod/replay/ReplayModReplay.java +++ b/src/main/java/com/replaymod/replay/ReplayModReplay.java @@ -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(); diff --git a/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java b/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java index 03e8c97e..eb0c0cf4 100755 --- a/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java +++ b/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java @@ -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 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())); diff --git a/versions/1.14.3/mapping.txt b/versions/1.14.3/mapping.txt index b435368a..fce7af0f 100644 --- a/versions/1.14.3/mapping.txt +++ b/versions/1.14.3/mapping.txt @@ -47,7 +47,7 @@ net.minecraft.client.util.Session net.minecraft.util.Session net.minecraft.client.util.Session getAccessToken() getToken() net.minecraft.server.integrated.IntegratedServer field_5524 isGamePaused net.minecraft.client.util.ScreenshotUtils net.minecraft.util.ScreenShotHelper -net.minecraft.client.util.ScreenshotUtils method_1662() saveScreenshot() +net.minecraft.client.util.ScreenshotUtils method_1663() createScreenshot() net.minecraft.client.render.ChunkRenderDispatcher net.minecraft.client.renderer.ViewFrustum net.minecraft.client.render.ChunkRenderDispatcher updateCameraPosition() updateChunkPositions() net.minecraft.client.render.block.entity.EndPortalBlockEntityRenderer net.minecraft.client.renderer.tileentity.TileEntityEndPortalRenderer