diff --git a/src/mol-canvas3d/canvas3d.ts b/src/mol-canvas3d/canvas3d.ts index a4a5648a9c38413e781334318a77c5913928b0ee..8f231b245f7260f44b7e127da81a0c73105e02d9 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 5c768690c85d276809d48647916c7166a511ead2..7a8400e549b068854f1ae0ffbd9121394b3a698b 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 1561924f44f82f54b2c5a8cbd9f5738aad59f72a..0944f523202eea629b8703c6bcf29df0fe17cb40 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 fa3f2d19fb8c82b48c1406518731e59c1ae8e601..c8975a722b301537c0652c4fd8c8fef62e86be4a 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 f09a8c13a7c13ff9eb4ff6a25404fda25be1574f..d82a66b7b74b02746fd114c8faadc56874ee1cf6 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 f5b7dec523ae81dfcef1ca12861228a964407d21..3918eb5d168b8498eaba19e621d7de16c4414ddb 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 0000000000000000000000000000000000000000..dcbe39a8b71a345d50dc850acdbd553cf473f706 --- /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