Browse Source

Exercise 02 finished

master
fdai7303 2 years ago
commit
e394a86973
  1. 11
      .gitignore
  2. 8
      .vscode/extensions.json
  3. 57
      .vscode/launch.json
  4. 65
      .vscode/tasks.json
  5. BIN
      dist/ai-logo.png
  6. BIN
      dist/checkerboard-finished.png
  7. 39
      dist/checkerboard.html
  8. BIN
      dist/circle-finished.png
  9. 40
      dist/circle.html
  10. 40
      dist/circlesimple.html
  11. BIN
      dist/fillscreen-finished.png
  12. 39
      dist/fillscreen.html
  13. BIN
      dist/gradient-finished.png
  14. 39
      dist/gradient.html
  15. 152
      exercises.json
  16. 51
      index.tpl.html
  17. 39
      package.json
  18. 26
      src/01/fillscreen.ts
  19. 41
      src/01/setup-fillscreen.ts
  20. 48
      src/02/checkerboard.ts
  21. 34
      src/02/circle.ts
  22. 32
      src/02/circlesimple.ts
  23. 24
      src/02/gradient.ts
  24. 41
      src/02/setup-checkerboard.ts
  25. 58
      src/02/setup-circle.ts
  26. 47
      src/02/setup-circlesimple.ts
  27. 41
      src/02/setup-gradient.ts
  28. 2
      src/index.ts
  29. 58
      src/ui/ui.ts
  30. 19
      tsconfig.json
  31. 83
      webpack.config.js

11
.gitignore

