Commit d2b31028 authored by Noé Le Cam's avatar Noé Le Cam
Browse files

Merge branch 'master' of git.unistra.fr:nlecam/canvastraight

parents 97c06869 d5846712
Pipeline #59055 passed with stage
in 5 seconds
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="preload" href="test.jpg" as="image" />
<link href="stylesheet.css" rel="stylesheet" />
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="scripts/libs/perspective.js"></script>
<script src="scripts/classes/linear_algebra.js"></script>
<link rel="preload" href="test.jpg" as="image" />
<link href="stylesheet.css" rel="stylesheet" />
<title>Perspective Transforms</title>
</head>
<body>
<main>
<!-- Libraries and utilities -->
<script src="scripts/libs/glfx.js"></script>
<script src="scripts/libs/linear_algebra.js"></script>
<title>Perspective Transforms</title>
</head>
<body>
<main>
<div>
<canvas width="1280" height="720" id="test_canvas"></canvas>
<div id="controls"><button id="straighten_btn">Straighten</button></div>
<canvas width="1280" height="720" id="2d_canvas"></canvas>
<div id="controls"><button id="straighten_btn">Straighten</button></div>
</div>
</main>
<script src="scripts/mains.js"></script>
<script src="scripts/rendering.js"></script>
<script src="scripts/selection.js"></script>
<script src="scripts/straighten.js"></script>
</body>
</html>
</main>
<!-- Scripts -->
<script src="scripts/mains.js"></script>
<script src="scripts/rendering.js"></script>
<script src="scripts/selection.js"></script>
<script src="scripts/straighten.js"></script>
</body>
</html>
\ No newline at end of file
/**
* Representation of a segment
*/
class Segment {
/**
* Create a segment.
* @param {Vector2D} a - First point
* @param {Vector2D} b - Second point
*/
constructor(a, b) {
this.a = a;
this.b = b;
}
/**
* Get segments length
*/
get length() {
return this.b.sub(this.a).length;
}
/**
* Returns the 'parallel percentage' to another segment:
* @example 1 : Segments are parallel to eachother
* 0 : Segments are orthogonal
*
* @param {Segment} segment - Segment to compare to
* @returns {number}
*/
parallelFactor(segment) {
let seg1 = this.b.sub(this.a).normalized;
let seg2 = segment.b.sub(segment.a).normalized;
return seg1.dot(seg2);
}
}
// var leftRight = [
// Segment(selection[0], selection[3]),
// Segment(selection[1], selection[2]),
// ];
// var topBottom = [
// Segment(selection[0], selection[3]),
// Segment(selection[1], selection[2]),
// ];
var rotationMat = Matrix2x2.rotationMatrix(
selection[3].sub(selection[0]).angleTo(new Vector2D(1, 1))
);
selection.forEach(
(elmt, index) => (this[index] = rotationMat.multiplyVector(elmt)),
selection
);
function sample(x, y, pixelData) {
return 255;
}
var button = document.getElementById("straighten_btn");
button.addEventListener("click", function () {
window.cancelAnimationFrame(render2d);
rendering = false;
console.log("UwU");
let imgData = context2d.getImageData(0, 0, canvas.width, canvas.height);
let screenPixels = Array.from(imgData.data);
for (let i = 0; i < imgData.data.length; i += 4) {
let x = ((i % (canvas.width * 4)) / 4);
let y = Math.floor(i / (canvas.width * 4));
// imgData.data[i] = screenPixels[pixelIndexY];
// imgData.data[i + 1] = screenPixels[pixelIndexY];
// imgData.data[i + 2] = screenPixels[pixelIndexY];
// imgData.data[i + 3] = 255;
let pixelIndex = x * 4 + y * canvas.width * 4;
imgData.data[i] = screenPixels[pixelIndex];
imgData.data[i + 1] = screenPixels[pixelIndex+1];
imgData.data[i + 2] = screenPixels[pixelIndex+2];
imgData.data[i + 3] = 255;
// imgData.data[i] = y * 255;
// imgData.data[i + 1] = y * 255;
// imgData.data[i + 2] = y * 255;
// imgData.data[i + 3] = 255;
}
context2d.fillStyle = "#000000";
context2d.fillRect(0, 0, canvas.width, canvas.height);
context2d.putImageData(imgData, 0, 0);
});
function setupGl(gl) {
if (gl === null) {
alert(
"Unable to initialize WebGL. Your browser or machine may not support it."
);
return;
}
// Set clear color to black, fully opaque
gl.clearColor(0.0, 0.0, 0.0, 1.0);
// Clear the color buffer with specified clear color
gl.clear(gl.COLOR_BUFFER_BIT);
}
function createShader(gl, type, source) {
var shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
var success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
if (success) {
return shader;
}
gl.deleteShader(shader);
}
function createProgram(gl, vertexShader, fragmentShader) {
var program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
var success = gl.getProgramParameter(program, gl.LINK_STATUS);
if (success) {
return program;
}
console.log(gl.getProgramInfoLog(program));
gl.deleteProgram(program);
}
setupGl(contextGl);
var vertexShaderSource;
async function a() {
let u = await readFile("shaders/vertex_shader_2d.vert");
vertexShaderSource = u;
}
a();
console.log(vertexShaderSource);
var fragmentShaderSource = readFile("shaders/fragment_shader_2d.frag");
var vertexShader = createShader(
contextGl,
contextGl.VERTEX_SHADER,
vertexShaderSource
);
var fragmentShader = createShader(
contextGl,
contextGl.FRAGMENT_SHADER,
fragmentShaderSource
);
var program = createProgram(contextGl, vertexShader, fragmentShader);
var positionAttributeLocation = contextGl.getAttribLocation(
program,
"a_position"
);
var positionBuffer = contextGl.createBuffer();
contextGl.bindBuffer(contextGl.ARRAY_BUFFER, positionBuffer);
var positions = [0, 0, 0, 0.5, 0.7, 0];
contextGl.bufferData(
contextGl.ARRAY_BUFFER,
new Float32Array(positions),
contextGl.STATIC_DRAW
);
contextGl.viewport(0, 0, contextGl.canvas.width, contextGl.canvas.height);
contextGl.clearColor(0, 0, 0, 0);
contextGl.clear(contextGl.COLOR_BUFFER_BIT);
contextGl.useProgram(program);
contextGl.enableVertexAttribArray(positionAttributeLocation);
contextGl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// Tell the attribute how to get data out of positionBuffer (ARRAY_BUFFER)
var size = 2; // 2 components per iteration
var type = gl.FLOAT; // the data is 32bit floats
var normalize = false; // don't normalize the data
var stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
var offset = 0; // start at the beginning of the buffer
contextGl.vertexAttribPointer(
positionAttributeLocation,
size,
type,
normalize,
stride,
offset
);
var primitiveType = contextGl.TRIANGLES;
var offset = 0;
var count = 3;
contextGl.drawArrays(primitiveType, offset, count);
This diff is collapsed.
......@@ -35,6 +35,10 @@ class Vector2D {
angleTo(vector) {
Math.acos(this.dot(vector) - this.length * vector.length);
}
get asArray() {
return [this.x, this.y];
}
}
class Matrix2x2 {
......
const canvas = document.getElementById("test_canvas");
const canvas = document.getElementById("2d_canvas");
const fxCanvas = fx.canvas();
/** @type {CanvasRenderingcontext2d} */
const context2d = canvas.getContext("2d");
/** @type {WebGLRenderingContext} */
// const contextGl = canvasGl.getContext("webgl");
async function readFile(path) {
let response = await fetch(path);
return response.text();
}
var selection = [
new Vector2D(50, 50),
new Vector2D(canvas.width - 50, 50),
new Vector2D(canvas.width - 50, canvas.height - 50),
new Vector2D(50, canvas.height - 50),
new Vector2D(50, 50),
new Vector2D(canvas.width - 50, 50),
new Vector2D(canvas.width - 50, canvas.height - 50),
new Vector2D(50, canvas.height - 50),
];
var rendering = true;
var img = new Image();
img.src = "cat.jpg";
img.src = "test.jpg";
// Calculate the height ratio to the canvas
var ratioToCanvas = canvas.height / img.height;
/**
......@@ -9,35 +10,38 @@ var ratioToCanvas = canvas.height / img.height;
* @param {Vector2D[]} point
*/
function drawQuad(points) {
// Styling
context2d.lineWidth = 2;
context2d.strokeStyle = "#ffffff";
context2d.fillStyle = "#ffffff";
// Styling
context2d.lineWidth = 2;
context2d.strokeStyle = "#ffffff";
context2d.fillStyle = "#ffffff";
// Drawing
context2d.beginPath();
context2d.moveTo(points[3].x, points[3].y);
// Drawing
context2d.beginPath();
context2d.moveTo(points[3].x, points[3].y);
for (let i = 0; i < 4; i++) {
context2d.lineTo(points[i].x, points[i].y);
context2d.stroke();
context2d.fillRect(points[i].x - 10, points[i].y - 10, 20, 20);
}
for (let i = 0; i < 4; i++) {
context2d.lineTo(points[i].x, points[i].y);
context2d.stroke();
context2d.fillRect(points[i].x - 10, points[i].y - 10, 20, 20);
}
}
function render() {
if (!rendering) return;
/**
* Render loop
*/
function render2d() {
if (!rendering) return;
context2d.clearRect(0, 0, canvas.width, canvas.height);
context2d.drawImage(
img,
0,
0,
img.width * ratioToCanvas,
img.height * ratioToCanvas
);
drawQuad(selection);
requestAnimationFrame(render);
context2d.clearRect(0, 0, canvas.width, canvas.height);
context2d.drawImage(
img,
0,
0,
img.width * ratioToCanvas,
img.height * ratioToCanvas
);
drawQuad(selection);
requestAnimationFrame(render2d);
}
render();
render2d();
\ No newline at end of file
......@@ -6,49 +6,49 @@ var _grabbedHandle = null;
* @param {Vector2D[]} bounds
*/
function inBounds(point, bounds) {
return (
point.x > bounds[0].x &&
point.x < bounds[1].x &&
point.y > bounds[0].y &&
point.y < bounds[1].y
);
}
return (
point.x > bounds[0].x &&
point.x < bounds[1].x &&
point.y > bounds[0].y &&
point.y < bounds[1].y
);
}
function handleMouseDown(e) {
// Get mouse positon as a Vector2D
let position = new Vector2D(e.offsetX, e.offsetY);
let closest = {
index: 0,
distance: selection[0].sub(position).lengthSquared,
};
// Find the closest handle
for (let i = 1; i < selection.length; i++) {
let distanceToMouse = selection[i].sub(position).lengthSquared;
if (distanceToMouse < closest.distance) {
closest.distance = distanceToMouse;
closest.index = i;
}
}
// Compute the bouding box of the handle
let handleBounds = [
selection[closest.index].add(new Vector2D(-10, -10)),
selection[closest.index].add(new Vector2D(10, 10)),
];
// Select the handle if mouse in bouding box
if (inBounds(position, handleBounds)) _grabbedHandle = closest.index;
// Get mouse positon as a Vector2D
let position = new Vector2D(e.offsetX, e.offsetY);
let closest = {
index: 0,
distance: selection[0].sub(position).lengthSquared,
};
// Find the closest handle
for (let i = 1; i < selection.length; i++) {
let distanceToMouse = selection[i].sub(position).lengthSquared;
if (distanceToMouse < closest.distance) {
closest.distance = distanceToMouse;
closest.index = i;
}
}
// Compute the bouding box of the handle
let handleBounds = [
selection[closest.index].add(new Vector2D(-10, -10)),
selection[closest.index].add(new Vector2D(10, 10)),
];
// Select the handle if mouse in bouding box
if (inBounds(position, handleBounds)) _grabbedHandle = closest.index;
}
function handleMouseUp(e) {
_grabbedHandle = null;
_grabbedHandle = null;
}
function handleMouseMove(e) {
if (_grabbedHandle == null) return;
// Move the handle according to the mouse
selection[_grabbedHandle] = new Vector2D(e.offsetX, e.offsetY);
if (_grabbedHandle == null) return;
// Move the handle according to the mouse
selection[_grabbedHandle] = new Vector2D(e.offsetX, e.offsetY);
}
canvas.addEventListener("mousedown", handleMouseDown);
......
var button = document.getElementById("straighten_btn");
/**
* Representation of a segment
* On staigthen buton click
*/
class Segment {
/**
* Create a segment.
* @param {Vector2D} a - First point
* @param {Vector2D} b - Second point
*/
constructor(a, b) {
this.a = a;
this.b = b;
}
/**
* Get segments length
*/
get length() {
return this.b.sub(this.a).length;
}
/**
* Returns the 'parallel percentage' to another segment:
* @example 1 : Segments are parallel to eachother
* 0 : Segments are orthogonal
*
* @param {Segment} segment - Segment to compare to
* @returns {number}
*/
parallelFactor(segment) {
let seg1 = this.b.sub(this.a).normalized;
let seg2 = segment.b.sub(segment.a).normalized;
return seg1.dot(seg2);
}
}
// var leftRight = [
// Segment(selection[0], selection[3]),
// Segment(selection[1], selection[2]),
// ];
button.addEventListener("click", function () {
// Stop render loop
window.cancelAnimationFrame(render2d);
rendering = false;
// var topBottom = [
// Segment(selection[0], selection[3]),
// Segment(selection[1], selection[2]),
// ];
// Draw the image without gizmo
context2d.drawImage(
img,
0,
0,
img.width * ratioToCanvas,
img.height * ratioToCanvas
);
var rotationMat = Matrix2x2.rotationMatrix(
selection[3].sub(selection[0]).angleTo(new Vector2D(1, 1))
);
selection.forEach(
(elmt, index) => (this[index] = rotationMat.multiplyVector(elmt)),
selection
);
// Perspective transform
function sample(x, y, pixelData) {
return 255;
}
var texture = fxCanvas.texture(canvas);
fxCanvas.draw(texture);
var button = document.getElementById("straighten_btn");
button.addEventListener("click", function () {
window.cancelAnimationFrame(render);
rendering = false;
var h_t = canvas.height;
var w_t = Math.floor(canvas.height / (3 / 4)); // A4 paper dimensions
let imgData = context2d.getImageData(0, 0, canvas.width, canvas.height);
let screenPixels = Array.from(imgData.data);
let from = selection.map((x) => x.asArray).flat();
let to = [0, 0, w_t, 0, w_t, h_t, 0, h_t];
for (let i = 0; i < imgData.data.length; i += 4) {
let x = ((i % (canvas.width * 4)) / 4);
let y = Math.floor(i / (canvas.width * 4));
// imgData.data[i] = screenPixels[pixelIndexY];
// imgData.data[i + 1] = screenPixels[pixelIndexY];
// imgData.data[i + 2] = screenPixels[pixelIndexY];
// imgData.data[i + 3] = 255;
let nX = x / canvas.width;
y+= x;
let pixelIndex = x * 4 + y * canvas.width * 4;
fxCanvas.perspective(from, to);
fxCanvas.update();
imgData.data[i] = screenPixels[pixelIndex];
imgData.data[i + 1] = screenPixels[pixelIndex+1];
imgData.data[i + 2] = screenPixels[pixelIndex+2];
imgData.data[i + 3] = 255;
var trs = new Image();
trs.src = fxCanvas.toDataURL("image/png");
// imgData.data[i] = y * 255;
// imgData.data[i + 1] = y * 255;
// imgData.data[i + 2] = y * 255;
// imgData.data[i + 3] = 255;
}
context2d.fillStyle = "#000000";
context2d.fillRect(0, 0, canvas.width, canvas.height);
context2d.putImageData(imgData, 0, 0);
trs.onload = () => {
context2d.fillStyle = "#000000";
context2d.fillRect(0, 0, canvas.width, canvas.height);
// Draw the resulting image at the center
context2d.drawImage(trs, 0, 0, w_t, h_t, (canvas.width - w_t) / 2, 0, w_t, h_t);
};
});
// fragment shaders don't have a default precision so we need
// to pick one. mediump is a good default
precision mediump float;
uniform sampler2D float u_image;
varying vec2 v_texCoord;
void main() {
// gl_FragColor is a special variable a fragment shader
// is responsible for setting
gl_FragColor = texture2D(u_image, v_texCoord); // return reddish-purple
}
\ No newline at end of file
// an attribute will receive data from a buffer
attribute vec4 a_position;
varying vec2 v_texCoord;