Scripts/resources/[cfx-default]/[local]/screenshot-basic/ui/src/main.ts
2024-12-29 20:48:41 +01:00

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();