Spaces:
Sleeping
Sleeping
| import { Canvas, loadImage } from 'skia-canvas'; | |
| import * as THREE from './three/Three'; | |
| import gl from 'gl'; | |
| // threejs内部使用了OffscreenCanvas | |
| (globalThis as any).OffscreenCanvas = (globalThis as any).OffscreenCanvas || Canvas; | |
| const cc = <T>(a: T[][]): T[] => { | |
| const result: T[] = []; | |
| for (const x of a) { | |
| for (const e of x) result.push(e); | |
| } | |
| return result; | |
| }; | |
| class GLCanvas { | |
| ctx: ReturnType<typeof gl>; | |
| width: number = 256; | |
| height: number = 192; | |
| resizeBuffer: number[]; | |
| constructor(width: number, height: number) { | |
| this.width = width; | |
| this.height = height; | |
| } | |
| getContext(type, options) { | |
| if (type === 'webgl') { | |
| this.ctx = this.ctx || gl(this.width, this.height, options); | |
| return this.ctx; | |
| } | |
| return null as WebGLRenderingContext; | |
| } | |
| addEventListener(evt: 'webglcontextlost') {} | |
| removeEventListener(evt: any) {} | |
| async toBuffer() { | |
| const pixels = new Uint8Array(this.width * this.height * 4); | |
| this.ctx.readPixels(0, 0, this.width, this.height, this.ctx.RGBA, this.ctx.UNSIGNED_BYTE, pixels); | |
| const canvas = new Canvas(this.width, this.height); | |
| const ctx = canvas.getContext('2d'); | |
| ctx.putImageData({ data: new Uint8ClampedArray(pixels), width: this.width, height: this.height }, 0, 0); | |
| return canvas.toBuffer('png'); | |
| } | |
| } | |
| export const renderGaugeImage = async (sourceURL: string | Buffer, gaugeURL: string | Buffer, baseY: number) => { | |
| const source = await loadImage(sourceURL); | |
| const gauge = await loadImage(gaugeURL); | |
| const { width: gaugeWidth, height: gaugeHeight } = gauge; | |
| const { width: srcWidth, height: srcHeight } = source; | |
| let width: number = 256; | |
| let height: number = 192; | |
| if (width !== srcWidth || height !== srcHeight) { | |
| if (Number.isFinite(gaugeWidth)) { | |
| width = gaugeWidth; | |
| } else { | |
| width = Math.round((height * srcWidth) / srcHeight); | |
| } | |
| } | |
| const camera = new THREE.OrthographicCamera(width / -2, width / 2, height / 2, height / -2, 0.1, 10); | |
| camera.position.set(0, 0, 1); | |
| camera.up.set(0, 1, 0); | |
| camera.lookAt(0, 0, 0); | |
| const ctx = new Canvas(gaugeWidth, gaugeHeight).getContext('2d'); | |
| ctx.drawImage(gauge, 0, 0); | |
| const { data: buff } = ctx.getImageData(0, 0, gaugeWidth, gaugeHeight); | |
| const xFactor = width / gaugeWidth; | |
| baseY = Math.round(Number.isFinite(baseY) ? baseY : gaugeHeight / 2); | |
| baseY = Math.max(0, Math.min(gaugeHeight - 1, baseY)); | |
| const propertyArray = Array(gaugeHeight) | |
| .fill(null) | |
| .map((_, y) => | |
| Array(gaugeWidth) | |
| .fill(null) | |
| .map((_, x) => ({ | |
| uv: [(x + 0.5) / gaugeWidth, 1 - (y + 0.5) / gaugeHeight], | |
| position: [(x - gaugeWidth / 2) * xFactor, (buff[(y * gaugeWidth + x) * 4] + buff[(y * gaugeWidth + x) * 4 + 2] / 256 - 128) / xFactor, 0], | |
| })) | |
| ); | |
| // integral X by K | |
| for (let y = baseY; y > 0; --y) { | |
| for (let x = 0; x < gaugeWidth; ++x) | |
| propertyArray[y - 1][x].position[0] = propertyArray[y][x].position[0] - ((buff[(y * gaugeWidth + x) * 4 + 1] - 128) * xFactor) / 127; | |
| } | |
| for (let y = baseY + 1; y < gaugeHeight; ++y) { | |
| for (let x = 0; x < gaugeWidth; ++x) | |
| propertyArray[y][x].position[0] = propertyArray[y - 1][x].position[0] + ((buff[((y - 1) * gaugeWidth + x) * 4 + 1] - 128) * xFactor) / 127; | |
| } | |
| const uvs = cc(cc(propertyArray).map((p) => p.uv)); | |
| const positions = cc(cc(propertyArray).map((p) => p.position)); | |
| const geometry = new THREE.BufferGeometry(); | |
| geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2)); | |
| geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3)); | |
| const faces = Array(gaugeHeight - 1) | |
| .fill(null) | |
| .map((_, y) => | |
| Array(gaugeWidth - 1) | |
| .fill(null) | |
| .map((_, x) => [ | |
| y * gaugeWidth + x, | |
| y * gaugeWidth + x + 1, | |
| (y + 1) * gaugeWidth + x, | |
| (y + 1) * gaugeWidth + x, | |
| (y + 1) * gaugeWidth + x + 1, | |
| y * gaugeWidth + x + 1, | |
| ]) | |
| ); | |
| const indices = cc(cc(faces)); | |
| geometry.setIndex(indices); | |
| const material = new THREE.MeshBasicMaterial({ | |
| side: THREE.DoubleSide, | |
| map: new THREE.Texture(source), | |
| }); | |
| material.map.needsUpdate = true; | |
| material.needsUpdate = true; | |
| const scene = new THREE.Scene(); | |
| const plane = new THREE.Mesh(geometry, material); | |
| scene.add(plane); | |
| const glCanvas = new GLCanvas(width, height); | |
| const renderer = new THREE.WebGLRenderer({ | |
| antialias: true, | |
| canvas: glCanvas as any, | |
| alpha: false, | |
| }); | |
| renderer.render(scene, camera); | |
| renderer.dispose(); | |
| return { | |
| buffer: await glCanvas.toBuffer(), | |
| size: { | |
| width, | |
| height, | |
| }, | |
| }; | |
| }; | |