225 lines
6.5 KiB
TypeScript
225 lines
6.5 KiB
TypeScript
import {
|
|
OrthographicCamera,
|
|
Scene,
|
|
WebGLRenderTarget,
|
|
LinearFilter,
|
|
NearestFilter,
|
|
RGBAFormat,
|
|
UnsignedByteType,
|
|
CfxTexture,
|
|
ShaderMaterial,
|
|
PlaneBufferGeometry,
|
|
Mesh,
|
|
WebGLRenderer
|
|
} from '@citizenfx/three';
|
|
|
|
class ScreenshotRequest {
|
|
encoding: 'jpg' | 'png' | 'webp';
|
|
quality: number;
|
|
headers: any;
|
|
|
|
correlation: string;
|
|
|
|
resultURL: string;
|
|
|
|
targetURL: string;
|
|
targetField: string;
|
|
}
|
|
|
|
// from https://stackoverflow.com/a/12300351
|
|
function dataURItoBlob(dataURI: string) {
|
|
const byteString = atob(dataURI.split(',')[1]);
|
|
const mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]
|
|
|
|
const ab = new ArrayBuffer(byteString.length);
|
|
const ia = new Uint8Array(ab);
|
|
|
|
for (let i = 0; i < byteString.length; i++) {
|
|
ia[i] = byteString.charCodeAt(i);
|
|
}
|
|
|
|
const blob = new Blob([ab], {type: mimeString});
|
|
return blob;
|
|
}
|
|
|
|
class ScreenshotUI {
|
|
renderer: any;
|
|
rtTexture: any;
|
|
sceneRTT: any;
|
|
cameraRTT: any;
|
|
material: any;
|
|
request: ScreenshotRequest;
|
|
|
|
initialize() {
|
|
window.addEventListener('message', event => {
|
|
this.request = event.data.request;
|
|
});
|
|
|
|
window.addEventListener('resize', event => {
|
|
this.resize();
|
|
});
|
|
|
|
const cameraRTT: any = new OrthographicCamera( window.innerWidth / -2, window.innerWidth / 2, window.innerHeight / 2, window.innerHeight / -2, -10000, 10000 );
|
|
cameraRTT.position.z = 100;
|
|
|
|
const sceneRTT: any = new Scene();
|
|
|
|
const rtTexture = new WebGLRenderTarget( window.innerWidth, window.innerHeight, { minFilter: LinearFilter, magFilter: NearestFilter, format: RGBAFormat, type: UnsignedByteType } );
|
|
const gameTexture: any = new CfxTexture( );
|
|
gameTexture.needsUpdate = true;
|
|
|
|
const material = new ShaderMaterial( {
|
|
|
|
uniforms: { "tDiffuse": { value: gameTexture } },
|
|
vertexShader: `
|
|
varying vec2 vUv;
|
|
|
|
void main() {
|
|
vUv = vec2(uv.x, 1.0-uv.y); // fuck gl uv coords
|
|
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
|
|
}
|
|
`,
|
|
fragmentShader: `
|
|
varying vec2 vUv;
|
|
uniform sampler2D tDiffuse;
|
|
|
|
void main() {
|
|
gl_FragColor = texture2D( tDiffuse, vUv );
|
|
}
|
|
`
|
|
|
|
} );
|
|
|
|
this.material = material;
|
|
|
|
const plane = new PlaneBufferGeometry( window.innerWidth, window.innerHeight );
|
|
const quad: any = new Mesh( plane, material );
|
|
quad.position.z = -100;
|
|
sceneRTT.add( quad );
|
|
|
|
const renderer = new WebGLRenderer();
|
|
renderer.setPixelRatio( window.devicePixelRatio );
|
|
renderer.setSize( window.innerWidth, window.innerHeight );
|
|
renderer.autoClear = false;
|
|
|
|
document.getElementById('app').appendChild(renderer.domElement);
|
|
document.getElementById('app').style.display = 'none';
|
|
|
|
this.renderer = renderer;
|
|
this.rtTexture = rtTexture;
|
|
this.sceneRTT = sceneRTT;
|
|
this.cameraRTT = cameraRTT;
|
|
|
|
this.animate = this.animate.bind(this);
|
|
|
|
requestAnimationFrame(this.animate);
|
|
}
|
|
|
|
resize() {
|
|
const cameraRTT: any = new OrthographicCamera( window.innerWidth / -2, window.innerWidth / 2, window.innerHeight / 2, window.innerHeight / -2, -10000, 10000 );
|
|
cameraRTT.position.z = 100;
|
|
|
|
this.cameraRTT = cameraRTT;
|
|
|
|
const sceneRTT: any = new Scene();
|
|
|
|
const plane = new PlaneBufferGeometry( window.innerWidth, window.innerHeight );
|
|
const quad: any = new Mesh( plane, this.material );
|
|
quad.position.z = -100;
|
|
sceneRTT.add( quad );
|
|
|
|
this.sceneRTT = sceneRTT;
|
|
|
|
this.rtTexture = new WebGLRenderTarget( window.innerWidth, window.innerHeight, { minFilter: LinearFilter, magFilter: NearestFilter, format: RGBAFormat, type: UnsignedByteType } );
|
|
|
|
this.renderer.setSize( window.innerWidth, window.innerHeight );
|
|
}
|
|
|
|
animate() {
|
|
requestAnimationFrame(this.animate);
|
|
|
|
this.renderer.clear();
|
|
this.renderer.render(this.sceneRTT, this.cameraRTT, this.rtTexture, true);
|
|
|
|
if (this.request) {
|
|
const request = this.request;
|
|
this.request = null;
|
|
|
|
this.handleRequest(request);
|
|
}
|
|
}
|
|
|
|
handleRequest(request: ScreenshotRequest) {
|
|
// read the screenshot
|
|
const read = new Uint8Array(window.innerWidth * window.innerHeight * 4);
|
|
this.renderer.readRenderTargetPixels(this.rtTexture, 0, 0, window.innerWidth, window.innerHeight, read);
|
|
|
|
// create a temporary canvas to compress the image
|
|
const canvas = document.createElement('canvas');
|
|
canvas.style.display = 'inline';
|
|
canvas.width = window.innerWidth;
|
|
canvas.height = window.innerHeight;
|
|
|
|
// draw the image on the canvas
|
|
const d = new Uint8ClampedArray(read.buffer);
|
|
|
|
const cxt = canvas.getContext('2d');
|
|
cxt.putImageData(new ImageData(d, window.innerWidth, window.innerHeight), 0, 0);
|
|
|
|
// encode the image
|
|
let type = 'image/png';
|
|
|
|
switch (request.encoding) {
|
|
case 'jpg':
|
|
type = 'image/jpeg';
|
|
break;
|
|
case 'png':
|
|
type = 'image/png';
|
|
break;
|
|
case 'webp':
|
|
type = 'image/webp';
|
|
break;
|
|
}
|
|
|
|
if (!request.quality) {
|
|
request.quality = 0.92;
|
|
}
|
|
|
|
// actual encoding
|
|
const imageURL = canvas.toDataURL(type, request.quality);
|
|
|
|
const getFormData = () => {
|
|
const formData = new FormData();
|
|
formData.append(request.targetField, dataURItoBlob(imageURL), `screenshot.${request.encoding}`);
|
|
|
|
return formData;
|
|
};
|
|
|
|
// upload the image somewhere
|
|
fetch(request.targetURL, {
|
|
method: 'POST',
|
|
mode: 'cors',
|
|
headers: request.headers,
|
|
body: (request.targetField) ? getFormData() : JSON.stringify({
|
|
data: imageURL,
|
|
id: request.correlation
|
|
})
|
|
})
|
|
.then(response => response.text())
|
|
.then(text => {
|
|
if (request.resultURL) {
|
|
fetch(request.resultURL, {
|
|
method: 'POST',
|
|
mode: 'cors',
|
|
body: JSON.stringify({
|
|
data: text,
|
|
id: request.correlation
|
|
})
|
|
});
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
const ui = new ScreenshotUI();
|
|
ui.initialize(); |