commit e394a8697385d3c30f04ab5e0ce92e2b10cf3179 Author: fdai7303 Date: Thu Apr 27 09:42:57 2023 +0200 Exercise 02 finished diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..949e558 --- /dev/null +++ b/.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 \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..b9e33d0 --- /dev/null +++ b/.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" + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..65d9918 --- /dev/null +++ b/.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": [ + "/**" + ], + "type": "pwa-node", + "env": { + "TS_NODE_COMPILER_OPTIONS": "{\"module\":\"commonjs\"}" + } + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..110ec37 --- /dev/null +++ b/.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, + } + ] +} \ No newline at end of file diff --git a/dist/ai-logo.png b/dist/ai-logo.png new file mode 100644 index 0000000..eb82d24 Binary files /dev/null and b/dist/ai-logo.png differ diff --git a/dist/checkerboard-finished.png b/dist/checkerboard-finished.png new file mode 100644 index 0000000..d5f96dd Binary files /dev/null and b/dist/checkerboard-finished.png differ diff --git a/dist/checkerboard.html b/dist/checkerboard.html new file mode 100644 index 0000000..bfcb072 --- /dev/null +++ b/dist/checkerboard.html @@ -0,0 +1,39 @@ + + + + + + + Graphische Datenverarbeitung - 2D - Checkerboard + + + + + + +
+
+
+
+ +
+ Determine for each pixel if it is black or white to create a checkerboard pattern +
+
+
+ +
Reference image
+
+
+
+ + + diff --git a/dist/circle-finished.png b/dist/circle-finished.png new file mode 100644 index 0000000..2dddc9a Binary files /dev/null and b/dist/circle-finished.png differ diff --git a/dist/circle.html b/dist/circle.html new file mode 100644 index 0000000..4894fc0 --- /dev/null +++ b/dist/circle.html @@ -0,0 +1,40 @@ + + + + + + + Graphische Datenverarbeitung - 2D - Circle + + + + + + +
+
+
+
+ + +
+ Determine for each pixel if it is black or white to create a circle +
+
+
+ +
Reference image
+
+
+
+ + + diff --git a/dist/circlesimple.html b/dist/circlesimple.html new file mode 100644 index 0000000..863957d --- /dev/null +++ b/dist/circlesimple.html @@ -0,0 +1,40 @@ + + + + + + + Graphische Datenverarbeitung - 2D - Circle + + + + + + +
+
+
+
+ + +
+ Determine for each pixel if it is black or white to create a circle +
+
+
+ +
Reference image
+
+
+
+ + + diff --git a/dist/fillscreen-finished.png b/dist/fillscreen-finished.png new file mode 100644 index 0000000..f953847 Binary files /dev/null and b/dist/fillscreen-finished.png differ diff --git a/dist/fillscreen.html b/dist/fillscreen.html new file mode 100644 index 0000000..84971e6 --- /dev/null +++ b/dist/fillscreen.html @@ -0,0 +1,39 @@ + + + + + + + Graphische Datenverarbeitung - 2D - Fillscreen + + + + + + +
+
+
+
+ +
+ Fill the screen with a single RGBA color #FF0000FF (red). +
+
+
+ +
Reference image
+
+
+
+ + + diff --git a/dist/gradient-finished.png b/dist/gradient-finished.png new file mode 100644 index 0000000..3554d0b Binary files /dev/null and b/dist/gradient-finished.png differ diff --git a/dist/gradient.html b/dist/gradient.html new file mode 100644 index 0000000..715414c --- /dev/null +++ b/dist/gradient.html @@ -0,0 +1,39 @@ + + + + + + + Graphische Datenverarbeitung - 2D - gradient + + + + + + +
+
+
+
+ +
+ Fill the screen with a gradient from #000000FF (black) to #FFFFFFFF (white). +
+
+
+ +
Reference image
+
+
+
+ + + diff --git a/exercises.json b/exercises.json new file mode 100644 index 0000000..33cdd09 --- /dev/null +++ b/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" + } + ] + } +] diff --git a/index.tpl.html b/index.tpl.html new file mode 100644 index 0000000..d3e1b2c --- /dev/null +++ b/index.tpl.html @@ -0,0 +1,51 @@ + + + + + + GDV Übungen + + + + + + +
+ +
+ <% htmlWebpackPlugin.options.exercises.forEach((exercise) => { %> +
+
+
+ <% if (exercise['exists'] === true) { %> +
<%= exercise['title'] %>
+ <% } else { %> +
<%= exercise['title'] %>
+ <% } %> +
+ <% exercise['entries'].forEach((entry) => { %> + <% if (entry['exists'] === true) { %> + + <% } else { %> + + class="list-group-item list-group-item-action flex-column align-items-start"> +
+
<%= entry['title'] %>
+
+

<%= entry['description'] %>

+
+ <% }); %> +
+ <% }); %> +
+ + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..d6a47d7 --- /dev/null +++ b/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" + } +} diff --git a/src/01/fillscreen.ts b/src/01/fillscreen.ts new file mode 100644 index 0000000..9dfde86 --- /dev/null +++ b/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; + +} diff --git a/src/01/setup-fillscreen.ts b/src/01/setup-fillscreen.ts new file mode 100644 index 0000000..24437ec --- /dev/null +++ b/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(); +}); diff --git a/src/02/checkerboard.ts b/src/02/checkerboard.ts new file mode 100644 index 0000000..a7a7827 --- /dev/null +++ b/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; + } + } + +} diff --git a/src/02/circle.ts b/src/02/circle.ts new file mode 100644 index 0000000..b30ab54 --- /dev/null +++ b/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; + } +} diff --git a/src/02/circlesimple.ts b/src/02/circlesimple.ts new file mode 100644 index 0000000..6add13c --- /dev/null +++ b/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; + } +} diff --git a/src/02/gradient.ts b/src/02/gradient.ts new file mode 100644 index 0000000..fbad598 --- /dev/null +++ b/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; +} diff --git a/src/02/setup-checkerboard.ts b/src/02/setup-checkerboard.ts new file mode 100644 index 0000000..fb003c0 --- /dev/null +++ b/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(); +}); diff --git a/src/02/setup-circle.ts b/src/02/setup-circle.ts new file mode 100644 index 0000000..2275aca --- /dev/null +++ b/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, (e).detail.x, (e).detail.y); + }); +}); diff --git a/src/02/setup-circlesimple.ts b/src/02/setup-circlesimple.ts new file mode 100644 index 0000000..8735f6c --- /dev/null +++ b/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); +}); diff --git a/src/02/setup-gradient.ts b/src/02/setup-gradient.ts new file mode 100644 index 0000000..4732695 --- /dev/null +++ b/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(); +}); diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..24c679c --- /dev/null +++ b/src/index.ts @@ -0,0 +1,2 @@ +import 'bootstrap'; +import 'bootstrap/scss/bootstrap.scss'; \ No newline at end of file diff --git a/src/ui/ui.ts b/src/ui/ui.ts new file mode 100644 index 0000000..2fedf82 --- /dev/null +++ b/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) { + 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; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..f9641e9 --- /dev/null +++ b/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" + ] +} diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..6b56dac --- /dev/null +++ b/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'], + }, +};