@ -0,0 +1,11 @@
node_modules
dist/*.bundle.js
dist/webpack.config.js
dist/webpack.config.js.map
package-lock.json
!dist
dist/dist
dist/src
dist/test
out
.decker

8
.vscode/extensions.json

@ -0,0 +1,8 @@
{
"recommendations": [
"firefox-devtools.vscode-firefox-debug",
"msjsdiag.debugger-for-chrome",
"hbenl.vscode-mocha-test-adapter",
"hbenl.vscode-test-explorer"
]
}

57
.vscode/launch.json

@ -0,0 +1,57 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "chrome",
"request": "launch",
"timeout": 20000,
"name": "Launch Chrome",
"url": "http://localhost:45217",
"webRoot": "${workspaceFolder}",
"preLaunchTask": "npm: start without browser",
"sourceMaps": true,
"disableNetworkCache": true
},
{
"name": "Launch Firefox",
"type": "firefox",
"request": "launch",
"timeout": 20000,
"reAttach": true,
"url": "http://localhost:45217",
"preLaunchTask": "npm: start without browser",
"webRoot": "${workspaceFolder}/dist/",
"suggestPathMappingWizard": true,
"pathMappings": [
{
"url": "webpack://gdv/src",
"path": "${workspaceFolder}/src"
}
]
},
{
"args": [
"--timeout",
"999999",
"--colors",
"-r",
"ts-node/register",
"${workspaceFolder}/test/*-spec.ts"
],
"internalConsoleOptions": "openOnSessionStart",
"name": "Mocha Tests",
"program": "${workspaceFolder}/node_modules/mocha/bin/_mocha",
"request": "launch",
"skipFiles": [
"<node_internals>/**"
],
"type": "pwa-node",
"env": {
"TS_NODE_COMPILER_OPTIONS": "{\"module\":\"commonjs\"}"
}
}
]
}

65
.vscode/tasks.json

@ -0,0 +1,65 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "start",
"problemMatcher": {
"owner": "webpack",
"severity": "error",
"fileLocation": "absolute",
"pattern": [
{
"regexp": "ERROR in (.*)",
"file": 1
},
{
"regexp": "\\((\\d+),(\\d+)\\):(.*)",
"line": 1,
"column": 2,
"message": 3
}
],
"background": {
"activeOnStart": true,
"beginsPattern": "Project is running at",
"endsPattern": "compiled successfully"
}
},
"label": "npm: start",
"detail": "start the development server",
"promptOnClose": true,
"isBackground": true,
},
{
"type": "npm",
"script": "start-no-browser",
"problemMatcher": {
"owner": "webpack",
"severity": "error",
"fileLocation": "absolute",
"pattern": [
{
"regexp": "ERROR in (.*)",
"file": 1
},
{
"regexp": "\\((\\d+),(\\d+)\\):(.*)",
"line": 1,
"column": 2,
"message": 3
}
],
"background": {
"activeOnStart": true,
"beginsPattern": "Project is running at",
"endsPattern": "compiled successfully"
}
},
"label": "npm: start without browser",
"detail": "start the development server without browser",
"promptOnClose": true,
"isBackground": true,
}
]
}

BIN
dist/ai-logo.png

After

Width: 225  |  Height: 225  |  Size: 3.4 KiB

BIN
dist/checkerboard-finished.png

After

Width: 300  |  Height: 300  |  Size: 3.2 KiB

39
dist/checkerboard.html

@ -0,0 +1,39 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Graphische Datenverarbeitung - 2D - Checkerboard</title>
<link rel="icon" type="image/png" href="ai-logo.png">
<script type="text/javascript" src="checkerboard.bundle.js"></script>
</head>
<body>
<nav class="navbar navbar-light bg-light">
<div class="container">
<a class="navbar-brand" href="/index.html">
<img src="ai-logo.png" width="30" height="30" class="d-inline-block align-top" alt="">
Graphische Datenverarbeitung
</a>
<div class="navbar-brand">Checkerboard</div>
</div>
</nav>
<br>
<div class="container">
<div class="row">
<figure class="figure col-sm-6">
<canvas id="result" class="figure-img mx-auto d-block" width="300" height="300"></canvas>
<figcaption class="figure-caption text-center">
Determine for each pixel if it is black or white to create a checkerboard pattern
</figcaption>
</figure>
<figure class="col-sm-6 figure">
<img class="figure-img mx-auto d-block" src="checkerboard-finished.png">
<figcaption class="figure-caption text-center">Reference image</figcaption>
</figure>
</div>
</div>
</body>
</html>

BIN
dist/circle-finished.png

After

Width: 300  |  Height: 300  |  Size: 1.4 KiB

40
dist/circle.html

@ -0,0 +1,40 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Graphische Datenverarbeitung - 2D - Circle</title>
<script type="text/javascript" src="circle.bundle.js"></script>
<link rel="icon" type="image/png" href="ai-logo.png">
</head>
<body>
<nav class="navbar navbar-light bg-light">
<div class="container">
<a class="navbar-brand" href="/index.html">
<img src="ai-logo.png" width="30" height="30" class="d-inline-block align-top" alt="">
Graphische Datenverarbeitung
</a>
<div class="navbar-brand">Circle</div>
</div>
</nav>
<br>
<div class="container">
<div class="row">
<figure class="figure col-sm-6">
<canvas id="result" class="figure-img mx-auto d-block" width="300" height="300">
</canvas>
<figcaption class="figure-caption text-center">
Determine for each pixel if it is black or white to create a circle
</figcaption>
</figure>
<figure class="col-sm-6 figure">
<img class="figure-img mx-auto d-block" src="circle-finished.png">
<figcaption class="figure-caption text-center">Reference image</figcaption>
</figure>
</div>
</div>
</body>
</html>

40
dist/circlesimple.html

@ -0,0 +1,40 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Graphische Datenverarbeitung - 2D - Circle</title>
<script type="text/javascript" src="circlesimple.bundle.js"></script>
<link rel="icon" type="image/png" href="ai-logo.png">
</head>
<body>
<nav class="navbar navbar-light bg-light">
<div class="container">
<a class="navbar-brand" href="/index.html">
<img src="ai-logo.png" width="30" height="30" class="d-inline-block align-top" alt="">
Graphische Datenverarbeitung
</a>
<div class="navbar-brand">Circle</div>
</div>
</nav>
<br>
<div class="container">
<div class="row">
<figure class="figure col-sm-6">
<canvas id="result" class="figure-img mx-auto d-block" width="300" height="300">
</canvas>
<figcaption class="figure-caption text-center">
Determine for each pixel if it is black or white to create a circle
</figcaption>
</figure>
<figure class="col-sm-6 figure">
<img class="figure-img mx-auto d-block" src="circle-finished.png">
<figcaption class="figure-caption text-center">Reference image</figcaption>
</figure>
</div>
</div>
</body>
</html>

BIN
dist/fillscreen-finished.png

After

Width: 300  |  Height: 300  |  Size: 869 B

39
dist/fillscreen.html

@ -0,0 +1,39 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Graphische Datenverarbeitung - 2D - Fillscreen</title>
<link rel="icon" type="image/png" href="ai-logo.png">
<script type="text/javascript" src="fillscreen.bundle.js"></script>
</head>
<body>
<nav class="navbar navbar-light bg-light">
<div class="container">
<a class="navbar-brand" href="/index.html">
<img src="ai-logo.png" width="30" height="30" class="d-inline-block align-top" alt="">
Graphische Datenverarbeitung
</a>
<div class="navbar-brand">Fillscreen</div>
</div>
</nav>
<br>
<div class="container">
<div class="row">
<figure class="figure col-sm-6">
<canvas id="result" class="figure-img mx-auto d-block" width="300" height="300"></canvas>
<figcaption class="figure-caption text-center">
Fill the screen with a single RGBA color #FF0000FF (red).
</figcaption>
</figure>
<figure class="col-sm-6 figure">
<img class="figure-img mx-auto d-block" src="fillscreen-finished.png">
<figcaption class="figure-caption text-center">Reference image</figcaption>
</figure>
</div>
</div>
</body>
</html>

BIN
dist/gradient-finished.png

After

Width: 300  |  Height: 300  |  Size: 3.1 KiB

39
dist/gradient.html

@ -0,0 +1,39 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Graphische Datenverarbeitung - 2D - gradient</title>
<link rel="icon" type="image/png" href="ai-logo.png">
<script type="text/javascript" src="gradient.bundle.js"></script>
</head>
<body>
<nav class="navbar navbar-light bg-light">
<div class="container">
<a class="navbar-brand" href="/index.html">
<img src="ai-logo.png" width="30" height="30" class="d-inline-block align-top" alt="">
Graphische Datenverarbeitung
</a>
<div class="navbar-brand">gradient</div>
</div>
</nav>
<br>
<div class="container">
<div class="row">
<figure class="figure col-sm-6">
<canvas id="result" class="figure-img mx-auto d-block" width="300" height="300"></canvas>
<figcaption class="figure-caption text-center">
Fill the screen with a gradient from #000000FF (black) to #FFFFFFFF (white).
</figcaption>
</figure>
<figure class="col-sm-6 figure">
<img class="figure-img mx-auto d-block" src="gradient-finished.png">
<figcaption class="figure-caption text-center">Reference image</figcaption>
</figure>
</div>
</div>
</body>
</html>

152
exercises.json

@ -0,0 +1,152 @@
[
{
"title": "01 Introduction to the exercises: Setup and index into a linearized array",
"entries":
[
{
"title": "Fill Screen",
"description": "Fill the entire buffer with a constant RGBA color #FF0000FF (red)",
"file": "fillscreen.html"
}
]
},
{
"title": "02 Creating patterns",
"entries":
[
{
"title": "Gradient",
"description": "Fill the buffer with a gradient from black to white (horizontal)",
"file": "gradient.html"
},
{
"title": "Checkerboard",
"description": "Create a checkerboard in the buffer by alternating between black and white patches",
"file": "checkerboard.html"
},
{
"title": "Simple Circle",
"description": "Warm up in thinking about distances",
"file": "circlesimple.html"
},
{
"title": "Circle",
"description": "Circle with movable center",
"file": "circle.html"
}
]
},
{
"title": "03 Quantisation",
"entries":
[
{
"title": "Grayscale",
"description": "Convert an RGB image to brightness values",
"file": "grayscale.html"
},
{
"title": "Quantisation to 4 brightness values",
"description": "Posterize an RGB image to 4 brightness values",
"file": "quantisegrayscale.html"
},
{
"title": "Quantisation to 4 values per color channel",
"description": "Posterize an RGB image to 4 brightness values per color channel",
"file": "quantisecolor.html"
},
{
"title": "Gamma correction",
"description": "Perform gamma correction on an image",
"file": "gammacorrection.html"
}
]
},
{
"title": "04 Lines",
"entries":
[
{
"title": "Simple DDA",
"description": "Draw a single line with the DDA algorithm",
"file": "ddasimple.html"
},
{
"title": "Simple Bresenham",
"description": "Draw a single line with the Bresenham algorithm",
"file": "bresenhamsimple.html"
},
{
"title": "DDA",
"description": "Draw lines with the DDA algorithm",
"file": "dda.html"
},
{
"title": "Bresenham",
"description": "Draw lines with the Bresenham algorithm",
"file": "bresenham.html"
}
]
},
{
"title": "05 Raytracing",
"entries":
[
{
"title": "Basic Raytracing",
"description": "Raytrace a single sphere",
"file": "raytracing.html"
},
{
"title": "Enhanced Raytracing",
"description": "Raytrace many spheres",
"file": "manyspheres.html"
}
]
},
{
"title": "06 Phong",
"entries":
[
{
"title": "Basic Lighting",
"description": "Introduce lighting in your scene",
"file": "phong.html"
}
]
},
{
"title": "07 Transformations",
"entries":
[
{
"title": "Basic Transformations",
"description": "Transform objects with matrices",
"file": "matrix.html"
}
]
},
{
"title": "08 Scene Graph",
"entries":
[
{
"title": "Basic Scene Graphs",
"description": "Arrange objects in a hierarchical data structure and render them",
"file": "scenegraph.html"
}
]
},
{
"title": "09 Rasterization",
"entries":
[
{
"title": "WebGL: Primitives, Buffers, Vertex and Fragment Shaders",
"description": "Understand rasterisation with WebGL",
"file": "threejssimple.html"
}
]
}
]

51
index.tpl.html

@ -0,0 +1,51 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>GDV Übungen</title>
<link rel="icon" type="image/png" href="ai-logo.png">
<script type="text/javascript" src="index.bundle.js"></script>
</head>
<body>
<nav class="navbar navbar-light bg-light">
<div class="container">
<a class="navbar-brand" href="/index.html">
<img src="ai-logo.png" width="30" height="30" class="d-inline-block align-top" alt="">
Graphische Datenverarbeitung
</a>
</div>
</nav>
<br>
<div class="list-group list-group-flush">
<% htmlWebpackPlugin.options.exercises.forEach((exercise) => { %>
<div class="list-group-item flex-column align-items-start">
<br>
<div class="d-flex w-100 justify-content-between">
<% if (exercise['exists'] === true) { %>
<h5 class="mb-2"><%= exercise['title'] %></h5>
<% } else { %>
<h5 class="mb-2" style="color:#C0C0C0"><%= exercise['title'] %></h5>
<% } %>
</div>
<% exercise['entries'].forEach((entry) => { %>
<% if (entry['exists'] === true) { %>
<a href=<%= entry['file'] %>
<% } else { %>
<a style="color:#C0C0C0"
<% } %>
class="list-group-item list-group-item-action flex-column align-items-start">
<div class="d-flex w-100 justify-content-between">
<h6 class="mb-0"><%= entry['title'] %></h6>
</div>
<p class="mb-0"><%= entry['description'] %> </p>
</a>
<% }); %>
</div>
<% }); %>
</div>
</body>
</html>

39
package.json

@ -0,0 +1,39 @@
{
"name": "gdv",
"version": "2022.2.0",
"description": "GDV Uebungen",
"scripts": {
"start": "webpack-dev-server --port 45217 --open",
"start-no-browser": "webpack-dev-server --port 45217",
"tests": "mocha -r ts-node/register test/*-spec.ts"
},
"license": "UNLICENSED",
"private": true,
"author": "Florian Niebling",
"devDependencies": {
"@types/chai": "^4.3.4",
"@types/html-webpack-plugin": "^3.2.6",
"@types/mocha": "^10.0.1",
"@types/three": "^0.149.0",
"chai": "^4.3.7",
"css-loader": "^6.7.3",
"html-webpack-plugin": "^5.5.0",
"mocha": "^10.2.0",
"sass": "^1.59.3",
"sass-loader": "^13.2.0",
"style-loader": "^3.3.2",
"three": "^0.150.1",
"ts-loader": "^9.4.2",
"ts-node": "^10.9.1",
"typescript": "^5.0.2",
"webpack": "^5.76.2",
"webpack-cli": "^5.0.1",
"webpack-dev-server": "^4.12.0"
},
"dependencies": {
"bootstrap": "^5.2.3",
"jquery": "^3.6.4",
"node-glob": "^1.2.0",
"popper.js": "^1.16.1"
}
}

26
src/01/fillscreen.ts

@ -0,0 +1,26 @@
import { swapBuffers } from './setup-fillscreen';
/**
* Determines the color of a pixel (x, y)
* and saves it into the data array.
* The data array holds the linearised pixel data of the target canvas
* row major. Each pixel is of RGBA format.
* @param data The linearised pixel array
* @param x The x coordinate of the pixel
* @param y The y coordinate of the pixel
* @param width The width of the canvas
* @param height The height of the canvas
*/
export function fillscreen(data: Uint8ClampedArray, x: number, y: number, width: number, height: number) {
// TODO: Compute the position of pixel (x, y) in the linearised 'data' array. Each pixel is using 4 bytes in the data array, one each for red, green, blue and alpha.
// TODO: Set the red and alpha component of pixel (x, y) to maximum (255).
var posInArrayX = x * 4;
var posInArrayY = y * 4 * width;
data[0 + posInArrayX + posInArrayY] = 255;
data[1 + posInArrayX + posInArrayY] = 0;
data[2 + posInArrayX + posInArrayY] = 0;
data[3 + posInArrayX + posInArrayY] = 255;
}

