Files
BetterFoliage/src/main/kotlin/mods/betterfoliage/util/Sprites.kt
octarine-noise df50f61b0d [WIP] initial Fabric port
major package refactoring
2020-02-05 13:43:54 +01:00

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
}