pdf.js/src/display/webgl.js
Jonas Jenwald 36881e3770 Ensure that all import and require statements, in the entire code-base, have a .js file extension
In order to eventually get rid of SystemJS and start using native `import`s instead, we'll need to provide "complete" file identifiers since otherwise there'll be MIME type errors when attempting to use `import`.
2020-01-04 13:01:43 +01:00

500 lines
17 KiB
JavaScript

/* Copyright 2014 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-disable no-multi-str */
import { shadow } from "../shared/util.js";
class WebGLContext {
constructor({ enable = false }) {
this._enabled = enable === true;
}
get isEnabled() {
let enabled = this._enabled;
if (enabled) {
enabled = WebGLUtils.tryInitGL();
}
return shadow(this, "isEnabled", enabled);
}
composeSMask({ layer, mask, properties }) {
return WebGLUtils.composeSMask(layer, mask, properties);
}
drawFigures({ width, height, backgroundColor, figures, context }) {
return WebGLUtils.drawFigures(
width,
height,
backgroundColor,
figures,
context
);
}
clear() {
WebGLUtils.cleanup();
}
}
var WebGLUtils = (function WebGLUtilsClosure() {
function loadShader(gl, code, shaderType) {
var shader = gl.createShader(shaderType);
gl.shaderSource(shader, code);
gl.compileShader(shader);
var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
if (!compiled) {
var errorMsg = gl.getShaderInfoLog(shader);
throw new Error("Error during shader compilation: " + errorMsg);
}
return shader;
}
function createVertexShader(gl, code) {
return loadShader(gl, code, gl.VERTEX_SHADER);
}
function createFragmentShader(gl, code) {
return loadShader(gl, code, gl.FRAGMENT_SHADER);
}
function createProgram(gl, shaders) {
var program = gl.createProgram();
for (var i = 0, ii = shaders.length; i < ii; ++i) {
gl.attachShader(program, shaders[i]);
}
gl.linkProgram(program);
var linked = gl.getProgramParameter(program, gl.LINK_STATUS);
if (!linked) {
var errorMsg = gl.getProgramInfoLog(program);
throw new Error("Error during program linking: " + errorMsg);
}
return program;
}
function createTexture(gl, image, textureId) {
gl.activeTexture(textureId);
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
// Set the parameters so we can render any size image.
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
// Upload the image into the texture.
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
return texture;
}
var currentGL, currentCanvas;
function generateGL() {
if (currentGL) {
return;
}
// The temporary canvas is used in the WebGL context.
currentCanvas = document.createElement("canvas");
currentGL = currentCanvas.getContext("webgl", {
premultipliedalpha: false,
});
}
var smaskVertexShaderCode =
"\
attribute vec2 a_position; \
attribute vec2 a_texCoord; \
\
uniform vec2 u_resolution; \
\
varying vec2 v_texCoord; \
\
void main() { \
vec2 clipSpace = (a_position / u_resolution) * 2.0 - 1.0; \
gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1); \
\
v_texCoord = a_texCoord; \
} ";
var smaskFragmentShaderCode =
"\
precision mediump float; \
\
uniform vec4 u_backdrop; \
uniform int u_subtype; \
uniform sampler2D u_image; \
uniform sampler2D u_mask; \
\
varying vec2 v_texCoord; \
\
void main() { \
vec4 imageColor = texture2D(u_image, v_texCoord); \
vec4 maskColor = texture2D(u_mask, v_texCoord); \
if (u_backdrop.a > 0.0) { \
maskColor.rgb = maskColor.rgb * maskColor.a + \
u_backdrop.rgb * (1.0 - maskColor.a); \
} \
float lum; \
if (u_subtype == 0) { \
lum = maskColor.a; \
} else { \
lum = maskColor.r * 0.3 + maskColor.g * 0.59 + \
maskColor.b * 0.11; \
} \
imageColor.a *= lum; \
imageColor.rgb *= imageColor.a; \
gl_FragColor = imageColor; \
} ";
var smaskCache = null;
function initSmaskGL() {
var canvas, gl;
generateGL();
canvas = currentCanvas;
currentCanvas = null;
gl = currentGL;
currentGL = null;
// setup a GLSL program
var vertexShader = createVertexShader(gl, smaskVertexShaderCode);
var fragmentShader = createFragmentShader(gl, smaskFragmentShaderCode);
var program = createProgram(gl, [vertexShader, fragmentShader]);
gl.useProgram(program);
var cache = {};
cache.gl = gl;
cache.canvas = canvas;
cache.resolutionLocation = gl.getUniformLocation(program, "u_resolution");
cache.positionLocation = gl.getAttribLocation(program, "a_position");
cache.backdropLocation = gl.getUniformLocation(program, "u_backdrop");
cache.subtypeLocation = gl.getUniformLocation(program, "u_subtype");
var texCoordLocation = gl.getAttribLocation(program, "a_texCoord");
var texLayerLocation = gl.getUniformLocation(program, "u_image");
var texMaskLocation = gl.getUniformLocation(program, "u_mask");
// provide texture coordinates for the rectangle.
var texCoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
// prettier-ignore
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
0.0, 0.0,
1.0, 0.0,
0.0, 1.0,
0.0, 1.0,
1.0, 0.0,
1.0, 1.0]), gl.STATIC_DRAW);
gl.enableVertexAttribArray(texCoordLocation);
gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0);
gl.uniform1i(texLayerLocation, 0);
gl.uniform1i(texMaskLocation, 1);
smaskCache = cache;
}
function composeSMask(layer, mask, properties) {
var width = layer.width,
height = layer.height;
if (!smaskCache) {
initSmaskGL();
}
var cache = smaskCache,
canvas = cache.canvas,
gl = cache.gl;
canvas.width = width;
canvas.height = height;
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
gl.uniform2f(cache.resolutionLocation, width, height);
if (properties.backdrop) {
gl.uniform4f(
cache.resolutionLocation,
properties.backdrop[0],
properties.backdrop[1],
properties.backdrop[2],
1
);
} else {
gl.uniform4f(cache.resolutionLocation, 0, 0, 0, 0);
}
gl.uniform1i(
cache.subtypeLocation,
properties.subtype === "Luminosity" ? 1 : 0
);
// Create a textures
var texture = createTexture(gl, layer, gl.TEXTURE0);
var maskTexture = createTexture(gl, mask, gl.TEXTURE1);
// Create a buffer and put a single clipspace rectangle in
// it (2 triangles)
var buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
// prettier-ignore
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
0, 0,
width, 0,
0, height,
0, height,
width, 0,
width, height]), gl.STATIC_DRAW);
gl.enableVertexAttribArray(cache.positionLocation);
gl.vertexAttribPointer(cache.positionLocation, 2, gl.FLOAT, false, 0, 0);
// draw
gl.clearColor(0, 0, 0, 0);
gl.enable(gl.BLEND);
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES, 0, 6);
gl.flush();
gl.deleteTexture(texture);
gl.deleteTexture(maskTexture);
gl.deleteBuffer(buffer);
return canvas;
}
var figuresVertexShaderCode =
"\
attribute vec2 a_position; \
attribute vec3 a_color; \
\
uniform vec2 u_resolution; \
uniform vec2 u_scale; \
uniform vec2 u_offset; \
\
varying vec4 v_color; \
\
void main() { \
vec2 position = (a_position + u_offset) * u_scale; \
vec2 clipSpace = (position / u_resolution) * 2.0 - 1.0; \
gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1); \
\
v_color = vec4(a_color / 255.0, 1.0); \
} ";
var figuresFragmentShaderCode =
"\
precision mediump float; \
\
varying vec4 v_color; \
\
void main() { \
gl_FragColor = v_color; \
} ";
var figuresCache = null;
function initFiguresGL() {
var canvas, gl;
generateGL();
canvas = currentCanvas;
currentCanvas = null;
gl = currentGL;
currentGL = null;
// setup a GLSL program
var vertexShader = createVertexShader(gl, figuresVertexShaderCode);
var fragmentShader = createFragmentShader(gl, figuresFragmentShaderCode);
var program = createProgram(gl, [vertexShader, fragmentShader]);
gl.useProgram(program);
var cache = {};
cache.gl = gl;
cache.canvas = canvas;
cache.resolutionLocation = gl.getUniformLocation(program, "u_resolution");
cache.scaleLocation = gl.getUniformLocation(program, "u_scale");
cache.offsetLocation = gl.getUniformLocation(program, "u_offset");
cache.positionLocation = gl.getAttribLocation(program, "a_position");
cache.colorLocation = gl.getAttribLocation(program, "a_color");
figuresCache = cache;
}
function drawFigures(width, height, backgroundColor, figures, context) {
if (!figuresCache) {
initFiguresGL();
}
var cache = figuresCache,
canvas = cache.canvas,
gl = cache.gl;
canvas.width = width;
canvas.height = height;
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
gl.uniform2f(cache.resolutionLocation, width, height);
// count triangle points
var count = 0;
var i, ii, rows;
for (i = 0, ii = figures.length; i < ii; i++) {
switch (figures[i].type) {
case "lattice":
rows = (figures[i].coords.length / figures[i].verticesPerRow) | 0;
count += (rows - 1) * (figures[i].verticesPerRow - 1) * 6;
break;
case "triangles":
count += figures[i].coords.length;
break;
}
}
// transfer data
var coords = new Float32Array(count * 2);
var colors = new Uint8Array(count * 3);
var coordsMap = context.coords,
colorsMap = context.colors;
var pIndex = 0,
cIndex = 0;
for (i = 0, ii = figures.length; i < ii; i++) {
var figure = figures[i],
ps = figure.coords,
cs = figure.colors;
switch (figure.type) {
case "lattice":
var cols = figure.verticesPerRow;
rows = (ps.length / cols) | 0;
for (var row = 1; row < rows; row++) {
var offset = row * cols + 1;
for (var col = 1; col < cols; col++, offset++) {
coords[pIndex] = coordsMap[ps[offset - cols - 1]];
coords[pIndex + 1] = coordsMap[ps[offset - cols - 1] + 1];
coords[pIndex + 2] = coordsMap[ps[offset - cols]];
coords[pIndex + 3] = coordsMap[ps[offset - cols] + 1];
coords[pIndex + 4] = coordsMap[ps[offset - 1]];
coords[pIndex + 5] = coordsMap[ps[offset - 1] + 1];
colors[cIndex] = colorsMap[cs[offset - cols - 1]];
colors[cIndex + 1] = colorsMap[cs[offset - cols - 1] + 1];
colors[cIndex + 2] = colorsMap[cs[offset - cols - 1] + 2];
colors[cIndex + 3] = colorsMap[cs[offset - cols]];
colors[cIndex + 4] = colorsMap[cs[offset - cols] + 1];
colors[cIndex + 5] = colorsMap[cs[offset - cols] + 2];
colors[cIndex + 6] = colorsMap[cs[offset - 1]];
colors[cIndex + 7] = colorsMap[cs[offset - 1] + 1];
colors[cIndex + 8] = colorsMap[cs[offset - 1] + 2];
coords[pIndex + 6] = coords[pIndex + 2];
coords[pIndex + 7] = coords[pIndex + 3];
coords[pIndex + 8] = coords[pIndex + 4];
coords[pIndex + 9] = coords[pIndex + 5];
coords[pIndex + 10] = coordsMap[ps[offset]];
coords[pIndex + 11] = coordsMap[ps[offset] + 1];
colors[cIndex + 9] = colors[cIndex + 3];
colors[cIndex + 10] = colors[cIndex + 4];
colors[cIndex + 11] = colors[cIndex + 5];
colors[cIndex + 12] = colors[cIndex + 6];
colors[cIndex + 13] = colors[cIndex + 7];
colors[cIndex + 14] = colors[cIndex + 8];
colors[cIndex + 15] = colorsMap[cs[offset]];
colors[cIndex + 16] = colorsMap[cs[offset] + 1];
colors[cIndex + 17] = colorsMap[cs[offset] + 2];
pIndex += 12;
cIndex += 18;
}
}
break;
case "triangles":
for (var j = 0, jj = ps.length; j < jj; j++) {
coords[pIndex] = coordsMap[ps[j]];
coords[pIndex + 1] = coordsMap[ps[j] + 1];
colors[cIndex] = colorsMap[cs[j]];
colors[cIndex + 1] = colorsMap[cs[j] + 1];
colors[cIndex + 2] = colorsMap[cs[j] + 2];
pIndex += 2;
cIndex += 3;
}
break;
}
}
// draw
if (backgroundColor) {
gl.clearColor(
backgroundColor[0] / 255,
backgroundColor[1] / 255,
backgroundColor[2] / 255,
1.0
);
} else {
gl.clearColor(0, 0, 0, 0);
}
gl.clear(gl.COLOR_BUFFER_BIT);
var coordsBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, coordsBuffer);
gl.bufferData(gl.ARRAY_BUFFER, coords, gl.STATIC_DRAW);
gl.enableVertexAttribArray(cache.positionLocation);
gl.vertexAttribPointer(cache.positionLocation, 2, gl.FLOAT, false, 0, 0);
var colorsBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, colorsBuffer);
gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
gl.enableVertexAttribArray(cache.colorLocation);
gl.vertexAttribPointer(
cache.colorLocation,
3,
gl.UNSIGNED_BYTE,
false,
0,
0
);
gl.uniform2f(cache.scaleLocation, context.scaleX, context.scaleY);
gl.uniform2f(cache.offsetLocation, context.offsetX, context.offsetY);
gl.drawArrays(gl.TRIANGLES, 0, count);
gl.flush();
gl.deleteBuffer(coordsBuffer);
gl.deleteBuffer(colorsBuffer);
return canvas;
}
return {
tryInitGL() {
try {
generateGL();
return !!currentGL;
} catch (ex) {}
return false;
},
composeSMask,
drawFigures,
cleanup() {
if (smaskCache && smaskCache.canvas) {
smaskCache.canvas.width = 0;
smaskCache.canvas.height = 0;
}
if (figuresCache && figuresCache.canvas) {
figuresCache.canvas.width = 0;
figuresCache.canvas.height = 0;
}
smaskCache = null;
figuresCache = null;
},
};
})();
export { WebGLContext };