mirror of
https://github.com/HMCL-dev/HMCL.git
synced 2024-12-21 07:00:22 +08:00
Update NormalizedSkin (#2036)
* Update NormalizedSkin * Add tests * update * update
This commit is contained in:
parent
3d9c9689e3
commit
c86302df58
@ -25,6 +25,7 @@ import javafx.beans.property.StringProperty;
|
|||||||
import javafx.beans.value.ObservableBooleanValue;
|
import javafx.beans.value.ObservableBooleanValue;
|
||||||
import javafx.scene.control.RadioButton;
|
import javafx.scene.control.RadioButton;
|
||||||
import javafx.scene.control.Skin;
|
import javafx.scene.control.Skin;
|
||||||
|
import javafx.scene.image.Image;
|
||||||
import javafx.stage.FileChooser;
|
import javafx.stage.FileChooser;
|
||||||
import org.jackhuang.hmcl.auth.Account;
|
import org.jackhuang.hmcl.auth.Account;
|
||||||
import org.jackhuang.hmcl.auth.AuthenticationException;
|
import org.jackhuang.hmcl.auth.AuthenticationException;
|
||||||
@ -47,9 +48,8 @@ import org.jackhuang.hmcl.util.skin.InvalidSkinException;
|
|||||||
import org.jackhuang.hmcl.util.skin.NormalizedSkin;
|
import org.jackhuang.hmcl.util.skin.NormalizedSkin;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
|
||||||
import java.awt.image.BufferedImage;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@ -167,14 +167,14 @@ public class AccountListItem extends RadioButton {
|
|||||||
|
|
||||||
return refreshAsync()
|
return refreshAsync()
|
||||||
.thenRunAsync(() -> {
|
.thenRunAsync(() -> {
|
||||||
BufferedImage skinImg;
|
Image skinImg;
|
||||||
try {
|
try (FileInputStream input = new FileInputStream(selectedFile)) {
|
||||||
skinImg = ImageIO.read(selectedFile);
|
skinImg = new Image(input);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new InvalidSkinException("Failed to read skin image", e);
|
throw new InvalidSkinException("Failed to read skin image", e);
|
||||||
}
|
}
|
||||||
if (skinImg == null) {
|
if (skinImg.isError()) {
|
||||||
throw new InvalidSkinException("Failed to read skin image");
|
throw new InvalidSkinException("Failed to read skin image", skinImg.getException());
|
||||||
}
|
}
|
||||||
NormalizedSkin skin = new NormalizedSkin(skinImg);
|
NormalizedSkin skin = new NormalizedSkin(skinImg);
|
||||||
String model = skin.isSlim() ? "slim" : "";
|
String model = skin.isSlim() ? "slim" : "";
|
||||||
|
@ -26,7 +26,9 @@ public final class JavaFXLauncher {
|
|||||||
private JavaFXLauncher() {
|
private JavaFXLauncher() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void start() {
|
private static boolean started = false;
|
||||||
|
|
||||||
|
static {
|
||||||
// init JavaFX Toolkit
|
// init JavaFX Toolkit
|
||||||
try {
|
try {
|
||||||
// Java 9 or Latter
|
// Java 9 or Latter
|
||||||
@ -35,12 +37,26 @@ public final class JavaFXLauncher {
|
|||||||
javafx.application.Platform.class, "startup", MethodType.methodType(void.class, Runnable.class));
|
javafx.application.Platform.class, "startup", MethodType.methodType(void.class, Runnable.class));
|
||||||
startup.invokeExact((Runnable) () -> {
|
startup.invokeExact((Runnable) () -> {
|
||||||
});
|
});
|
||||||
} catch (Throwable e) {
|
started = true;
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
// Java 8
|
// Java 8
|
||||||
try {
|
try {
|
||||||
Class.forName("javafx.embed.swing.JFXPanel").getDeclaredConstructor().newInstance();
|
Class.forName("javafx.embed.swing.JFXPanel").getDeclaredConstructor().newInstance();
|
||||||
} catch (Throwable ignored) {
|
started = true;
|
||||||
|
} catch (Throwable e0) {
|
||||||
|
e0.printStackTrace();
|
||||||
|
}
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
started = true;
|
||||||
|
} catch (Throwable e) {
|
||||||
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void start() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isStarted() {
|
||||||
|
return started;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,10 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.util.skin;
|
package org.jackhuang.hmcl.util.skin;
|
||||||
|
|
||||||
import java.awt.image.BufferedImage;
|
import javafx.scene.image.Image;
|
||||||
|
import javafx.scene.image.PixelReader;
|
||||||
|
import javafx.scene.image.PixelWriter;
|
||||||
|
import javafx.scene.image.WritableImage;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Describes a Minecraft 1.8+ skin (64x64).
|
* Describes a Minecraft 1.8+ skin (64x64).
|
||||||
@ -27,26 +30,29 @@ import java.awt.image.BufferedImage;
|
|||||||
*/
|
*/
|
||||||
public class NormalizedSkin {
|
public class NormalizedSkin {
|
||||||
|
|
||||||
private static void copyImage(BufferedImage src, BufferedImage dst, int sx, int sy, int dx, int dy, int w, int h, boolean flipHorizontal) {
|
private static void copyImage(Image src, WritableImage dst, int sx, int sy, int dx, int dy, int w, int h, boolean flipHorizontal) {
|
||||||
|
PixelReader reader = src.getPixelReader();
|
||||||
|
PixelWriter writer = dst.getPixelWriter();
|
||||||
|
|
||||||
for (int y = 0; y < h; y++) {
|
for (int y = 0; y < h; y++) {
|
||||||
for (int x = 0; x < w; x++) {
|
for (int x = 0; x < w; x++) {
|
||||||
int pixel = src.getRGB(sx + x, sy + y);
|
int pixel = reader.getArgb(sx + x, sy + y);
|
||||||
dst.setRGB(dx + (flipHorizontal ? w - x - 1 : x), dy + y, pixel);
|
writer.setArgb(dx + (flipHorizontal ? w - x - 1 : x), dy + y, pixel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final BufferedImage texture;
|
private final Image texture;
|
||||||
private final BufferedImage normalizedTexture;
|
private final WritableImage normalizedTexture;
|
||||||
private final int scale;
|
private final int scale;
|
||||||
private final boolean oldFormat;
|
private final boolean oldFormat;
|
||||||
|
|
||||||
public NormalizedSkin(BufferedImage texture) throws InvalidSkinException {
|
public NormalizedSkin(Image texture) throws InvalidSkinException {
|
||||||
this.texture = texture;
|
this.texture = texture;
|
||||||
|
|
||||||
// check format
|
// check format
|
||||||
int w = texture.getWidth();
|
int w = (int) texture.getWidth();
|
||||||
int h = texture.getHeight();
|
int h = (int) texture.getHeight();
|
||||||
if (w % 64 != 0) {
|
if (w % 64 != 0) {
|
||||||
throw new InvalidSkinException("Invalid size " + w + "x" + h);
|
throw new InvalidSkinException("Invalid size " + w + "x" + h);
|
||||||
}
|
}
|
||||||
@ -61,7 +67,7 @@ public class NormalizedSkin {
|
|||||||
// compute scale
|
// compute scale
|
||||||
scale = w / 64;
|
scale = w / 64;
|
||||||
|
|
||||||
normalizedTexture = new BufferedImage(w, w, BufferedImage.TYPE_INT_ARGB);
|
normalizedTexture = new WritableImage(w, w);
|
||||||
copyImage(texture, normalizedTexture, 0, 0, 0, 0, w, h, false);
|
copyImage(texture, normalizedTexture, 0, 0, 0, 0, w, h, false);
|
||||||
if (oldFormat) {
|
if (oldFormat) {
|
||||||
convertOldSkin();
|
convertOldSkin();
|
||||||
@ -87,11 +93,11 @@ public class NormalizedSkin {
|
|||||||
copyImage(normalizedTexture, normalizedTexture, sx * scale, sy * scale, dx * scale, dy * scale, w * scale, h * scale, flipHorizontal);
|
copyImage(normalizedTexture, normalizedTexture, sx * scale, sy * scale, dx * scale, dy * scale, w * scale, h * scale, flipHorizontal);
|
||||||
}
|
}
|
||||||
|
|
||||||
public BufferedImage getOriginalTexture() {
|
public Image getOriginalTexture() {
|
||||||
return texture;
|
return texture;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BufferedImage getNormalizedTexture() {
|
public Image getNormalizedTexture() {
|
||||||
return normalizedTexture;
|
return normalizedTexture;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,10 +109,6 @@ public class NormalizedSkin {
|
|||||||
return oldFormat;
|
return oldFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests whether the skin is slim.
|
|
||||||
* Note that this method doesn't guarantee the result is correct.
|
|
||||||
*/
|
|
||||||
public boolean isSlim() {
|
public boolean isSlim() {
|
||||||
return (hasTransparencyRelative(50, 16, 2, 4) ||
|
return (hasTransparencyRelative(50, 16, 2, 4) ||
|
||||||
hasTransparencyRelative(54, 20, 2, 12) ||
|
hasTransparencyRelative(54, 20, 2, 12) ||
|
||||||
@ -119,13 +121,14 @@ public class NormalizedSkin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean hasTransparencyRelative(int x0, int y0, int w, int h) {
|
private boolean hasTransparencyRelative(int x0, int y0, int w, int h) {
|
||||||
|
PixelReader reader = normalizedTexture.getPixelReader();
|
||||||
x0 *= scale;
|
x0 *= scale;
|
||||||
y0 *= scale;
|
y0 *= scale;
|
||||||
w *= scale;
|
w *= scale;
|
||||||
h *= scale;
|
h *= scale;
|
||||||
for (int y = 0; y < h; y++) {
|
for (int y = 0; y < h; y++) {
|
||||||
for (int x = 0; x < w; x++) {
|
for (int x = 0; x < w; x++) {
|
||||||
int pixel = normalizedTexture.getRGB(x0 + x, y0 + y);
|
int pixel = reader.getArgb(x0 + x, y0 + y);
|
||||||
if (pixel >>> 24 != 0xff) {
|
if (pixel >>> 24 != 0xff) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -135,13 +138,14 @@ public class NormalizedSkin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean isAreaBlackRelative(int x0, int y0, int w, int h) {
|
private boolean isAreaBlackRelative(int x0, int y0, int w, int h) {
|
||||||
|
PixelReader reader = normalizedTexture.getPixelReader();
|
||||||
x0 *= scale;
|
x0 *= scale;
|
||||||
y0 *= scale;
|
y0 *= scale;
|
||||||
w *= scale;
|
w *= scale;
|
||||||
h *= scale;
|
h *= scale;
|
||||||
for (int y = 0; y < h; y++) {
|
for (int y = 0; y < h; y++) {
|
||||||
for (int x = 0; x < w; x++) {
|
for (int x = 0; x < w; x++) {
|
||||||
int pixel = normalizedTexture.getRGB(x0 + x, y0 + y);
|
int pixel = reader.getArgb(x0 + x, y0 + y);
|
||||||
if (pixel != 0xff000000) {
|
if (pixel != 0xff000000) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,9 @@ public final class JavaFXLauncher {
|
|||||||
private JavaFXLauncher() {
|
private JavaFXLauncher() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void start() {
|
private static boolean started = false;
|
||||||
|
|
||||||
|
static {
|
||||||
// init JavaFX Toolkit
|
// init JavaFX Toolkit
|
||||||
try {
|
try {
|
||||||
// Java 9 or Latter
|
// Java 9 or Latter
|
||||||
@ -35,12 +37,26 @@ public final class JavaFXLauncher {
|
|||||||
javafx.application.Platform.class, "startup", MethodType.methodType(void.class, Runnable.class));
|
javafx.application.Platform.class, "startup", MethodType.methodType(void.class, Runnable.class));
|
||||||
startup.invokeExact((Runnable) () -> {
|
startup.invokeExact((Runnable) () -> {
|
||||||
});
|
});
|
||||||
} catch (Throwable e) {
|
started = true;
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
// Java 8
|
// Java 8
|
||||||
try {
|
try {
|
||||||
Class.forName("javafx.embed.swing.JFXPanel").getDeclaredConstructor().newInstance();
|
Class.forName("javafx.embed.swing.JFXPanel").getDeclaredConstructor().newInstance();
|
||||||
} catch (Throwable ignored) {
|
started = true;
|
||||||
|
} catch (Throwable e0) {
|
||||||
|
e0.printStackTrace();
|
||||||
|
}
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
started = true;
|
||||||
|
} catch (Throwable e) {
|
||||||
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void start() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isStarted() {
|
||||||
|
return started;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,13 +17,13 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.util;
|
package org.jackhuang.hmcl.util;
|
||||||
|
|
||||||
import org.jackhuang.hmcl.JavaFXLauncher;
|
|
||||||
import org.jackhuang.hmcl.task.Schedulers;
|
import org.jackhuang.hmcl.task.Schedulers;
|
||||||
import org.jackhuang.hmcl.task.Task;
|
import org.jackhuang.hmcl.task.Task;
|
||||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||||
import org.jackhuang.hmcl.util.platform.JavaVersion;
|
import org.jackhuang.hmcl.util.platform.JavaVersion;
|
||||||
import org.junit.jupiter.api.Assertions;
|
import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.condition.EnabledIf;
|
||||||
|
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
@ -75,8 +75,9 @@ public class TaskTest {
|
|||||||
assertTrue(bool.get(), "withRunAsync should be executed");
|
assertTrue(bool.get(), "withRunAsync should be executed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnabledIf("org.jackhuang.hmcl.JavaFXLauncher#isStarted")
|
||||||
public void testThenAccept() {
|
public void testThenAccept() {
|
||||||
JavaFXLauncher.start();
|
|
||||||
AtomicBoolean flag = new AtomicBoolean();
|
AtomicBoolean flag = new AtomicBoolean();
|
||||||
boolean result = Task.supplyAsync(JavaVersion::fromCurrentEnvironment)
|
boolean result = Task.supplyAsync(JavaVersion::fromCurrentEnvironment)
|
||||||
.thenAcceptAsync(Schedulers.javafx(), javaVersion -> {
|
.thenAcceptAsync(Schedulers.javafx(), javaVersion -> {
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
package org.jackhuang.hmcl.util.skin;
|
||||||
|
|
||||||
|
import javafx.scene.image.Image;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.condition.EnabledIf;
|
||||||
|
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
public class NormalizedSkinTest {
|
||||||
|
private static NormalizedSkin getSkin(String name) throws InvalidSkinException {
|
||||||
|
String path = Paths.get("../HMCL/src/main/resources/assets/img/skin/" + name + ".png").normalize().toAbsolutePath().toUri().toString();
|
||||||
|
return new NormalizedSkin(new Image(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnabledIf("org.jackhuang.hmcl.JavaFXLauncher#isStarted")
|
||||||
|
public void testIsSlim() throws Exception {
|
||||||
|
assertFalse(getSkin("steve").isSlim());
|
||||||
|
assertTrue(getSkin("alex").isSlim());
|
||||||
|
assertTrue(getSkin("noor").isSlim());
|
||||||
|
assertFalse(getSkin("sunny").isSlim());
|
||||||
|
assertFalse(getSkin("ari").isSlim());
|
||||||
|
assertFalse(getSkin("zuri").isSlim());
|
||||||
|
assertTrue(getSkin("makena").isSlim());
|
||||||
|
assertFalse(getSkin("kai").isSlim());
|
||||||
|
assertTrue(getSkin("efe").isSlim());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user