UNPKG

threepipe

Version:

A modern 3D viewer framework built on top of three.js, written in TypeScript, designed to make creating high-quality, modular, and extensible 3D experiences on the web simple and enjoyable.

128 lines (111 loc) 4.97 kB
import { ColorSpace, DataTexture, DataUtils, FloatType, HalfFloatType, LinearSRGBColorSpace, Texture, TextureImageData, TextureDataType, UnsignedByteType, WebGLRenderer, } from 'three' import {canvasFlipY, LinearToSRGB} from 'ts-browser-helpers' export function getTextureDataType(renderer?: WebGLRenderer): TextureDataType { if (!renderer) return UnsignedByteType const halfFloatSupport = renderer.extensions.has('EXT_color_buffer_half_float') || renderer.capabilities.isWebGL2 && renderer.extensions.has('EXT_color_buffer_float') const floatSupport = renderer.capabilities.isWebGL2 || renderer.extensions.has('OES_texture_float') || renderer.extensions.has('WEBGL_color_buffer_float') return halfFloatSupport ? HalfFloatType : floatSupport ? FloatType : UnsignedByteType } export function textureDataToImageData(imgData: TextureImageData | ImageData | {data: Float32Array|Uint16Array|Uint8Array, width: number, height: number}, colorSpace?: ColorSpace, outData?: ImageData) { const data = outData?.data ?? new Uint8ClampedArray(imgData.height * imgData.width * 4) const isFloat32 = imgData.data instanceof Float32Array const isUint16 = imgData.data instanceof Uint16Array for (let i = 0; i < data.length; i++) { if (isFloat32) { // Float32 data[i] = imgData.data[i] * 255 } else if (isUint16) { // Uint16 (half float) data[i] = DataUtils.fromHalfFloat(imgData.data[i]) * 255 } else { // Uint8 data[i] = imgData.data[i] } if (colorSpace === LinearSRGBColorSpace) { data[i] = LinearToSRGB(data[i] / 255.0) * 255 } // todo: rgbm? } return outData ?? new ImageData(data, imgData.width, imgData.height) } /** * * @param texture * @param maxWidth * @param flipY * @param canvas */ export function textureToCanvas(texture: Texture|DataTexture, maxWidth: number, flipY = false) { let img if ((texture as DataTexture).isDataTexture) img = textureDataToImageData(texture.image, texture.colorSpace) else img = texture.image return texImageToCanvas(img, maxWidth, flipY) } export function texImageToCanvas(image: TexImageSource, maxWidth: number, flipY = false) { let width, height if (!window.VideoFrame) window.VideoFrame = HTMLVideoElement as any if (image instanceof window.VideoFrame) { width = image.displayWidth height = image.displayHeight } else { width = image.width || (image as HTMLVideoElement).videoWidth || (image as HTMLImageElement).naturalWidth || 1 height = image.height || (image as HTMLVideoElement).videoHeight || (image as HTMLImageElement).naturalHeight || 1 } if (window.VideoFrame as any === HTMLVideoElement) delete (window as any).VideoFrame const canvas = document.createElement('canvas') // resize it to the size of our image canvas.width = Math.min(maxWidth, width) canvas.height = Math.floor(1.0 + canvas.width * height / width) const ctx = canvas.getContext('2d') if (!ctx) { console.error('textureToDataUrl: could not get canvas context') return canvas } if (flipY === true) { ctx.translate(0, canvas.height) ctx.scale(1, -1) } let needsFlipY = false if ((image as ImageData).data !== undefined) { // THREE.DataTexture const imageData = image as ImageData if (width !== canvas.width || height !== canvas.height) { const tempCanvas = document.createElement('canvas') tempCanvas.width = width tempCanvas.height = height const tempCtx = tempCanvas.getContext('2d') if (!tempCtx) { console.error('textureToDataUrl: could not get temp canvas context') ctx.putImageData(imageData, 0, 0) } else { tempCtx.putImageData(imageData, 0, 0) // for resize ctx.drawImage(tempCanvas, 0, 0, canvas.width, canvas.height) } } else { ctx.putImageData(imageData, 0, 0) if (flipY) needsFlipY = true // because of putImageData } } else { ctx.drawImage(image as any, 0, 0, canvas.width, canvas.height) } return !needsFlipY ? canvas : canvasFlipY(canvas) } export function textureToDataUrl(texture: Texture|DataTexture, maxWidth: number, flipY: boolean, mimeType?: string, quality?: number) { return textureToCanvas(texture, maxWidth, flipY).toDataURL(mimeType, quality) } export async function textureToBlob(texture: Texture|DataTexture, maxWidth: number, flipY: boolean, mimeType?: string, quality?: number) { const canvas = textureToCanvas(texture, maxWidth, flipY) return new Promise<Blob>((resolve, reject) => { canvas.toBlob(blob => { if (blob) resolve(blob) else reject(new Error('Failed to create blob')) }, mimeType, quality) }) }