41
src/01/setup-fillscreen.ts

@ -0,0 +1,41 @@
import 'bootstrap';
import 'bootstrap/scss/bootstrap.scss';
import { fillscreen } from './fillscreen';
var canvas: HTMLCanvasElement;
var ctx: CanvasRenderingContext2D;
var imageData: ImageData;
export function swapBuffers() {
// swap imageData to canvas
ctx.putImageData(imageData, 0, 0);
}
function drawFillscreen() {
canvas = document.getElementById("result") as HTMLCanvasElement;
if (canvas === null)
return;
ctx = canvas.getContext("2d");
var pixel = ctx.createImageData(1, 1);
imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
for (let y = 0; y < canvas.height; y++) {
for (let x = 0; x < canvas.width; x++) {
fillscreen(data, x, y, canvas.width, canvas.height);
// update pixel in HTML context2d
for (let i = 0; i < 4; i ++)
pixel.data[i] = data[(x + y * canvas.width) * 4 + i];
ctx.putImageData(pixel, x, y);
}
}
}
window.addEventListener('load', evt => {
drawFillscreen();
});

48
src/02/checkerboard.ts

@ -0,0 +1,48 @@
import { swapBuffers } from './setup-checkerboard';
var currentline = -1;
/**
* Determines the color of a pixel (x, y) to create
* a checkerboard pattern and saves it into the data array.
* The data array holds the linearised pixel data of the target canvas
* row major. Each pixel is of RGBA format.
* @param data The linearised pixel array
* @param x The x coordinate of the pixel
* @param y The y coordinate of the pixel
* @param width The width of the canvas
* @param height The height of the canvas
*/
export function checkerboard(data: Uint8ClampedArray, x: number, y: number, width: number, height: number) {
// TODO: Imagine an 8x8 tile checkerboard across the width and height of the canvas. Compute if the pixel at position (x, y) is inside a black or white tile. Set the pixel color accordingly in the pixel array 'data'.
var posInArrayX = x * 4;
var posInArrayY = y * 4 * width;
if( y % (height/4) < height/8 ) {
if((x + y*width) % (width/4) > width/8){
data[0 + posInArrayX + posInArrayY] = 255;
data[1 + posInArrayX + posInArrayY] = 255;
data[2 + posInArrayX + posInArrayY] = 255;
data[3 + posInArrayX + posInArrayY] = 255;
} else {
data[0 + posInArrayX + posInArrayY] = 0;
data[1 + posInArrayX + posInArrayY] = 0;
data[2 + posInArrayX + posInArrayY] = 0;
data[3 + posInArrayX + posInArrayY] = 255;
}
} else {
if((x + y*width) % (width/4) < width/8){
data[0 + posInArrayX + posInArrayY] = 255;
data[1 + posInArrayX + posInArrayY] = 255;
data[2 + posInArrayX + posInArrayY] = 255;
data[3 + posInArrayX + posInArrayY] = 255;
} else {
data[0 + posInArrayX + posInArrayY] = 0;
data[1 + posInArrayX + posInArrayY] = 0;
data[2 + posInArrayX + posInArrayY] = 0;
data[3 + posInArrayX + posInArrayY] = 255;
}
}
}

