From 2e2d65cf8c1683dc00340c76b3be7a281672b35f Mon Sep 17 00:00:00 2001 From: Alexander Rose <alex.rose@rcsb.org> Date: Thu, 16 May 2019 17:19:35 -0700 Subject: [PATCH] image/pixel data refactoring --- src/mol-canvas3d/canvas3d.ts | 13 ++++++------ src/mol-gl/renderable/util.ts | 4 ++-- src/mol-gl/renderer.ts | 12 +---------- src/mol-gl/webgl/context.ts | 26 +++++++++++------------ src/mol-gl/webgl/render-target.ts | 8 ++++--- src/mol-gl/webgl/texture.ts | 4 ++-- src/mol-util/image.ts | 35 +++++++++++++++++++++++++++++++ 7 files changed, 64 insertions(+), 38 deletions(-) create mode 100644 src/mol-util/image.ts diff --git a/src/mol-canvas3d/canvas3d.ts b/src/mol-canvas3d/canvas3d.ts index a4a5648a9..8f231b245 100644 --- a/src/mol-canvas3d/canvas3d.ts +++ b/src/mol-canvas3d/canvas3d.ts @@ -33,6 +33,7 @@ import { ValueCell } from 'mol-util'; import { getPostprocessingRenderable, PostprocessingParams, setPostprocessingProps } from './helper/postprocessing'; import { JitterVectors, getComposeRenderable } from './helper/multi-sample'; import { GLRenderingContext } from 'mol-gl/webgl/compat'; +import { PixelData } from 'mol-util/image'; export const Canvas3DParams = { // TODO: FPS cap? @@ -77,7 +78,7 @@ interface Canvas3D { resetCamera: () => void readonly camera: Camera downloadScreenshot: () => void - getImageData: (variant: GraphicsRenderVariant) => ImageData + getPixelData: (variant: GraphicsRenderVariant) => PixelData setProps: (props: Partial<Canvas3DProps>) => void /** Returns a copy of the current Canvas3D instance props */ @@ -620,12 +621,12 @@ namespace Canvas3D { downloadScreenshot: () => { // TODO }, - getImageData: (variant: GraphicsRenderVariant) => { + getPixelData: (variant: GraphicsRenderVariant) => { switch (variant) { - case 'draw': return renderer.getImageData() - case 'pickObject': return objectPickTarget.getImageData() - case 'pickInstance': return instancePickTarget.getImageData() - case 'pickGroup': return groupPickTarget.getImageData() + case 'draw': return webgl.getDrawingBufferPixelData() + case 'pickObject': return objectPickTarget.getPixelData() + case 'pickInstance': return instancePickTarget.getPixelData() + case 'pickGroup': return groupPickTarget.getPixelData() } }, didDraw, diff --git a/src/mol-gl/renderable/util.ts b/src/mol-gl/renderable/util.ts index 5c768690c..7a8400e54 100644 --- a/src/mol-gl/renderable/util.ts +++ b/src/mol-gl/renderable/util.ts @@ -53,7 +53,7 @@ export function printTextureImage(textureImage: TextureImage<any>, scale = 1) { return printImageData(new ImageData(data, width, height), scale) } -export function printImageData(imageData: ImageData, scale = 1) { +export function printImageData(imageData: ImageData, scale = 1, pixelated = false) { const canvas = document.createElement('canvas') canvas.width = imageData.width canvas.height = imageData.height @@ -66,7 +66,7 @@ export function printImageData(imageData: ImageData, scale = 1) { img.src = objectURL img.style.width = imageData.width * scale + 'px' img.style.height = imageData.height * scale + 'px'; - (img.style as any).imageRendering = 'pixelated' // works in Chrome + if (pixelated) (img.style as any).imageRendering = 'pixelated' // supported only in Chrome img.style.position = 'absolute' img.style.top = '0px' img.style.left = '0px' diff --git a/src/mol-gl/renderer.ts b/src/mol-gl/renderer.ts index 1561924f4..0944f5232 100644 --- a/src/mol-gl/renderer.ts +++ b/src/mol-gl/renderer.ts @@ -8,7 +8,7 @@ import { Viewport } from 'mol-canvas3d/camera/util'; import { Camera } from 'mol-canvas3d/camera'; import Scene from './scene'; -import { WebGLContext, createImageData } from './webgl/context'; +import { WebGLContext } from './webgl/context'; import { Mat4, Vec3, Vec4 } from 'mol-math/linear-algebra'; import { Renderable } from './renderable'; import { Color } from 'mol-util/color'; @@ -41,7 +41,6 @@ interface Renderer { render: (scene: Scene, variant: GraphicsRenderVariant) => void setProps: (props: Partial<RendererProps>) => void setViewport: (x: number, y: number, width: number, height: number) => void - getImageData: () => ImageData dispose: () => void } @@ -257,15 +256,6 @@ namespace Renderer { ValueCell.update(globalUniforms.uViewport, Vec4.set(globalUniforms.uViewport.ref.value, x, y, width, height)) } }, - getImageData: () => { - const { x, y, width, height } = viewport - const dw = width - x - const dh = height - y - const buffer = new Uint8Array(dw * dh * 4) - ctx.unbindFramebuffer() - ctx.readPixels(x, y, width, height, buffer) - return createImageData(buffer, dw, dh) - }, get props() { return p diff --git a/src/mol-gl/webgl/context.ts b/src/mol-gl/webgl/context.ts index fa3f2d19f..c8975a722 100644 --- a/src/mol-gl/webgl/context.ts +++ b/src/mol-gl/webgl/context.ts @@ -12,6 +12,7 @@ import { Scheduler } from 'mol-task'; import { isDebugMode } from 'mol-util/debug'; import { createExtensions, WebGLExtensions } from './extensions'; import { WebGLState, createState } from './state'; +import { PixelData } from 'mol-util/image'; export function getGLContext(canvas: HTMLCanvasElement, contextAttributes?: WebGLContextAttributes): GLRenderingContext | null { function getContext(contextId: 'webgl' | 'experimental-webgl' | 'webgl2') { @@ -134,19 +135,14 @@ function readPixels(gl: GLRenderingContext, x: number, y: number, width: number, if (isDebugMode) checkError(gl) } -export function createImageData(buffer: ArrayLike<number>, width: number, height: number) { - const w = width * 4 - const h = height - const data = new Uint8ClampedArray(width * height * 4) - for (let i = 0, maxI = h / 2; i < maxI; ++i) { - for (let j = 0, maxJ = w; j < maxJ; ++j) { - const index1 = i * w + j; - const index2 = (h-i-1) * w + j; - data[index1] = buffer[index2]; - data[index2] = buffer[index1]; - } - } - return new ImageData(data, width, height); +function getDrawingBufferPixelData(gl: GLRenderingContext) { + const w = gl.drawingBufferWidth + const h = gl.drawingBufferHeight + const buffer = new Uint8Array(w * h * 4) + unbindFramebuffer(gl) + gl.viewport(0, 0, w, h) + readPixels(gl, 0, 0, w, h, buffer) + return PixelData.flipY(PixelData.create(buffer, w, h)) } // @@ -177,7 +173,7 @@ function createStats(): WebGLStats { } } - +// /** A WebGL context object, including the rendering context, resource caches and counts */ export interface WebGLContext { @@ -201,6 +197,7 @@ export interface WebGLContext { readPixelsAsync: (x: number, y: number, width: number, height: number, buffer: Uint8Array) => Promise<void> waitForGpuCommandsComplete: () => Promise<void> waitForGpuCommandsCompleteSync: () => void + getDrawingBufferPixelData: () => PixelData destroy: () => void } @@ -286,6 +283,7 @@ export function createContext(gl: GLRenderingContext): WebGLContext { readPixelsAsync, waitForGpuCommandsComplete: () => waitForGpuCommandsComplete(gl), waitForGpuCommandsCompleteSync: () => waitForGpuCommandsCompleteSync(gl), + getDrawingBufferPixelData: () => getDrawingBufferPixelData(gl), destroy: () => { unbindResources(gl) diff --git a/src/mol-gl/webgl/render-target.ts b/src/mol-gl/webgl/render-target.ts index f09a8c13a..d82a66b7b 100644 --- a/src/mol-gl/webgl/render-target.ts +++ b/src/mol-gl/webgl/render-target.ts @@ -4,13 +4,14 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { WebGLContext, createImageData } from './context' +import { WebGLContext } from './context' import { idFactory } from 'mol-util/id-factory'; import { createTexture, Texture } from './texture'; import { createFramebuffer, Framebuffer } from './framebuffer'; import { createRenderbuffer } from './renderbuffer'; import { TextureImage } from '../renderable/util'; import { Mutable } from 'mol-util/type-helpers'; +import { PixelData } from 'mol-util/image'; const getNextRenderTargetId = idFactory() @@ -27,7 +28,7 @@ export interface RenderTarget { setSize: (width: number, height: number) => void readBuffer: (x: number, y: number, width: number, height: number, dst: Uint8Array) => void getBuffer: () => Uint8Array - getImageData: () => ImageData + getPixelData: () => PixelData destroy: () => void } @@ -55,6 +56,7 @@ export function createRenderTarget (ctx: WebGLContext, _width: number, _height: function readBuffer(x: number, y: number, width: number, height: number, dst: Uint8Array) { framebuffer.bind() + gl.viewport(0, 0, _width, _height) ctx.readPixels(x, y, width, height, dst) } @@ -86,7 +88,7 @@ export function createRenderTarget (ctx: WebGLContext, _width: number, _height: }, readBuffer, getBuffer, - getImageData: () => createImageData(getBuffer(), _width, _height), + getPixelData: () => PixelData.flipY(PixelData.create(getBuffer(), _width, _height)), destroy: () => { if (destroyed) return targetTexture.destroy() diff --git a/src/mol-gl/webgl/texture.ts b/src/mol-gl/webgl/texture.ts index f5b7dec52..3918eb5d1 100644 --- a/src/mol-gl/webgl/texture.ts +++ b/src/mol-gl/webgl/texture.ts @@ -159,8 +159,8 @@ export function createTexture(ctx: WebGLContext, kind: TextureKind, _format: Tex // check texture kind and type compatability if ( - (kind.endsWith('float32') && _type !== 'float') || - (kind.endsWith('uint8') && _type !== 'ubyte') || + (kind.endsWith('float32') && _type !== 'float') || + (kind.endsWith('uint8') && _type !== 'ubyte') || (kind.endsWith('depth') && _type !== 'ushort') ) { throw new Error(`texture kind '${kind}' and type '${_type}' are incompatible`) diff --git a/src/mol-util/image.ts b/src/mol-util/image.ts new file mode 100644 index 000000000..dcbe39a8b --- /dev/null +++ b/src/mol-util/image.ts @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +export { PixelData } + +interface PixelData { + readonly array: Uint8Array + readonly width: number + readonly height: number +} + +namespace PixelData { + export function create(array: Uint8Array, width: number, height: number): PixelData { + return { array, width, height } + } + + /** horizontally flips the pixel data in-place */ + export function flipY(pixelData: PixelData): PixelData { + const { array, width, height } = pixelData + const width4 = width * 4 + for (let i = 0, maxI = height / 2; i < maxI; ++i) { + for (let j = 0, maxJ = width4; j < maxJ; ++j) { + const index1 = i * width4 + j; + const index2 = (height - i - 1) * width4 + j; + const tmp = array[index1] + array[index1] = array[index2] + array[index2] = tmp + } + } + return pixelData + } +} \ No newline at end of file -- GitLab