rework: rewrite the checking logic

* Fix a duplicate in diff place
* Add cache on HttpTexture
This commit is contained in:
Puqns67 2024-08-21 21:54:03 +08:00
parent 06277bacb0
commit a39c7a9402
Signed by: Puqns67
GPG Key ID: 9669DF042554F536
8 changed files with 104 additions and 63 deletions

View File

@ -10,7 +10,10 @@ import org.slf4j.LoggerFactory;
@Environment(EnvType.CLIENT)
public class SkinTypeFix implements ClientModInitializer {
public static final Logger LOGGER = LoggerFactory.getLogger("SkinTypeFix");
public static final String ID = "skintypefix";
public static final String NAME = "SkinTypeFix";
public static final Logger LOGGER = LoggerFactory.getLogger(NAME);
public static final Config CONFIG = ConfigLoader.get();
@Override

View File

@ -1,9 +1,11 @@
package icu.puqns67.skintypefix.accessor;
import com.mojang.blaze3d.platform.NativeImage;
import net.minecraft.client.resources.PlayerSkin;
import org.jetbrains.annotations.Nullable;
public interface HttpTextureAccessor {
void skinTypeFix$joinFuture();
NativeImage skinTypeFix$getImage();
@Nullable
PlayerSkin.Model skinTypeFix$getType();
}

View File

@ -6,6 +6,7 @@ import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
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 org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
@ -14,6 +15,8 @@ import org.spongepowered.asm.mixin.*;
import java.io.InputStream;
import java.util.concurrent.CompletableFuture;
import static icu.puqns67.skintypefix.util.Utils.checkSkinModelType;
@Environment(EnvType.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) {

View File

@ -8,7 +8,6 @@ import com.mojang.authlib.minecraft.MinecraftSessionService;
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.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.renderer.texture.TextureManager;
@ -35,11 +34,11 @@ public class SkinManagerMixin {
@Mutable
@Final
@Unique
private TextureManager textureManager;
private TextureManager skinTypeFix$textureManager;
@Inject(method = "<init>", at = @At("TAIL"))
public void onInit(TextureManager textureManager, Path path, MinecraftSessionService minecraftSessionService, Executor executor, CallbackInfo ci) {
this.textureManager = textureManager;
this.skinTypeFix$textureManager = textureManager;
}
@Inject(method = "registerTextures", at = @At("TAIL"), cancellable = true)
@ -67,31 +66,24 @@ public class SkinManagerMixin {
CompletableFuture<PlayerSkin.Model> modelFuture = skinFuture.thenApply(v -> {
// Get texture from TextureManager
var skinTexture = (HttpTextureAccessor) 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("[SkinTypeFix] [{}] Unable to get image!", uuid);
var skinModelChecked = skinTexture.skinTypeFix$getType();
if (skinModelChecked == null) {
SkinTypeFix.LOGGER.warn("[SkinTypeFix] [{}] 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("[SkinTypeFix] [{}] Fixed skin type: {} -> {}", uuid, skinModelOrigin, skinModelFixed);
return skinModelFixed;
if (skinModelOrigin != skinModelChecked) {
SkinTypeFix.LOGGER.info("[SkinTypeFix] [{}] Fixed skin type: {} -> {}", uuid, skinModelOrigin, skinModelChecked);
}
return skinModelOrigin;
return skinModelChecked;
});
// Return

View File

@ -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 org.jetbrains.annotations.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<Point> PLAYER_SKIN_SLIM = Places.PLAYER_SKIN_SLIM.points();
private static final ArrayList<Point> 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<Integer, Integer>();
var timesForSlim = new HashMap<Integer, Integer>();
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;
}
}

View File

@ -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<Square> 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<Point> points() {
var result = new ArrayList<Point>();
this.squares.forEach(v -> result.addAll(v.points()));
return result;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}