34
src/02/circle.ts

@ -0,0 +1,34 @@
import { swapBuffers } from './setup-circle';
/**
* Determines the color of a pixel (x, y) to create
* a circle and saves it into the data array.
* The data array holds the linearised pixel data of the target canvas
* row major. Each pixel is of RGBA format.
* @param data The linearised pixel array
* @param x The x coordinate of the pixel
* @param y The y coordinate of the pixel
* @param cx The center x coordinate of the circle
* @param cy The center y coordinate of the circle
* @param width The width of the canvas
* @param height The height of the canvas
* @param radius The radius of the circle
*/
export function circle(data: Uint8ClampedArray, x: number, y: number, cx: number, cy: number, width: number, height: number, radius: number) {
// TODO: Imagine a circle with center (cx, cy) and given radius. Check if pixel (x, y) is inside this circle or not. Set the pixel color accordingly in the pixel array 'data'.
var posInArrayX = x * 4;
var posInArrayY = y * 4 * width;
if( Math.pow((x-(cx)), 2) + Math.pow((y-(cy)), 2) < Math.pow((radius), 2)) {
data[0 + posInArrayX + posInArrayY] = 0;
data[1 + posInArrayX + posInArrayY] = 0;
data[2 + posInArrayX + posInArrayY] = 0;
data[3 + posInArrayX + posInArrayY] = 255;
} else {
data[0 + posInArrayX + posInArrayY] = 255;
data[1 + posInArrayX + posInArrayY] = 255;
data[2 + posInArrayX + posInArrayY] = 255;
data[3 + posInArrayX + posInArrayY] = 255;
}
}

