forked from AllenInstitute/vis
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdzi-viewer.tsx
More file actions
117 lines (108 loc) · 4.37 KB
/
Copy pathdzi-viewer.tsx
File metadata and controls
117 lines (108 loc) · 4.37 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import {
type GpuProps as CachedPixels,
type DziImage,
type DziRenderSettings,
type DziTile,
buildAsyncDziRenderer,
} from '@alleninstitute/vis-dzi';
import { Vec2, type vec2 } from '@alleninstitute/vis-geometry';
import type { RenderFrameFn, buildAsyncRenderer } from '@alleninstitute/vis-core';
import { useContext, useEffect, useRef } from 'react';
import { renderServerContext } from '~/common/react/render-server-provider';
type Props = {
id: string;
dzi: DziImage;
svgOverlay: HTMLImageElement;
onWheel?: (e: WheelEvent) => void;
onMouseDown?: (e: React.MouseEvent<HTMLCanvasElement>) => void;
onMouseUp?: (e: React.MouseEvent<HTMLCanvasElement>) => void;
onMouseMove?: (e: React.MouseEvent<HTMLCanvasElement>) => void;
onMouseLeave?: (e: React.MouseEvent<HTMLCanvasElement>) => void;
} & DziRenderSettings;
export function DziViewer(props: Props) {
const { svgOverlay, camera, dzi, onWheel, id, onMouseDown, onMouseUp, onMouseMove, onMouseLeave } = props;
const server = useContext(renderServerContext);
const cnvs = useRef<HTMLCanvasElement>(null);
// the renderer needs WebGL for us to create it, and WebGL needs a canvas to exist, and that canvas needs to be the same canvas forever
// hence the awkwardness of refs + an effect to initialize the whole hting
const renderer =
useRef<
ReturnType<typeof buildAsyncRenderer<DziImage, DziTile, DziRenderSettings, string, string, CachedPixels>>
>();
useEffect(() => {
if (server?.regl) {
renderer.current = buildAsyncDziRenderer(server.regl);
}
return () => {
if (cnvs.current) {
server?.destroyClient(cnvs.current);
}
};
}, [server]);
useEffect(() => {
const compose = (ctx: CanvasRenderingContext2D, image: ImageData) => {
// first, draw the results from webGL
ctx.putImageData(image, 0, 0);
if (svgOverlay) {
// then add our svg overlay
const { width, height } = svgOverlay;
const svgSize: vec2 = [width, height];
const start = Vec2.mul(camera.view.minCorner, svgSize);
const wh = Vec2.sub(Vec2.mul(camera.view.maxCorner, svgSize), start);
const [sx, sy] = start;
const [sw, sh] = wh;
ctx.drawImage(svgOverlay, sx, sy, sw, sh, 0, 0, ctx.canvas.width, ctx.canvas.height);
}
};
if (server && renderer.current && cnvs.current) {
const renderMyData: RenderFrameFn<DziImage, DziTile> = (target, cache, callback) => {
if (renderer.current) {
// erase the frame before we start drawing on it
return renderer.current(dzi, { camera }, callback, target, cache);
}
return null;
};
server.beginRendering(
renderMyData,
(e) => {
if (e.status === 'begin') {
server.regl?.clear({
framebuffer: e.target,
color: [0, 0, 0, 0],
depth: 1,
});
} else if (e.status === 'progress' || e.status === 'finished') {
e.server.copyToClient(compose);
}
},
cnvs.current,
);
}
}, [server, svgOverlay, dzi, camera]);
// we have to add the listener this way because onWheel is a passive listener by default
// that means we can't preventDefault to stop scrolling
useEffect(() => {
const handleWheel = (e: WheelEvent) => onWheel?.(e);
const canvas = cnvs;
if (canvas?.current) {
canvas.current.addEventListener('wheel', handleWheel, { passive: false });
}
return () => {
if (canvas?.current) {
canvas.current.removeEventListener('wheel', handleWheel);
}
};
}, [onWheel]);
return (
<canvas
id={id}
ref={cnvs}
width={camera.screenSize[0]}
height={camera.screenSize[1]}
onMouseDown={onMouseDown}
onMouseUp={onMouseUp}
onMouseMove={onMouseMove}
onMouseLeave={onMouseLeave}
/>
);
}