diff --git a/src/main/java/icu/puqns67/skintypefix/SkinTypeFix.java b/src/main/java/icu/puqns67/skintypefix/SkinTypeFix.java index d870426..9c4dac9 100644 --- a/src/main/java/icu/puqns67/skintypefix/SkinTypeFix.java +++ b/src/main/java/icu/puqns67/skintypefix/SkinTypeFix.java @@ -14,7 +14,9 @@ import org.slf4j.LoggerFactory; @Mod(SkinTypeFix.ID) public class SkinTypeFix { public static final String ID = "skintypefix"; - public static final Logger LOGGER = LoggerFactory.getLogger(ID); + public static final String NAME = "SkinTypeFix"; + + public static final Logger LOGGER = LoggerFactory.getLogger(NAME); public SkinTypeFix(IEventBus modEventBus, ModContainer modContainer) { modEventBus.addListener(this::setup); diff --git a/src/main/java/icu/puqns67/skintypefix/accessor/HttpTextureAccessor.java b/src/main/java/icu/puqns67/skintypefix/accessor/HttpTextureAccessor.java index 6ab2093..85703fa 100644 --- a/src/main/java/icu/puqns67/skintypefix/accessor/HttpTextureAccessor.java +++ b/src/main/java/icu/puqns67/skintypefix/accessor/HttpTextureAccessor.java @@ -1,9 +1,12 @@ package icu.puqns67.skintypefix.accessor; -import com.mojang.blaze3d.platform.NativeImage; +import net.minecraft.client.resources.PlayerSkin; + +import javax.annotation.Nullable; public interface HttpTextureAccessor { void skinTypeFix$joinFuture(); - NativeImage skinTypeFix$getImage(); + @Nullable + PlayerSkin.Model skinTypeFix$getType(); } diff --git a/src/main/java/icu/puqns67/skintypefix/mixin/HttpTextureMixin.java b/src/main/java/icu/puqns67/skintypefix/mixin/HttpTextureMixin.java index 5807e4b..3dbdf19 100644 --- a/src/main/java/icu/puqns67/skintypefix/mixin/HttpTextureMixin.java +++ b/src/main/java/icu/puqns67/skintypefix/mixin/HttpTextureMixin.java @@ -4,6 +4,7 @@ import com.mojang.blaze3d.platform.NativeImage; import icu.puqns67.skintypefix.accessor.HttpTextureAccessor; import net.minecraft.client.renderer.texture.HttpTexture; import net.minecraft.client.renderer.texture.SimpleTexture; +import net.minecraft.client.resources.PlayerSkin; import net.minecraft.resources.ResourceLocation; import net.neoforged.api.distmarker.Dist; import net.neoforged.api.distmarker.OnlyIn; @@ -14,6 +15,8 @@ import javax.annotation.Nullable; import java.io.InputStream; import java.util.concurrent.CompletableFuture; +import static icu.puqns67.skintypefix.util.Utils.checkSkinModelType; + @OnlyIn(Dist.CLIENT) @Mixin(HttpTexture.class) public abstract class HttpTextureMixin extends SimpleTexture implements HttpTextureAccessor { @@ -29,6 +32,9 @@ public abstract class HttpTextureMixin extends SimpleTexture implements HttpText @Shadow @Final private boolean processLegacySkin; + @Unique + @Nullable + private PlayerSkin.Model skinTypeFix$type = null; public HttpTextureMixin(ResourceLocation location) { super(location); @@ -45,10 +51,25 @@ public abstract class HttpTextureMixin extends SimpleTexture implements HttpText } } + @Unique + private void setSkinTypeFix$closeImage() { + if (this.skinTypeFix$image != null) { + this.skinTypeFix$image.close(); + this.skinTypeFix$image = null; + } + } + @Unique @Nullable - public NativeImage skinTypeFix$getImage() { - return this.skinTypeFix$image; + public PlayerSkin.Model skinTypeFix$getType() { + if (this.skinTypeFix$type == null) { + if (this.skinTypeFix$image == null) { + return null; + } + this.skinTypeFix$type = checkSkinModelType(this.skinTypeFix$image); + this.setSkinTypeFix$closeImage(); + } + return this.skinTypeFix$type; } /** @@ -61,11 +82,16 @@ public abstract class HttpTextureMixin extends SimpleTexture implements HttpText try { var result = NativeImage.read(stream); if (this.processLegacySkin) { - // If this.processLegacySkin is true, the image is the player's skin, so a backup needs to be created for check - this.skinTypeFix$image = new NativeImage(64, 64, true); - this.skinTypeFix$image.copyFrom(result); - result = this.processLegacySkin(result); + + if (result != null) { + this.setSkinTypeFix$closeImage(); + + // If this.processLegacySkin is true, the image is the player's skin, + // so a backup needs to be created for check. + this.skinTypeFix$image = new NativeImage(64, 64, true); + this.skinTypeFix$image.copyFrom(result); + } } return result; } catch (Exception e) { diff --git a/src/main/java/icu/puqns67/skintypefix/mixin/SkinManagerMixin.java b/src/main/java/icu/puqns67/skintypefix/mixin/SkinManagerMixin.java index a0681f0..918f545 100644 --- a/src/main/java/icu/puqns67/skintypefix/mixin/SkinManagerMixin.java +++ b/src/main/java/icu/puqns67/skintypefix/mixin/SkinManagerMixin.java @@ -9,7 +9,6 @@ import icu.puqns67.skintypefix.Config; import icu.puqns67.skintypefix.SkinTypeFix; import icu.puqns67.skintypefix.accessor.HttpTextureAccessor; import icu.puqns67.skintypefix.util.Utils; -import icu.puqns67.skintypefix.util.image.Places; import net.minecraft.client.renderer.texture.TextureManager; import net.minecraft.client.resources.PlayerSkin; import net.minecraft.client.resources.SkinManager; @@ -68,31 +67,24 @@ public class SkinManagerMixin { CompletableFuture modelFuture = skinFuture.thenApply(v -> { // Get texture from TextureManager - var skinTexture = (HttpTextureAccessor) skinTypeFix$textureManager.getTexture(skinFuture.join()); + var skinTexture = (HttpTextureAccessor) this.skinTypeFix$textureManager.getTexture(skinFuture.join()); // Wait skin loading if it needed fetch from web skinTexture.skinTypeFix$joinFuture(); // Get image from PlayerSkinTexture - var skinImage = skinTexture.skinTypeFix$getImage(); - if (skinImage == null) { - SkinTypeFix.LOGGER.warn("[{}] Unable to get image!", uuid); + var skinModelChecked = skinTexture.skinTypeFix$getType(); + + if (skinModelChecked == null) { + SkinTypeFix.LOGGER.warn("[{}] Unable to get skin type, using original skin type!", uuid); return skinModelOrigin; } - // Check skin type - var needFix = switch (skinModelOrigin) { - case SLIM -> !Places.DIFF_PLAYER_SKIN.hasTransparent(skinImage); - case WIDE -> Places.DIFF_PLAYER_SKIN.hasTransparent(skinImage); - }; - - if (needFix) { - var skinModelFixed = Utils.reverseModelType(skinModelOrigin); - SkinTypeFix.LOGGER.info("[{}] Fixed skin type: {} -> {}", uuid, skinModelOrigin, skinModelFixed); - return skinModelFixed; + if (skinModelOrigin != skinModelChecked) { + SkinTypeFix.LOGGER.info("[{}] Fixed skin type: {} -> {}", uuid, skinModelOrigin, skinModelChecked); } - return skinModelOrigin; + return skinModelChecked; }); // Return diff --git a/src/main/java/icu/puqns67/skintypefix/util/Utils.java b/src/main/java/icu/puqns67/skintypefix/util/Utils.java index 8d87937..3c7b6e3 100644 --- a/src/main/java/icu/puqns67/skintypefix/util/Utils.java +++ b/src/main/java/icu/puqns67/skintypefix/util/Utils.java @@ -1,19 +1,47 @@ package icu.puqns67.skintypefix.util; +import com.mojang.blaze3d.platform.NativeImage; +import icu.puqns67.skintypefix.SkinTypeFix; +import icu.puqns67.skintypefix.util.image.Places; +import icu.puqns67.skintypefix.util.image.Point; import net.minecraft.Util; import net.minecraft.client.resources.PlayerSkin; +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.HashMap; import java.util.UUID; public class Utils { - public static PlayerSkin.Model reverseModelType(PlayerSkin.Model type) { - return switch (type) { - case SLIM -> PlayerSkin.Model.WIDE; - case WIDE -> PlayerSkin.Model.SLIM; - }; - } + private static final ArrayList PLAYER_SKIN_SLIM = Places.PLAYER_SKIN_SLIM.points(); + private static final ArrayList PLAYER_SKIN_DIFF_SLIM_TO_WILD = Places.PLAYER_SKIN_DIFF_SLIM_TO_WILD.points(); public static boolean isInvalidUUID(UUID uuid) { return Util.NIL_UUID.equals(uuid); } + + @Nullable + public static PlayerSkin.Model checkSkinModelType(NativeImage image) { + SkinTypeFix.LOGGER.debug("Checking: {}", image); + + var timesForDiff = new HashMap(); + var timesForSlim = new HashMap(); + PlayerSkin.Model result = null; + + // The result of NativeImage.getPixelRGBA() is ARGB, not RGBA, like 0xAARRGGBB + PLAYER_SKIN_DIFF_SLIM_TO_WILD.forEach(p -> timesForDiff.merge(image.getPixelRGBA(p.x(), p.y()), 1, Integer::sum)); + PLAYER_SKIN_SLIM.forEach(p -> timesForSlim.merge(image.getPixelRGBA(p.x(), p.y()), 1, Integer::sum)); + + var blackTimesForDiff = timesForDiff.getOrDefault(0xff000000, 0); + var blackTimesForSlim = timesForSlim.getOrDefault(0xff000000, 0); + + // Rules for check skin module type + if (blackTimesForDiff == 64 && blackTimesForSlim < 256) { + result = PlayerSkin.Model.SLIM; + } else if (blackTimesForDiff == 0 || timesForDiff.size() >= 4) { + result = PlayerSkin.Model.WIDE; + } + + return result; + } } diff --git a/src/main/java/icu/puqns67/skintypefix/util/image/Places.java b/src/main/java/icu/puqns67/skintypefix/util/image/Places.java index e2f8fa7..05497dc 100644 --- a/src/main/java/icu/puqns67/skintypefix/util/image/Places.java +++ b/src/main/java/icu/puqns67/skintypefix/util/image/Places.java @@ -1,20 +1,30 @@ package icu.puqns67.skintypefix.util.image; -import com.mojang.blaze3d.platform.NativeImage; - import java.util.ArrayList; public class Places { - public static final Places DIFF_PLAYER_SKIN = Places.diffPlayerSkin(); + public static final Places PLAYER_SKIN_SLIM = Places.slimPlayerSkin(); + public static final Places PLAYER_SKIN_DIFF_SLIM_TO_WILD = Places.diffPlayerSkinSlimToWild(); + private final ArrayList squares = new ArrayList<>(); - public Places() { + private static Places slimPlayerSkin() { + var result = new Places(); + result.add(8, 0, 23, 7); + result.add(0, 8, 31, 15); + result.add(4, 16, 11, 19); + result.add(20, 16, 35, 19); + result.add(44, 16, 51, 19); + result.add(0, 20, 55, 31); + result.add(20, 48, 27, 51); + result.add(36, 48, 43, 51); + result.add(16, 52, 47, 63); + return result; } - public static Places diffPlayerSkin() { + private static Places diffPlayerSkinSlimToWild() { var result = new Places(); result.add(50, 16, 51, 19); - result.add(50, 16, 51, 19); result.add(54, 20, 55, 31); result.add(42, 48, 43, 51); result.add(46, 52, 47, 63); @@ -29,12 +39,9 @@ public class Places { this.add(new Square(x1, y1, x2, y2)); } - public boolean hasTransparent(NativeImage image) { - for (var square : this.squares) { - if (!square.hasTransparent(image)) { - return false; - } - } - return true; + public ArrayList points() { + var result = new ArrayList(); + this.squares.forEach(v -> result.addAll(v.points())); + return result; } } diff --git a/src/main/java/icu/puqns67/skintypefix/util/image/Point.java b/src/main/java/icu/puqns67/skintypefix/util/image/Point.java index e77e242..f80e171 100644 --- a/src/main/java/icu/puqns67/skintypefix/util/image/Point.java +++ b/src/main/java/icu/puqns67/skintypefix/util/image/Point.java @@ -1,15 +1,9 @@ package icu.puqns67.skintypefix.util.image; -import com.mojang.blaze3d.platform.NativeImage; - public record Point(int x, int y) { public Point { if (x < 0 || y < 0) { throw new IllegalArgumentException(String.format("Invalid position: %d, %d", x, y)); } } - - public boolean isTransparent(NativeImage image) { - return image.getPixelRGBA(x, y) >>> 24 != 0xff; - } } diff --git a/src/main/java/icu/puqns67/skintypefix/util/image/Square.java b/src/main/java/icu/puqns67/skintypefix/util/image/Square.java index 5b1a984..f04146a 100644 --- a/src/main/java/icu/puqns67/skintypefix/util/image/Square.java +++ b/src/main/java/icu/puqns67/skintypefix/util/image/Square.java @@ -1,7 +1,5 @@ package icu.puqns67.skintypefix.util.image; -import com.mojang.blaze3d.platform.NativeImage; - import java.util.ArrayList; public record Square(Point p1, Point p2) { @@ -27,13 +25,4 @@ public record Square(Point p1, Point p2) { } return result; } - - public boolean hasTransparent(NativeImage image) { - for (var point : this.points()) { - if (!point.isTransparent(image)) { - return false; - } - } - return true; - } }