32
src/02/circlesimple.ts

@ -0,0 +1,32 @@
import { swapBuffers } from './setup-circlesimple';
/**
* Determines the color of a pixel (x, y) to create
* a circle and saves it into the data array.
* The data array holds the linearised pixel data of the target canvas
* row major. Each pixel is of RGBA format.
* @param data The linearised pixel array
* @param x The x coordinate of the pixel
* @param y The y coordinate of the pixel
* @param width The width of the canvas
* @param height The height of the canvas
* @param radius The radius of the circle
*/
export function circleSimple(data: Uint8ClampedArray, x: number, y: number, width: number, height: number, radius: number) {
// TODO: Imagine a circle with center in the middle of the framebuffer and given radius. Which pixel is the center? Check if pixel (x, y) is inside the circle or not. Set the pixel color accordingly in the pixel array 'data'.
var posInArrayX = x * 4;
var posInArrayY = y * 4 * width;
if( Math.pow((x-(width/2)), 2) + Math.pow((y-(height/2)), 2) < Math.pow((radius), 2)) {
data[0 + posInArrayX + posInArrayY] = 0;
data[1 + posInArrayX + posInArrayY] = 0;
data[2 + posInArrayX + posInArrayY] = 0;
data[3 + posInArrayX + posInArrayY] = 255;
} else {
data[0 + posInArrayX + posInArrayY] = 255;
data[1 + posInArrayX + posInArrayY] = 255;
data[2 + posInArrayX + posInArrayY] = 255;
data[3 + posInArrayX + posInArrayY] = 255;
}
}

