diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/OfflineAccountSkinPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/OfflineAccountSkinPane.java index 9480fb667..74e614756 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/OfflineAccountSkinPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/OfflineAccountSkinPane.java @@ -141,9 +141,9 @@ public class OfflineAccountSkinPane extends StackPane { return; } canvas.updateSkin( - result.getSkin() != null ? new Image(result.getSkin().getInputStream()) : getDefaultTexture(), + result.getSkin() != null ? result.getSkin().getImage() : getDefaultTexture(), result.getModel() == TextureModel.ALEX, - result.getCape() != null ? new Image(result.getCape().getInputStream()) : null); + result.getCape() != null ? result.getCape().getImage() : null); } }).start(); }, skinItem.selectedDataProperty(), cslApiField.textProperty(), skinSelector.valueProperty(), capeSelector.valueProperty()); diff --git a/HMCLCore/build.gradle.kts b/HMCLCore/build.gradle.kts index ced525adf..c5c94d641 100644 --- a/HMCLCore/build.gradle.kts +++ b/HMCLCore/build.gradle.kts @@ -3,6 +3,7 @@ plugins { } dependencies { + api("org.glavo:simple-png-javafx:0.3.0") api("com.google.code.gson:gson:2.8.1") api("com.moandjiezana.toml:toml4j:0.7.2") api("org.tukaani:xz:1.8") diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/Texture.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/Texture.java index 9f3412fbb..6aa2b8ec5 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/Texture.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/Texture.java @@ -17,15 +17,12 @@ */ package org.jackhuang.hmcl.auth.offline; -import javax.imageio.IIOException; -import javax.imageio.ImageIO; -import java.awt.image.BufferedImage; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; +import javafx.scene.image.Image; +import javafx.scene.image.PixelReader; +import org.jackhuang.hmcl.util.Hex; + import java.io.IOException; import java.io.InputStream; -import java.math.BigInteger; -import java.net.URL; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.HashMap; @@ -33,29 +30,21 @@ import java.util.Map; import static java.util.Objects.requireNonNull; -public class Texture { +public final class Texture { private final String hash; - private final byte[] data; + private final Image image; - public Texture(String hash, byte[] data) { + public Texture(String hash, Image image) { this.hash = requireNonNull(hash); - this.data = requireNonNull(data); - } - - public byte[] getData() { - return data; + this.image = requireNonNull(image); } public String getHash() { return hash; } - public InputStream getInputStream() { - return new ByteArrayInputStream(data); - } - - public int getLength() { - return data.length; + public Image getImage() { + return image; } private static final Map textures = new HashMap<>(); @@ -68,15 +57,17 @@ public class Texture { return textures.get(hash); } - private static String computeTextureHash(BufferedImage img) { + private static String computeTextureHash(Image img) { MessageDigest digest; try { digest = MessageDigest.getInstance("SHA-256"); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } - int width = img.getWidth(); - int height = img.getHeight(); + + PixelReader reader = img.getPixelReader(); + int width = (int) img.getWidth(); + int height = (int) img.getHeight(); byte[] buf = new byte[4096]; putInt(buf, 0, width); @@ -84,7 +75,7 @@ public class Texture { int pos = 8; for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { - putInt(buf, pos, img.getRGB(x, y)); + putInt(buf, pos, reader.getArgb(x, y)); if (buf[pos + 0] == 0) { buf[pos + 1] = buf[pos + 2] = buf[pos + 3] = 0; } @@ -99,8 +90,7 @@ public class Texture { digest.update(buf, 0, pos); } - byte[] sha256 = digest.digest(); - return String.format("%0" + (sha256.length << 1) + "x", new BigInteger(1, sha256)); + return Hex.encodeHex(digest.digest()); } private static void putInt(byte[] array, int offset, int x) { @@ -112,25 +102,28 @@ public class Texture { public static Texture loadTexture(InputStream in) throws IOException { if (in == null) return null; - BufferedImage img; + Image img; try (InputStream is = in) { - img = ImageIO.read(is); - } - if (img == null) { - throw new IIOException("No image found"); + img = new Image(is); } - String hash = computeTextureHash(img); + if (img.isError()) { + throw new IOException("No image found", img.getException()); + } + return loadTexture(img); + } + + public static Texture loadTexture(Image image) { + if (image == null) return null; + + String hash = computeTextureHash(image); Texture existent = textures.get(hash); if (existent != null) { return existent; } - ByteArrayOutputStream buf = new ByteArrayOutputStream(); - ImageIO.write(img, "png", buf); - Texture texture = new Texture(hash, buf.toByteArray()); - + Texture texture = new Texture(hash, image); existent = textures.putIfAbsent(hash, texture); if (existent != null) { @@ -139,9 +132,4 @@ public class Texture { return texture; } - public static Texture loadTexture(String url) throws IOException { - if (url == null) return null; - return loadTexture(new URL(url).openStream()); - } - } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/YggdrasilServer.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/YggdrasilServer.java index 7fe17eb3a..fb546af3c 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/YggdrasilServer.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/YggdrasilServer.java @@ -18,6 +18,7 @@ package org.jackhuang.hmcl.auth.offline; import com.google.gson.reflect.TypeToken; +import org.glavo.png.javafx.PNGJavaFXUtils; import org.jackhuang.hmcl.auth.yggdrasil.GameProfile; import org.jackhuang.hmcl.auth.yggdrasil.TextureModel; import org.jackhuang.hmcl.util.KeyUtils; @@ -26,6 +27,7 @@ import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.gson.UUIDTypeAdapter; import org.jackhuang.hmcl.util.io.HttpServer; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.security.*; import java.util.*; @@ -127,7 +129,8 @@ public class YggdrasilServer extends HttpServer { if (Texture.hasTexture(hash)) { Texture texture = Texture.getTexture(hash); - Response response = newFixedLengthResponse(Response.Status.OK, "image/png", texture.getInputStream(), texture.getLength()); + byte[] data = PNGJavaFXUtils.writeImageToArray(texture.getImage()); + Response response = newFixedLengthResponse(Response.Status.OK, "image/png", new ByteArrayInputStream(data), data.length); response.addHeader("Etag", String.format("\"%s\"", hash)); response.addHeader("Cache-Control", "max-age=2592000, public"); return response;