From e394a8697385d3c30f04ab5e0ce92e2b10cf3179 Mon Sep 17 00:00:00 2001 From: fdai7303 Date: Thu, 27 Apr 2023 09:42:57 +0200 Subject: [PATCH] Exercise 02 finished --- .gitignore | 11 +++ .vscode/extensions.json | 8 ++ .vscode/launch.json | 57 +++++++++++++ .vscode/tasks.json | 65 ++++++++++++++ dist/ai-logo.png | Bin 0 -> 3433 bytes dist/checkerboard-finished.png | Bin 0 -> 3321 bytes dist/checkerboard.html | 39 +++++++++ dist/circle-finished.png | Bin 0 -> 1401 bytes dist/circle.html | 40 +++++++++ dist/circlesimple.html | 40 +++++++++ dist/fillscreen-finished.png | Bin 0 -> 869 bytes dist/fillscreen.html | 39 +++++++++ dist/gradient-finished.png | Bin 0 -> 3218 bytes dist/gradient.html | 39 +++++++++ exercises.json | 152 +++++++++++++++++++++++++++++++++ index.tpl.html | 51 +++++++++++ package.json | 39 +++++++++ src/01/fillscreen.ts | 26 ++++++ src/01/setup-fillscreen.ts | 41 +++++++++ src/02/checkerboard.ts | 48 +++++++++++ src/02/circle.ts | 34 ++++++++ src/02/circlesimple.ts | 32 +++++++ src/02/gradient.ts | 24 ++++++ src/02/setup-checkerboard.ts | 41 +++++++++ src/02/setup-circle.ts | 58 +++++++++++++ src/02/setup-circlesimple.ts | 47 ++++++++++ src/02/setup-gradient.ts | 41 +++++++++ src/index.ts | 2 + src/ui/ui.ts | 58 +++++++++++++ tsconfig.json | 19 +++++ webpack.config.js | 83 ++++++++++++++++++ 31 files changed, 1134 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/extensions.json create mode 100644 .vscode/launch.json create mode 100644 .vscode/tasks.json create mode 100644 dist/ai-logo.png create mode 100644 dist/checkerboard-finished.png create mode 100644 dist/checkerboard.html create mode 100644 dist/circle-finished.png create mode 100644 dist/circle.html create mode 100644 dist/circlesimple.html create mode 100644 dist/fillscreen-finished.png create mode 100644 dist/fillscreen.html create mode 100644 dist/gradient-finished.png create mode 100644 dist/gradient.html create mode 100644 exercises.json create mode 100644 index.tpl.html create mode 100644 package.json create mode 100644 src/01/fillscreen.ts create mode 100644 src/01/setup-fillscreen.ts create mode 100644 src/02/checkerboard.ts create mode 100644 src/02/circle.ts create mode 100644 src/02/circlesimple.ts create mode 100644 src/02/gradient.ts create mode 100644 src/02/setup-checkerboard.ts create mode 100644 src/02/setup-circle.ts create mode 100644 src/02/setup-circlesimple.ts create mode 100644 src/02/setup-gradient.ts create mode 100644 src/index.ts create mode 100644 src/ui/ui.ts create mode 100644 tsconfig.json create mode 100644 webpack.config.js 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 0000000000000000000000000000000000000000..eb82d24874e8c4a8eadea2adb0a28d190bfbe8c0 GIT binary patch literal 3433 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 LNkvXXu0mjfS8=+Y literal 0 HcmV?d00001 diff --git a/dist/checkerboard-finished.png b/dist/checkerboard-finished.png new file mode 100644 index 0000000000000000000000000000000000000000..d5f96dd989f1c5f25a76256a2730851c5a02a553 GIT binary patch literal 3321 zcmeAS@N?(olHy`uVBq!ia0y~yVAKI&4mO}jWo=(60|U??nvE>`%+LL*v74Vh$MBZQ>NT5$4(J>TIwz4k>1w35T$mqpr*ZOPI_-pi6$1jTA^sCt9lnCGD#MG%% z^G8`U41>ZS%-3p^awmm`MRF#LCig*}|NEjY^3PagpM9Er-}I~c7xg0lT1usIwZGi3 z>C`FnQ5Fruz~T>Mw%tUtlUl3Su#8q5gS{qoU;BdH=i>aWPnqwZellGXEM(=X=afnHn0#G}=xY> + + + + + + 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 0000000000000000000000000000000000000000..2dddc9a109964a42fd7503b4e118765aa6fd3146 GIT binary patch literal 1401 zcmeAS@N?(olHy`uVBq!ia0y~yVAKI&4mO}jWo=(60|Tprr;B4q#hkZS9Q&j#1za8P z{=YPDDsRujyp>m$R(Xc8eE1eq^XxGrPy?CZ&+YZU7!UmY!^Kpw%EGTt;zLMngN)$= z@2`c02O9FPvLE@#v@cY?tCR8Y75Bu}hJadzFuolge{Ys^{h08p_`OWQq+iDOH6QqX zeZIH&K=-TjTJ7BPR@pC?(W~(MtN1kIVDMMk2xnUffduv7+k1E&U%IRX1*#Tg2LCy_KG`N^Z+He!oEe z$nTtWOXN1Z6W_B`ZrwX+nIQhKrs-FcrvmK^SbG29_E(Kw_3ZJhj%)sv{S$2K^K$ir z`d5x#=bP+b83M`Uk1z5ER+;9llUq_mv?u}tSY$V|Jd!- zMo;_R*(=)B=8BpJ^!r$8n}zfHSXrBe^?L)!P=4=UAvr7LrhZv@!^c+R<>ieNt9&jk zPgedqV~PK9wO5Ls;YZnC2?EJSuNXbUj~{yV(E~;x}qHk~Gt3Z$7xkZUDRIPsOE z$K~W1RRWY4g5i?3Rqbf*M)J)`2E8@&oVY$NWPS9sgT+|~avvFgAs|AMZn6SwRwLTxjEiGQ8k)^EIa zi{!R_dfyV%yO6<_j-e?e)J;H&-xKf(FyPQsF3$r*75 zaeqCpuzRnx?~&2ASn|)JXw8EOznD*MY;u0puX3D=Z?*kY87-hh0#IVZugxbHf+W + + + + + + 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 0000000000000000000000000000000000000000..f9538472604e75e67ff3b8a34eb1ed4932fddad5 GIT binary patch literal 869 zcmeAS@N?(olHy`uVBq!ia0y~yVAKI&4mO}jWo=(6kYY>nc6VX;4}uH!E}sk(;Vkfo zEM{Qf76xHPhFNnYfP(BLp1!W^PuYb97_?7$#~ol`VEW|g;uunK>+KasK?Vkn!y6jw z&o5xsn|Q%JdlKgXMqS2)2!=LR39*JmMhP)6J0Swh1`=(oL`o8^5U&-ZreF{H;S(+m miJlDLq(+&S@TSiL>D;UHSi<%$XuJf>5DcEKelF{r5}E+ruGaqm literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..3554d0b0530db179aa627b10420479fac2df49fe GIT binary patch literal 3218 zcmeHK=~t3j7-#xm%eb^Tjau4qa?7PM!^py^97|0rGtFEZ4N_B2IHq7?rKV=NksvBi z$AEAv!3`;#R#Q|aHFGOhKtwQ6QxqLb zeQIv==|s-yPX+h4d$?H`8vAGKP@SAQJFV>vi|aR^H!oY`wyk!fvoAdU{mwd@OE1Fp z50Bm6zSqw>bCaRx_?ygxu{-N&rwh$(F4teXZ)l63c%Ms8o}YkCKTbz|f4yE*a8keh zNtS8pRtwLkZaG_R0-2GX#e0i;s9y9lL`T@{i?_aw*9RZj2SKyUu*rFcC$e{Tmy z3)R2oG9!d@bEc1hYe|#{>wA58VmgHkBB&q&Hu>NnN=QV~u&c_be;yz2J|d}hx<0U9 z&ji{zh@_Abx?xr#a!Opc%iK$+=sxR*#)mo-A`0bahvwZ6O`N+kcZ8PYI<#1OmpXai zEWJ_l?|0CndTH#bA}^GfN)R%64qhxoS5I{y%PIcbQ9>9N4e%_qt!*~94{AKa!`mka ziUWj|RkfZ~{xztj&jIvANk905_fUNlE~b`k`6V1m7|u8$pt zN{1S}R6nE%lMkJRw6ZwQEd5bbCG={C4D1;M7BwzsY#f{=|aNxlanWM zjw+^bMo0d-Mg}zMnn3N|-qYjto^>T5$!JSHTy_1Pff;$rZ|;%rvbPw)P?4ZM_R%%< z0(v}ihkPP;AENKsd80$oe(w3EqyrHjB4-EcjGW_wLos6N12b3nWLrKzW8*f*8x!wd zs^vVQQ9?*N8lMYb2& z1v2+gu*xXBLrCdW9$vuW!+9b~8d<<2AQf_f0yHfb5W<9@_Kq)MX{30r!qV2i`Xg|X zT-r1$Ofo=R$964_mm~`yJp%AD*nA@b8aGk`w%e9LV>>`E3m_9% z)wJX7gP5RNwQ8<)uq81DXcoVwQSBRUis$F;a$!ca<;{+;4{~lwJAnFFNIrS?E5|uy zS=Pl(RWX5Twr|G2$gr@xveE0(o2}YcEe=`{Xw9HK3A87ob{zcAV{wgtWz|55(~0-q T#~uK84T#4dK5n%q!teeI84rpK literal 0 HcmV?d00001 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'], + }, +};