24
src/02/gradient.ts

@ -0,0 +1,24 @@
import { swapBuffers } from './setup-gradient';
/**
* Determines the color of a pixel (x, y)
* and saves it into the data array.
* The data array holds the linearised pixel data of the target canvas
* row major. Each pixel is of RGBA format.
* @param data The linearised pixel array
* @param x The x coordinate of the pixel
* @param y The y coordinate of the pixel
* @param width The width of the canvas
* @param height The height of the canvas
*/
export function gradient(data: Uint8ClampedArray, x: number, y: number, width: number, height: number) {
// TODO: Compute the position of pixel (x, y) in the linearised 'data' array. Each pixel is using 4 bytes in the data array, one each for red, green, blue and alpha.
// TODO: Set the red, green and blue components of pixel (x, y) to draw a gradient from black (0) to white (255). Set the alpha component to maximum (255).
var posInArrayX = x * 4;
var posInArrayY = y * 4 * width;
data[0 + posInArrayX + posInArrayY] = Math.round((x/width)*255);
data[1 + posInArrayX + posInArrayY] = Math.round((x/width)*255);
data[2 + posInArrayX + posInArrayY] = Math.round((x/width)*255);
data[3 + posInArrayX + posInArrayY] = 255;
}

41
src/02/setup-checkerboard.ts

