Skip to content

Commit aa50a59

Browse files
committed
oneclient icon replacing (no 1.13+ yet)
1 parent 10a86c0 commit aa50a59

File tree

7 files changed

+243
-18
lines changed

7 files changed

+243
-18
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package org.polyfrost.polyplus.mixin;
2+
3+
import net.minecraft.client.Minecraft;
4+
import org.lwjgl.opengl.Display;
5+
import org.polyfrost.polyplus.client.PolyPlusClient;
6+
import org.polyfrost.polyplus.client.utils.IconLoader;
7+
import org.spongepowered.asm.mixin.Mixin;
8+
import org.spongepowered.asm.mixin.injection.At;
9+
import org.spongepowered.asm.mixin.injection.Inject;
10+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
11+
12+
import javax.imageio.ImageIO;
13+
import javax.swing.*;
14+
import java.io.InputStream;
15+
import java.util.Objects;
16+
17+
@Mixin(value = Minecraft.class, priority = Integer.MIN_VALUE)
18+
public class Mixin_ReplaceIcon {
19+
20+
@Inject(method = "setWindowIcon", at = @At("HEAD"), cancellable = true)
21+
private void setWindowIcon(CallbackInfo ci) {
22+
try (InputStream stream = PolyPlusClient.class.getResourceAsStream("/assets/polyplus/PolyPlusIcon.png")) {
23+
Display.setIcon(IconLoader.load(ImageIO.read(Objects.requireNonNull(stream))));
24+
ci.cancel();
25+
} catch (Exception e) {
26+
e.printStackTrace();
27+
}
28+
}
29+
}
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package org.polyfrost.polyplus.client.utils
2+
3+
4+
import dev.deftu.omnicore.api.client.OmniDesktop
5+
import java.awt.image.BufferedImage
6+
import java.io.File
7+
import java.io.IOException
8+
import java.io.InputStream
9+
import java.nio.ByteBuffer
10+
import javax.imageio.ImageIO
11+
12+
13+
/*****************************************************************************
14+
* A convenience class for loading icons from images.
15+
*
16+
* Icons loaded from this class are formatted to fit within the required
17+
* dimension (16x16, 32x32, or 128x128). If the source image is larger than the
18+
* target dimension, it is shrunk down to the minimum size that will fit. If it
19+
* is smaller, then it is only scaled up if the new scale can be a per-pixel
20+
* linear scale (i.e., x2, x3, x4, etc). In both cases, the image's width/height
21+
* ratio is kept the same as the source image.
22+
*
23+
* @author Chris Molini
24+
*/
25+
object IconLoader {
26+
/*************************************************************************
27+
* Loads an icon in ByteBuffer form.
28+
*
29+
* @param filepath
30+
* The location of the Image to use as an icon.
31+
*
32+
* @return An array of ByteBuffers containing the pixel data for the icon in
33+
* varying sizes.
34+
*/
35+
@JvmStatic
36+
fun load(filepath: String): Array<ByteBuffer?> {
37+
return load(File(filepath))
38+
}
39+
40+
/*************************************************************************
41+
* Loads an icon in ByteBuffer form.
42+
*
43+
* @param fil
44+
* A File pointing to the image.
45+
*
46+
* @return An array of ByteBuffers containing the pixel data for the icon in
47+
* various sizes (as recommended by the OS).
48+
*/
49+
@JvmStatic
50+
fun load(fil: File): Array<ByteBuffer?> {
51+
val image: BufferedImage
52+
try {
53+
image = ImageIO.read(fil)
54+
} catch (e: IOException) {
55+
e.printStackTrace()
56+
return arrayOfNulls(2)
57+
}
58+
59+
return load(image)
60+
}
61+
62+
@JvmStatic
63+
fun load(image: BufferedImage): Array<ByteBuffer?> {
64+
val buffers: Array<ByteBuffer?>
65+
if (OmniDesktop.isWindows) {
66+
buffers = arrayOfNulls(2)
67+
buffers[0] = loadInstance(image, 16)
68+
buffers[1] = loadInstance(image, 32)
69+
} else if (OmniDesktop.isMac) {
70+
buffers = arrayOfNulls(1)
71+
buffers[0] = loadInstance(image, 128)
72+
} else {
73+
buffers = arrayOfNulls(1)
74+
buffers[0] = loadInstance(image, 32)
75+
}
76+
return buffers
77+
}
78+
79+
/*************************************************************************
80+
* Copies the supplied image into a square icon at the indicated size.
81+
*
82+
* @param image
83+
* The image to place onto the icon.
84+
* @param dimension
85+
* The desired size of the icon.
86+
*
87+
* @return A ByteBuffer of pixel data at the indicated size.
88+
*/
89+
private fun loadInstance(image: BufferedImage, dimension: Int): ByteBuffer {
90+
val scaledIcon = BufferedImage(dimension, dimension, BufferedImage.TYPE_INT_ARGB_PRE)
91+
val g = scaledIcon.createGraphics()
92+
val ratio = getIconRatio(image, scaledIcon)
93+
val width = image.width * ratio
94+
val height = image.height * ratio
95+
g.drawImage(
96+
image,
97+
((scaledIcon.width - width) / 2).toInt(),
98+
((scaledIcon.height - height) / 2).toInt(),
99+
(width).toInt(),
100+
(height).toInt(),
101+
null
102+
)
103+
g.dispose()
104+
105+
return convertToByteBuffer(scaledIcon)
106+
}
107+
108+
/*************************************************************************
109+
* Gets the width/height ratio of the icon. This is meant to simplify
110+
* scaling the icon to a new dimension.
111+
*
112+
* @param src
113+
* The base image that will be placed onto the icon.
114+
* @param icon
115+
* The icon that will have the image placed on it.
116+
*
117+
* @return The amount to scale the source image to fit it onto the icon
118+
* appropriately.
119+
*/
120+
private fun getIconRatio(src: BufferedImage, icon: BufferedImage): Double {
121+
var ratio: Double
122+
if (src.width > icon.width) ratio = (icon.width).toDouble() / src.width
123+
else ratio = (icon.width.toFloat() / src.width).toDouble()
124+
if (src.height > icon.height) {
125+
val r2 = (icon.height).toDouble() / src.height
126+
if (r2 < ratio) ratio = r2
127+
} else {
128+
val r2 = (icon.height.toFloat() / src.height).toDouble()
129+
if (r2 < ratio) ratio = r2
130+
}
131+
return ratio
132+
}
133+
134+
/*************************************************************************
135+
* Converts a BufferedImage into a ByteBuffer of pixel data.
136+
*
137+
* @param image
138+
* The image to convert.
139+
*
140+
* @return A ByteBuffer that contains the pixel data of the supplied image.
141+
*/
142+
fun convertToByteBuffer(image: BufferedImage): ByteBuffer {
143+
val buffer = ByteArray(image.width * image.height * 4)
144+
var counter = 0
145+
for (i in 0..<image.height) for (j in 0..<image.width) {
146+
val colorSpace = image.getRGB(j, i)
147+
buffer[counter] = ((colorSpace shl 8) shr 24).toByte()
148+
buffer[counter + 1] = ((colorSpace shl 16) shr 24).toByte()
149+
buffer[counter + 2] = ((colorSpace shl 24) shr 24).toByte()
150+
buffer[counter + 3] = (colorSpace shr 24).toByte()
151+
counter += 4
152+
}
153+
return ByteBuffer.wrap(buffer)
154+
}
155+
}
521 KB
Loading

