added proof-of-concept base code provided by docent
This commit is contained in:
parent
aa229b2715
commit
6b0f1726e2
59
src/app.html
59
src/app.html
@ -5,8 +5,67 @@
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
%sveltekit.head%
|
||||
<style>
|
||||
.myPreview {
|
||||
overflow: hidden;
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
background: lightgray;
|
||||
}
|
||||
|
||||
.myPreview__image {
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
transform-origin: 0 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
<div style="display: flex; flex-flow: row">
|
||||
<svg width="300" height="300">
|
||||
<path d="" fill="none" stroke="red" />
|
||||
<circle cx="211" cy="41" r="5" />
|
||||
<circle cx="130" cy="50" r="5" />
|
||||
<circle cx="85" cy="247" r="5" />
|
||||
<circle cx="173" cy="261" r="5" />
|
||||
</svg>
|
||||
|
||||
<div class="myPreview" style="display: none">
|
||||
<img class="myPreview__image" src="images/sample.jpg" alt="sample" />
|
||||
</div>
|
||||
|
||||
<canvas width="300" height="300" class="myCanvas"></canvas>
|
||||
|
||||
<canvas width="300" height="300" id="resultCanvas"></canvas>
|
||||
</div>
|
||||
<script src="https://unpkg.com/perspective-transform@1.1.3/dist/perspective-transform.js"></script>
|
||||
<script src="js/editor.js"></script>
|
||||
<script src="js/preview.js"></script>
|
||||
<script src="js/webgl.js"></script>
|
||||
<script>
|
||||
var download = function(){
|
||||
var link = document.createElement('a');
|
||||
link.download = 'filename.png';
|
||||
link.href = document.getElementById("resultCanvas").toDataURL();
|
||||
link.click();
|
||||
}
|
||||
|
||||
const resultCanvas = document.getElementById("resultCanvas");
|
||||
const rctx = resultCanvas.getContext("2d");
|
||||
|
||||
make_base();
|
||||
|
||||
function make_base() {
|
||||
srcCtx = document.querySelector(".myCanvas");
|
||||
rctx.drawImage(srcCtx, 0, 0);
|
||||
|
||||
base_image = new Image();
|
||||
base_image.src = "images/mockup-test.png";
|
||||
base_image.onload = function() {
|
||||
rctx.drawImage(base_image, 0, 0);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,2 +0,0 @@
|
||||
<h1>Welcome to SvelteKit</h1>
|
||||
<p>Visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to read the documentation</p>
|
73
static/js/editor.js
Normal file
73
static/js/editor.js
Normal file
@ -0,0 +1,73 @@
|
||||
(function () {
|
||||
const svg = document.querySelector("svg");
|
||||
const path = svg.querySelector("path");
|
||||
const circles = toArray(svg.querySelectorAll("circle"));
|
||||
let draggedCircle = null;
|
||||
|
||||
function update() {
|
||||
const points = circles.map((circle) => ({
|
||||
x: parseFloat(circle.getAttribute("cx")),
|
||||
y: parseFloat(circle.getAttribute("cy")),
|
||||
}));
|
||||
|
||||
path.setAttribute("d", toSvgPath(points));
|
||||
|
||||
const event = new CustomEvent("myPointsChanged", { detail: points });
|
||||
document.dispatchEvent(event);
|
||||
|
||||
make_base();
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handlers
|
||||
*/
|
||||
|
||||
svg.addEventListener("mousedown", (event) => {
|
||||
if (circles.includes(event.target)) {
|
||||
draggedCircle = event.target;
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("mousemove", (event) => {
|
||||
if (draggedCircle) {
|
||||
const { height, left, top, width } = svg.getBoundingClientRect();
|
||||
const x = clamp(event.clientX - left, 0, width);
|
||||
const y = clamp(event.clientY - top, 0, height);
|
||||
|
||||
draggedCircle.setAttribute("cx", x);
|
||||
draggedCircle.setAttribute("cy", y);
|
||||
update();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("mouseup", () => {
|
||||
draggedCircle = null;
|
||||
});
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
update();
|
||||
});
|
||||
|
||||
/**
|
||||
* Helpers
|
||||
*/
|
||||
|
||||
function clamp(value, min, max) {
|
||||
return Math.max(min, Math.min(value, max));
|
||||
}
|
||||
|
||||
function toArray(value) {
|
||||
return Array.prototype.slice.call(value);
|
||||
}
|
||||
|
||||
function toSvgOperation(operation, { x, y }) {
|
||||
return `${operation} ${x} ${y}`;
|
||||
}
|
||||
|
||||
function toSvgPath(points) {
|
||||
return [
|
||||
toSvgOperation("M", points[points.length - 1]),
|
||||
...points.map((point) => toSvgOperation("L", point)),
|
||||
].join(" ");
|
||||
}
|
||||
})();
|
31
static/js/preview.js
Normal file
31
static/js/preview.js
Normal file
@ -0,0 +1,31 @@
|
||||
(function () {
|
||||
const image = document.querySelector(".myPreview__image");
|
||||
|
||||
function toCssMatrix(transform) {
|
||||
return `matrix3d(${toMatrix(transform.coeffs).join(",")})`;
|
||||
}
|
||||
|
||||
function toMatrix(t) {
|
||||
// prettier-ignore
|
||||
return [
|
||||
t[0], t[3], 0, t[6],
|
||||
t[1], t[4], 0, t[7],
|
||||
0, 0, 1, 0,
|
||||
t[2], t[5], 0, t[8]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handlers
|
||||
*/
|
||||
|
||||
document.addEventListener("myPointsChanged", (event) => {
|
||||
const points = event.detail.reduce(
|
||||
(result, { x, y }) => [...result, x, y],
|
||||
[]
|
||||
);
|
||||
|
||||
const transform = new PerspT([300, 0, 0, 0, 0, 300, 300, 300], points);
|
||||
image.style.transform = toCssMatrix(transform);
|
||||
});
|
||||
})();
|
214
static/js/webgl.js
Normal file
214
static/js/webgl.js
Normal file
@ -0,0 +1,214 @@
|
||||
(function () {
|
||||
const canvas = document.querySelector(".myCanvas");
|
||||
const image = document.querySelector(".myPreview__image");
|
||||
const gl = canvas.getContext("webgl", {
|
||||
preserveDrawingBuffer: true
|
||||
});
|
||||
|
||||
const plane = createPlane(gl);
|
||||
const texture = createTexture(gl);
|
||||
const shader = createImageShader(gl);
|
||||
let matrix = [];
|
||||
|
||||
function update() {
|
||||
gl.clearColor(255.0, 255.0, 255.0, 1.0);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
||||
|
||||
shader.use().setProjection(matrix).setTexture(texture);
|
||||
plane.draw(shader);
|
||||
}
|
||||
|
||||
function loadTexture() {
|
||||
drawToTexure(gl, texture, image);
|
||||
update();
|
||||
}
|
||||
|
||||
function toMatrix(t) {
|
||||
// prettier-ignore
|
||||
return [
|
||||
t[0], t[1], 0, t[2],
|
||||
t[3], t[4], 0, t[5],
|
||||
0, 0, 1, 0,
|
||||
t[6], t[7], 0, t[8]
|
||||
];
|
||||
}
|
||||
|
||||
if (image.naturalWidth && image.naturalHeight) {
|
||||
loadTexture();
|
||||
} else {
|
||||
image.addEventListener("load", () => {
|
||||
loadTexture();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handlers
|
||||
*/
|
||||
|
||||
document.addEventListener("myPointsChanged", (event) => {
|
||||
const points = event.detail.reduce(
|
||||
(result, { x, y }) => [...result, x / 150 - 1, 1 - y / 150],
|
||||
[]
|
||||
);
|
||||
|
||||
const transform = new PerspT([1, -1, -1, -1, -1, 1, 1, 1], points);
|
||||
matrix = toMatrix(transform.coeffs);
|
||||
|
||||
update();
|
||||
});
|
||||
|
||||
/**
|
||||
* Primitives
|
||||
*/
|
||||
|
||||
function createPlane(gl) {
|
||||
const positionBuffer = createBuffer(
|
||||
gl,
|
||||
[1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, -1.0]
|
||||
);
|
||||
|
||||
return {
|
||||
draw(shader) {
|
||||
shader.bindPositionBuffer(positionBuffer);
|
||||
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createImageShader(gl) {
|
||||
const vertexSource = `
|
||||
attribute vec4 aPosition;
|
||||
|
||||
uniform mat4 uProjection;
|
||||
|
||||
varying highp vec2 vTextureCoord;
|
||||
|
||||
void main() {
|
||||
vTextureCoord = vec2((aPosition.x - 1.0) * 0.5, (aPosition.y - 1.0) * 0.5);
|
||||
|
||||
gl_Position = vec4(aPosition.x, aPosition.y, 0.0, 1.0) * uProjection;
|
||||
}
|
||||
`;
|
||||
|
||||
const fragmentSource = `
|
||||
varying highp vec2 vTextureCoord;
|
||||
|
||||
uniform sampler2D uSampler;
|
||||
|
||||
void main() {
|
||||
gl_FragColor = texture2D(uSampler, vTextureCoord);
|
||||
}
|
||||
`;
|
||||
|
||||
const program = createProgram(
|
||||
gl,
|
||||
createShader(gl, vertexSource, gl.VERTEX_SHADER),
|
||||
createShader(gl, fragmentSource, gl.FRAGMENT_SHADER)
|
||||
);
|
||||
|
||||
const positionAttribute = gl.getAttribLocation(program, "aPosition");
|
||||
const projectionLocation = gl.getUniformLocation(program, "uProjection");
|
||||
const samplerLocation = gl.getUniformLocation(program, "uSampler");
|
||||
|
||||
const instance = {
|
||||
use() {
|
||||
gl.useProgram(program);
|
||||
gl.enableVertexAttribArray(positionAttribute);
|
||||
return instance;
|
||||
},
|
||||
bindPositionBuffer(buffer) {
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
||||
gl.vertexAttribPointer(positionAttribute, 2, gl.FLOAT, false, 0, 0);
|
||||
},
|
||||
setProjection(value) {
|
||||
gl.uniformMatrix4fv(projectionLocation, false, value);
|
||||
return instance;
|
||||
},
|
||||
setTexture(value) {
|
||||
gl.activeTexture(gl.TEXTURE0);
|
||||
gl.bindTexture(gl.TEXTURE_2D, value);
|
||||
gl.uniform1i(samplerLocation, 0);
|
||||
return instance;
|
||||
},
|
||||
};
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* WebGL helpers
|
||||
* https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Tutorial
|
||||
*/
|
||||
|
||||
function createBuffer(gl, positions) {
|
||||
const positionBuffer = gl.createBuffer();
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
|
||||
|
||||
return positionBuffer;
|
||||
}
|
||||
|
||||
function createProgram(gl, vertexShader, fragmentShader) {
|
||||
const program = gl.createProgram();
|
||||
gl.attachShader(program, vertexShader);
|
||||
gl.attachShader(program, fragmentShader);
|
||||
gl.linkProgram(program);
|
||||
|
||||
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
||||
const info = gl.getProgramInfoLog(program);
|
||||
throw `Could not compile WebGL program. \n\n${info}`;
|
||||
}
|
||||
|
||||
return program;
|
||||
}
|
||||
|
||||
function createShader(gl, source, type) {
|
||||
const shader = gl.createShader(type);
|
||||
gl.shaderSource(shader, source);
|
||||
gl.compileShader(shader);
|
||||
|
||||
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
||||
const info = gl.getShaderInfoLog(shader);
|
||||
throw `Could not compile WebGL program. \n\n${info}`;
|
||||
}
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
function createTexture(gl) {
|
||||
const texture = gl.createTexture();
|
||||
const pixel = new Uint8Array([0, 0, 255, 255]);
|
||||
|
||||
gl.bindTexture(gl.TEXTURE_2D, texture);
|
||||
gl.texImage2D(
|
||||
gl.TEXTURE_2D,
|
||||
0,
|
||||
gl.RGBA,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
gl.RGBA,
|
||||
gl.UNSIGNED_BYTE,
|
||||
pixel
|
||||
);
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
function drawToTexure(gl, texture, source) {
|
||||
gl.bindTexture(gl.TEXTURE_2D, texture);
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, source);
|
||||
|
||||
if (isPowerOf2(source.naturalWidth) && isPowerOf2(source.naturalHeight)) {
|
||||
gl.generateMipmap(gl.TEXTURE_2D);
|
||||
} else {
|
||||
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.LINEAR);
|
||||
}
|
||||
}
|
||||
|
||||
function isPowerOf2(value) {
|
||||
return (value & (value - 1)) === 0;
|
||||
}
|
||||
})();
|
Loading…
Reference in New Issue
Block a user