@ -0,0 +1,41 @@
import 'bootstrap';
import 'bootstrap/scss/bootstrap.scss';
import { checkerboard } from './checkerboard';
var canvas: HTMLCanvasElement;
var ctx: CanvasRenderingContext2D;
var imageData: ImageData;
export function swapBuffers() {
// swap imageData to canvas
ctx.putImageData(imageData, 0, 0);
}
function drawCheckerboard() {
canvas = document.getElementById("result") as HTMLCanvasElement;
if (canvas === null)
return;
ctx = canvas.getContext("2d");
var pixel = ctx.createImageData(1, 1);
imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
for (let y = 0; y < canvas.height; y++) {
for (let x = 0; x < canvas.width; x++) {
checkerboard(data, x, y, canvas.width, canvas.height);
// update pixel in HTML context2d
for (let i = 0; i < 4; i ++)
pixel.data[i] = data[(x + y * canvas.width) * 4 + i];
ctx.putImageData(pixel, x, y);
}
}
}
window.addEventListener('load', evt => {
drawCheckerboard();
});

58
src/02/setup-circle.ts

@ -0,0 +1,58 @@
import 'bootstrap';
import 'bootstrap/scss/bootstrap.scss';
import { circle } from './circle';
import { Node } from '../ui/ui';
var canvas: HTMLCanvasElement;
var ctx: CanvasRenderingContext2D;
var imageData: ImageData;
export function swapBuffers() {
// swap imageData to canvas
ctx.putImageData(imageData, 0, 0);
}
function drawCircle(canvas: HTMLCanvasElement, cx: number, cy: number) {
const ctx = canvas.getContext("2d");
var pixel = ctx.createImageData(1, 1);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
for (let y = 0; y < canvas.height; y++) {
for (let x = 0; x < canvas.width; x++) {
circle(data, x, y, cx, cy, canvas.width, canvas.height, canvas.width / 3);
// update pixel in HTML context2d
for (let i = 0; i < 4; i ++)
pixel.data[i] = data[(x + y * canvas.width) * 4 + i];
ctx.putImageData(pixel, x, y);
}
}
ctx.putImageData(imageData, 0, 0);
}
window.addEventListener('load', evt => {
canvas = document.getElementById("result") as HTMLCanvasElement;
if (canvas === null)
return;
ctx = canvas.getContext("2d");
imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
var node1 = new Node({ canvas: canvas,
x: canvas.width / 2, y: canvas.height / 2,
color: "red" });
drawCircle(canvas, canvas.width / 2, canvas.height / 2);
canvas.addEventListener('moved', (e) => {
drawCircle(canvas, (<CustomEvent>e).detail.x, (<CustomEvent>e).detail.y);
});
});

47
src/02/setup-circlesimple.ts

@ -0,0 +1,47 @@
import 'bootstrap';
import 'bootstrap/scss/bootstrap.scss';
import { circleSimple } from './circlesimple';
var canvas: HTMLCanvasElement;
var ctx: CanvasRenderingContext2D;
var imageData: ImageData;
export function swapBuffers() {
// swap imageData to canvas
ctx.putImageData(imageData, 0, 0);
}
function drawCircle(canvas: HTMLCanvasElement) {
const ctx = canvas.getContext("2d");
var pixel = ctx.createImageData(1, 1);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
for (let y = 0; y < canvas.height; y++) {
for (let x = 0; x < canvas.width; x++) {
circleSimple(data, x, y, canvas.width, canvas.height, canvas.width / 3);
// update pixel in HTML context2d
for (let i = 0; i < 4; i ++)
pixel.data[i] = data[(x + y * canvas.width) * 4 + i];
ctx.putImageData(pixel, x, y);
}
}
ctx.putImageData(imageData, 0, 0);
}
window.addEventListener('load', evt => {
canvas = document.getElementById("result") as HTMLCanvasElement;
if (canvas === null)
return;
ctx = canvas.getContext("2d");
imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
drawCircle(canvas);
});

41
src/02/setup-gradient.ts

@ -0,0 +1,41 @@
import 'bootstrap';
import 'bootstrap/scss/bootstrap.scss';
import { gradient } from './gradient';
var canvas: HTMLCanvasElement;
var ctx: CanvasRenderingContext2D;
var imageData: ImageData;
export function swapBuffers() {
// swap imageData to canvas
ctx.putImageData(imageData, 0, 0);
}
function drawGradient() {
canvas = document.getElementById("result") as HTMLCanvasElement;
if (canvas === null)
return;
ctx = canvas.getContext("2d");
var pixel = ctx.createImageData(1, 1);
imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
for (let y = 0; y < canvas.height; y++) {
for (let x = 0; x < canvas.width; x++) {
gradient(data, x, y, canvas.width, canvas.height);
// update pixel in HTML context2d
for (let i = 0; i < 4; i ++)
pixel.data[i] = data[(x + y * canvas.width) * 4 + i];
ctx.putImageData(pixel, x, y);
}
}
}
window.addEventListener('load', evt => {
drawGradient();
});