src/main/resources/fabric.mod.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"name": "${mod_name}",
66
"description": "${mod_description}",
77
"authors": [
8-
"Your Name"
8+
"Deftu", "SubAt0mic"
99
],
1010
"environment": "*",
1111
"entrypoints": {

src/main/resources/mcmod.info

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"url": "",
99
"updateUrl": "",
1010
"authorList": [
11-
"Your Name"
11+
"Deftu", "SubAt0mic"
1212
],
1313
"credits": "",
1414
"logoFile": "",
Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
{
2-
"compatibilityLevel": "${java_version}",
3-
"minVersion": "0.8",
4-
"package": "org.polyfrost.polyplus.mixin",
5-
"refmap": "mixins.${mod_id}.refmap.json",
6-
"injectors": {
7-
"maxShiftBy": 5
8-
},
9-
"mixins": [
10-
"Mixin_NoAI"
11-
],
12-
"client": [
13-
"client.Mixin_AddCosmeticsPage",
14-
"client.Mixin_ReplaceCapeTexture",
15-
"client.Mixin_ReplaceOneConfigLogo"
16-
],
17-
"verbose": true
2+
"compatibilityLevel": "${java_version}",
3+
"minVersion": "0.8",
4+
"package": "org.polyfrost.polyplus.mixin",
5+
"refmap": "mixins.${mod_id}.refmap.json",
6+
"injectors": {
7+
"maxShiftBy": 5
8+
},
9+
"mixins": [
10+
"Mixin_NoAI",
11+
"Mixin_ReplaceIcon"
12+
],
13+
"client": [
14+
"client.Mixin_AddCosmeticsPage",
15+
"client.Mixin_ReplaceCapeTexture",
16+
"client.Mixin_ReplaceOneConfigLogo"
17+
],
18+
"verbose": true
1819
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package org.polyfrost.polyplus.mixin;
2+
3+
import com.mojang.blaze3d.platform.Window;
4+
import net.minecraft.client.Minecraft;
5+
import org.lwjgl.glfw.GLFW;
6+
import org.lwjgl.glfw.GLFWImage;
7+
import org.polyfrost.polyplus.client.PolyPlusClient;
8+
import org.polyfrost.polyplus.client.utils.IconLoader;
9+
import org.spongepowered.asm.mixin.Final;
10+
import org.spongepowered.asm.mixin.Mixin;
11+
import org.spongepowered.asm.mixin.Shadow;
12+
import org.spongepowered.asm.mixin.injection.At;
13+
import org.spongepowered.asm.mixin.injection.Inject;
14+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
15+
16+
import javax.imageio.ImageIO;
17+
import javax.swing.*;
18+
import java.io.InputStream;
19+
import java.nio.ByteBuffer;
20+
import java.util.Objects;
21+
22+
@Mixin(value = Minecraft.class, priority = Integer.MIN_VALUE)
23+
public class Mixin_ReplaceIcon { //todo: 1.13+
24+
//
25+
// @Shadow @Final private Window window;
26+
//
27+
// @Inject(method = "<init>", at = @At("TAIL"), cancellable = true)
28+
// private void setWindowIcon(CallbackInfo ci) {
29+
// try (InputStream stream = PolyPlusClient.class.getResourceAsStream("/assets/polyplus/PolyPlusIcon.png")) {
30+
// GLFWImage.Buffer icons = GLFWImage.malloc(2);
31+
// ByteBuffer[] buffers = IconLoader.load(ImageIO.read(Objects.requireNonNull(stream)));
32+
// icons.put(0, buffers[0]);
33+
// GLFW.glfwSetWindowIcon(this.window.getWindow(), icons);
34+
// ci.cancel();
35+
// } catch (Exception e) {
36+
// e.printStackTrace();
37+
// }
38+
// }
39+
}
40+

0 commit comments

Comments
 (0)