pages235/renderer/viewer/lib/utils/skins.ts
2025-07-02 16:25:36 +03:00

120 lines
3.6 KiB
TypeScript

import { loadSkinToCanvas } from 'skinview-utils'
import * as THREE from 'three'
import stevePng from 'mc-assets/dist/other-textures/latest/entity/player/wide/steve.png'
import { getLoadedImage } from 'mc-assets/dist/utils'
const detectFullOffscreenCanvasSupport = () => {
if (typeof OffscreenCanvas === 'undefined') return false
try {
const canvas = new OffscreenCanvas(1, 1)
// Try to get a WebGL context - this will fail on iOS where only 2D is supported (iOS 16)
const gl = canvas.getContext('webgl2') || canvas.getContext('webgl')
return gl !== null
} catch (e) {
return false
}
}
const hasFullOffscreenCanvasSupport = detectFullOffscreenCanvasSupport()
export const loadThreeJsTextureFromUrlSync = (imageUrl: string) => {
const texture = new THREE.Texture()
const promise = getLoadedImage(imageUrl).then(image => {
texture.image = image
texture.needsUpdate = true
return texture
})
return {
texture,
promise
}
}
export const createCanvas = (width: number, height: number): OffscreenCanvas => {
if (hasFullOffscreenCanvasSupport) {
return new OffscreenCanvas(width, height)
}
const canvas = document.createElement('canvas')
canvas.width = width
canvas.height = height
return canvas as unknown as OffscreenCanvas // todo-low
}
export const loadThreeJsTextureFromUrl = async (imageUrl: string) => {
const loaded = new THREE.TextureLoader().loadAsync(imageUrl)
return loaded
}
export const loadThreeJsTextureFromBitmap = (image: ImageBitmap) => {
const canvas = createCanvas(image.width, image.height)
const ctx = canvas.getContext('2d')!
ctx.drawImage(image, 0, 0)
const texture = new THREE.Texture(canvas)
texture.magFilter = THREE.NearestFilter
texture.minFilter = THREE.NearestFilter
return texture
}
export const stevePngUrl = stevePng
export const steveTexture = loadThreeJsTextureFromUrl(stevePngUrl)
export async function loadImageFromUrl (imageUrl: string): Promise<ImageBitmap> {
const response = await fetch(imageUrl)
const blob = await response.blob()
return createImageBitmap(blob)
}
const config = {
apiEnabled: true,
}
export const setSkinsConfig = (newConfig: Partial<typeof config>) => {
Object.assign(config, newConfig)
}
export async function loadSkinFromUsername (username: string, type: 'skin' | 'cape'): Promise<string | undefined> {
if (!config.apiEnabled) return
if (type === 'cape') return
const url = `https://playerdb.co/api/player/minecraft/${username}`
const response = await fetch(url)
if (!response.ok) return
const data: {
data: {
player: {
skin_texture: string
}
}
} = await response.json()
return data.data.player.skin_texture
}
export const parseSkinTexturesValue = (value: string) => {
const decodedData: {
textures: {
SKIN: {
url: string
}
}
} = JSON.parse(Buffer.from(value, 'base64').toString())
return decodedData.textures?.SKIN?.url
}
export async function loadSkinImage (skinUrl: string): Promise<{ canvas: OffscreenCanvas, image: ImageBitmap }> {
if (!skinUrl.startsWith('data:')) {
skinUrl = await fetchAndConvertBase64Skin(skinUrl.replace('http://', 'https://'))
}
const image = await loadImageFromUrl(skinUrl)
const skinCanvas = createCanvas(64, 64)
loadSkinToCanvas(skinCanvas, image)
return { canvas: skinCanvas, image }
}
const fetchAndConvertBase64Skin = async (skinUrl: string) => {
const response = await fetch(skinUrl, { })
const arrayBuffer = await response.arrayBuffer()
const base64 = Buffer.from(arrayBuffer).toString('base64')
return `data:image/png;base64,${base64}`
}