2
src/index.ts

@ -0,0 +1,2 @@
import 'bootstrap';
import 'bootstrap/scss/bootstrap.scss';

58
src/ui/ui.ts

@ -0,0 +1,58 @@
export class Node {
canvas: HTMLElement;
x: number;
y: number;
r: number = 10;
color: string = "#000000";
public constructor(init?:Partial<Node>) {
Object.assign(this, init);
this.div = document.createElement("div");
this.div.style.position = "absolute";
this.div.style.left = (this.canvas.offsetLeft + this.x) + "px";
this.div.style.top = (this.canvas.offsetTop + this.y) + "px";
this.div.style.width = "10px";
this.div.style.height = "10px";
this.div.style.background = this.color;
this.div.style.color = "blue";
this.div.style.borderRadius = "5px";
this.div.style.zIndex = "1000";
this.canvas.parentElement.appendChild(this.div);
this.div.addEventListener('mousedown', (e) => {
this.isDown = true;
this.offset = [ this.div.offsetLeft - e.clientX,
this.div.offsetTop - e.clientY ];
}, true);
document.addEventListener('mouseup', (e) => {
if (this.isDown) {
var bounds = this.canvas.getBoundingClientRect();
this.isDown = false;
this.canvas.dispatchEvent(new CustomEvent('moved', {
detail: { x: e.clientX - bounds.left,
y: e.clientY - bounds.top }}));
}
}, true);
document.addEventListener('mousemove', (e) => {
e.preventDefault();
if (this.isDown) {
this.div.style.left = (this.offset[0] + e.clientX) + 'px';
this.div.style.top = (this.offset[1] + e.clientY) + 'px';
}
}, true);
}
div: HTMLDivElement;
offset: number[] = [0, 0];
isDown: boolean = false;
}

19
tsconfig.json

@ -0,0 +1,19 @@
{
"compilerOptions": {
"outDir": "./dist/",
"sourceMap": true,
"noImplicitAny": true,
"target": "es2015",
"moduleResolution": "node",
"module": "CommonJS",
"allowJs": true,
"esModuleInterop": true,
"types": [
"mocha"
]
},
"exclude": [
"dist",
"src/todo"
]
}

83
webpack.config.js

@ -0,0 +1,83 @@
const path = require('path');
const fs = require('fs');
var glob = require("glob")
const HtmlWebpackPlugin = require('html-webpack-plugin')
var exercises =
JSON.parse(fs.readFileSync('exercises.json', { encoding: 'utf8' }))
for (let exercise of exercises) {
for (let entry of exercise["entries"]) {
if (fs.existsSync(path.resolve(__dirname, `dist/${entry["file"]}`))) {
entry['exists'] = true;
exercise['exists'] = true;
} else {
console.log(entry["file"] + " does not exist");
}
}
}
//console.log(JSON.stringify(exercises, null, 2));
let entries = {
index: './src/index.ts'
}
let re = /src\/(\d\d)\/setup-(\w+).ts/;
let files = glob.sync("src/??/setup-*.ts");
for (let filename of files) {
let match = re.exec(filename);
let num = match[1];
let name = match[2];
if (fs.existsSync(path.resolve(__dirname, `src/${num}/setup-${name}.ts`))) {
entries[name] = `./src/${num}/setup-${name}.ts`;
console.log("+++++++ " + name + " => " + `./src/${num}/setup-${name}.ts`);
} else {
console.log("------- " + name + " does not exist.");
}
}
module.exports = {
mode: 'development',
devtool: 'inline-source-map',
entry: entries,
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
},
plugins: [
new HtmlWebpackPlugin({
template: 'index.tpl.html',
exercises: exercises
})
],
devServer: {
static: path.join(__dirname, 'dist'),
hot: false
},
module: {
rules: [
{
test: /\.css$/i,
use: ['style-loader', 'css-loader']
},
{
test: /\.s[ac]ss$/i,
use: ['style-loader', 'css-loader', 'sass-loader']
},
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
}
]
},
resolve: {
extensions: ['.tsx', '.ts', '.js'],
},
};
Loading…
Cancel
Save