85 lines
3.7 KiB
Kotlin
85 lines
3.7 KiB
Kotlin
package mods.betterfoliage.util
|
|
|
|
import mods.betterfoliage.resource.model.HSB
|
|
import net.minecraft.client.MinecraftClient
|
|
import net.minecraft.client.texture.Sprite
|
|
import net.minecraft.client.texture.SpriteAtlasTexture
|
|
import net.minecraft.resource.Resource
|
|
import net.minecraft.resource.ResourceManager
|
|
import net.minecraft.util.Identifier
|
|
import java.awt.image.BufferedImage
|
|
import java.io.ByteArrayOutputStream
|
|
import java.io.IOException
|
|
import javax.imageio.ImageIO
|
|
import kotlin.math.atan2
|
|
|
|
enum class Atlas(val basePath: String, val resourceId: Identifier) {
|
|
BLOCKS("textures", SpriteAtlasTexture.BLOCK_ATLAS_TEX),
|
|
PARTICLES("textures/particle", SpriteAtlasTexture.PARTICLE_ATLAS_TEX);
|
|
|
|
/** Get the fully-qualified resource name for sprites belonging to this atlas*/
|
|
fun wrap(resource: Identifier) = Identifier(resource.namespace, "$basePath/${resource.path}.png")
|
|
|
|
/** Get the short resource name for sprites belonging to this atlas*/
|
|
fun unwrap(resource: Identifier) = resource.stripStart("$basePath/").stripEnd(".png")
|
|
|
|
/** Reference to the atlas itself */
|
|
val atlas: SpriteAtlasTexture get() = MinecraftClient.getInstance().textureManager.getTexture(resourceId) as SpriteAtlasTexture
|
|
}
|
|
|
|
operator fun SpriteAtlasTexture.get(res: Identifier): Sprite? = getSprite(res)
|
|
operator fun SpriteAtlasTexture.get(name: String): Sprite? = getSprite(Identifier(name))
|
|
|
|
fun ResourceManager.loadSprite(id: Identifier) = this.get(id)?.loadImage() ?: throw IOException("Cannot load resource $id")
|
|
|
|
fun Resource.loadImage(): BufferedImage? = ImageIO.read(this.inputStream)
|
|
|
|
/** Index operator to get the RGB value of a pixel. */
|
|
operator fun BufferedImage.get(x: Int, y: Int) = this.getRGB(x, y)
|
|
/** Index operator to set the RGB value of a pixel. */
|
|
operator fun BufferedImage.set(x: Int, y: Int, value: Int) = this.setRGB(x, y, value)
|
|
|
|
val BufferedImage.bytes: ByteArray get() =
|
|
ByteArrayOutputStream().let { ImageIO.write(this, "PNG", it); it.toByteArray() }
|
|
|
|
/**
|
|
* Calculate the average color of an image.
|
|
*
|
|
* Only non-transparent pixels are considered. Averages are taken in the HSB color space (note: Hue is a circular average),
|
|
* and the result transformed back to the RGB color space.
|
|
*/
|
|
fun ResourceManager.averageImageColorHSB(id: Identifier, atlas: Atlas) = loadSprite(atlas.wrap(id)).let { image ->
|
|
var numOpaque = 0
|
|
var sumHueX = 0.0
|
|
var sumHueY = 0.0
|
|
var sumSaturation = 0.0f
|
|
var sumBrightness = 0.0f
|
|
for (x in 0 until image.width)
|
|
for (y in 0 until image.height) {
|
|
val pixel = image.get(x, y)
|
|
val alpha = (pixel shr 24) and 255
|
|
val hsb = HSB.fromColor(pixel)
|
|
if (alpha == 255) {
|
|
numOpaque++
|
|
sumHueX += Math.cos((hsb.hue.toDouble() - 0.5) * PI2)
|
|
sumHueY += Math.sin((hsb.hue.toDouble() - 0.5) * PI2)
|
|
sumSaturation += hsb.saturation
|
|
sumBrightness += hsb.brightness
|
|
}
|
|
}
|
|
|
|
// circular average - transform sum vector to polar angle
|
|
val avgHue = (atan2(sumHueY, sumHueX) / PI2 + 0.5).toFloat()
|
|
HSB(avgHue, sumSaturation / numOpaque.toFloat(), sumBrightness / numOpaque.toFloat())
|
|
}
|
|
|
|
/** Weighted blend of 2 packed RGB colors */
|
|
fun blendRGB(rgb1: Int, rgb2: Int, weight1: Int, weight2: Int): Int {
|
|
val r = (((rgb1 shr 16) and 255) * weight1 + ((rgb2 shr 16) and 255) * weight2) / (weight1 + weight2)
|
|
val g = (((rgb1 shr 8) and 255) * weight1 + ((rgb2 shr 8) and 255) * weight2) / (weight1 + weight2)
|
|
val b = ((rgb1 and 255) * weight1 + (rgb2 and 255) * weight2) / (weight1 + weight2)
|
|
val a = (rgb1 shr 24) and 255
|
|
val result = ((a shl 24) or (r shl 16) or (g shl 8) or b)
|
|
return result
|
|
}
|