diff --git a/.gitignore b/.gitignore index 7220fb2e..696a9565 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ /public/ -/resources/_gen/ # Backup Files *.md~ # Logs diff --git a/content/_index.md b/content/_index.md index e80fe0e6..345165a9 100644 --- a/content/_index.md +++ b/content/_index.md @@ -3,13 +3,20 @@ title: Introduction type: docs --- -# Showcase Template +# Visual Computing 2023-1 +This is a workbook for the Visual Computing class in the National University of Colombia. -Welcome to the [gohugo](https://gohugo.io/) template to create rich content [academic reports](https://www.wordy.com/writers-workshop/writing-an-academic-report/) having [p5.js](https://p5js.org/) sketches. +# Team +* Andrés Ortega - +* David Mateus - dmateusb@unal.edu.co +* Andryut Huertas - aahuertasca@unal.edu.co ## Hacking -Create a github [user account](https://docs.github.com/en/get-started/signing-up-for-github/signing-up-for-a-new-github-account) or [organization](https://docs.github.com/en/organizations/collaborating-with-groups-in-organizations/creating-a-new-organization-from-scratch), and install [git](https://git-scm.com/) and the [gohugo](https://gohugo.io/) [static site generator](https://jamstack.org/generators/) then: +Welcome to the [gohugo](https://gohugo.io/) template to create rich content [academic reports](https://www.wordy.com/writers-workshop/writing-an-academic-report/) having [p5.js](https://p5js.org/) sketches. + +Install the [gohugo](https://gohugo.io/) [static site generator](https://jamstack.org/generators/) then: + 1. Login into GitHUb and create a new repo [from this template](https://docs.github.com/en/repositories/creating-and-managing-repositories/creating-a-repository-from-a-template#creating-a-repository-from-a-template) into your user account or organization. **Don't rename the repo but leave it as 'showcase'**. 2. Grant [read and write permissions](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/enabling-features-for-your-repository/managing-github-actions-settings-for-a-repository#configuring-the-default-github_token-permissions) to your newly created repo workflow. **Observation:** If you're deploying an organization site this permission need to be granted within the organization settings. diff --git a/content/docs/exercises/Coloring.md b/content/docs/exercises/Coloring.md new file mode 100644 index 00000000..51751ec9 --- /dev/null +++ b/content/docs/exercises/Coloring.md @@ -0,0 +1,89 @@ +# Coloring for colorblindness +>**Prompt:** Implement a color mapping application that helps people who are color blind see the colors around them. +> +In order to assess this problem, first we need to understand colorblindness and later, pick one of the many forms this may manifest. + +## What is colorblindness? +Color blindness is classified according to the number of _color channels_ that are available to a person to conveying color information into: [monochromacy](https://en.wikipedia.org/wiki/Monochromacy), [dichromacy](https://en.wikipedia.org/wiki/Dichromacy) and anomalous trichromacy. Refer to the [Ishihara test](https://en.wikipedia.org/wiki/Ishihara_test) for a color vision dificiency test. + +For this particular P5.js implementation, I choose ***deuteranopia*** as my colorblindess type of interest. **Deuteranopia** is a kind of green color blindness that nullifies the person's ability to recognize the color green, thus their color spectrum goes only from blue hues to yellow ones, as shown bellow. + + +![undefined](https://upload.wikimedia.org/wikipedia/commons/5/5a/Rainbow_Deuteranopia.svg) + +Now, diving into the P5.js implementation, I used a fairly simple mapping function as the backbone of the color mapping process. This mapping function recieves an image as input, to process the image in the three color channels first we need to load them into a array. This is achieved by the next function: + + + newImg.loadPixels(); + +Now that the pixels are loaded to an array it is possible to separate them by the color channels given the RGB order. After having the individual pixels in their respective channels we can finally map them into ***deuteranopia-friendly*** colors thanks to the next function: + + // deuteranopia-friendly color transformation function + let rNew = 0.625 * r + 0.375 * g + 0.001 * b; + let gNew = 0.7 * r + 0.3 * g + 0 * b; + let bNew = 0 * r + 0.3 * g + 0.7 * b; + +The specific values used in the color mapping function are derived from studies [1] on the spectral sensitivity of the deuteranopia visual system. By applying these values to the original RGB color, the resulting color is transformed to a deuteranopia-friendly color that is easier for individuals with deuteranopia to differentiate from other colors. + +It's important to note that the color mapping function I provided assumes a moderate degree of deuteranopia and may not be appropriate for all cases of deuteranopia. Additionally, color perception can vary from person to person, so the resulting transformed color may not be perfectly accurate in simulating protanopia for all individuals. + +After this transformation takes place the image is rebuilt with the new pixels and it´s displayed besides the original one. + +Finally here's the complete P5.js implementation: + +[1] Stockman, A., Sharpe, L. and Vries, R. (1999) _Color Vision: From Genes to Perception_. Cambridge University Press. + +{{< p5-iframe sketch="/showcase/sketches/exercises/coloring/1.js" width="500" height="540" >}} + +## [Color modes](https://ww2.lacan.upc.edu/doc/intel/ipp/ipp_manual/IPPI/ippi_ch6/ch6_color_models.htm) +>**Prompt:** Research other color models such as HSL, HSB, XYZ. +> + +### HSB, and HSL Color Models + +The HSL (hue, lightness, saturation) and HSB (hue, saturation, brightness) color models were developed to be more “intuitive” in manipulating with color and were designed to approximate the way humans perceive and interpret color. + +Hue defines the color itself. The values for the hue axis vary from 0 to 360 beginning and ending with red and running through green, blue and all intermediary colors. + +Saturation indicates the degree to which the hue differs from a neutral gray. The values run from 0, which means no color saturation, to 1, which is the fullest saturation of a given hue at a given illumination. + +Intensity component - lightness (HSL) or brightness (HSB), indicates the illumination level. Both vary from 0 (black, no light) to 1 (white, full illumination). The difference between the two is that maximum saturation of hue (S=1) is at brightness B=1 (full illumination) in the HSB color model, and at lightness L=0.5 in the HSL color model. + +The HSB color space is essentially a cylinder, but usually it is represented as a cone or hexagonal cone (hexcone) as shown in the Figure "HSB Solid", because the hexcone defines the subset of the HSB space with valid RGB values. The brightness B is the vertical axis, and the vertex B=0 corresponds to black color. Similarly, a color solid, or 3D-representation, of the HSL model is a double hexcone (Figure "HSB Solid") with lightness as the axis, and the vertex of the second hexcone corresponding to white. + +Both color models have intensity component decoupled from the color information. The HSB color space yields a greater dynamic range of saturation. + +#### HSB Solid +{{< p5-iframe sketch="/showcase/sketches/exercises/coloring/HSV.js" width="500" height="540" >}} + +#### HSL Solid +{{< p5-iframe sketch="/showcase/sketches/exercises/coloring/HSL.js" width="500" height="700" >}} + +### XYZ Color Model +The XYZ color space is an international standard developed by the CIE (Commission Internationale de l'Eclairage). This model is based on three hypothetical primaries, XYZ, and all visible colors can be represented by using only positive values of X, Y, and Z. The CIE XYZ primaries are hypothetical because they do not correspond to any real light wavelengths. The Y primary is intentionally defined to match closely to luminance, while X and Z primaries give color information. The main advantage of the CIE XYZ space (and any color space based on it) is that this space is completely device-independent. The chromaticity diagram in Figure ["CIE xyY Chromaticity Diagram and Color Gamut"](https://ww2.lacan.upc.edu/doc/intel/ipp/ipp_manual/IPPI/ippi_ch6/ch6_cie_diagram.htm#fig6-1) is in fact a two-dimensional projection of the CIE XYZ sub-space. Note that arbitrarily combining X, Y, and Z values within nominal ranges can easily lead to a "color" outside of the visible color spectrum. + +The position of the block of RGB-representable colors in the XYZ space is shown in Figure "RGB Colors Cube in the XYZ Color Space". +#### RGB Colors Cube in the XYZ Color Space +{{< p5-iframe sketch="/showcase/sketches/exercises/coloring/XYZ.js" width="500" height="400" >}} + +Use the following basic equations [Rogers85], to convert between gamma-corrected R'G'B' and CIE XYZ models: + +``` +X = 0.412453*R' + 0.35758 *G' + 0.180423*B' +Y = 0.212671*R' + 0.71516 *G' + 0.072169*B' +Z = 0.019334*R' + 0.119193*G' + 0.950227*B' +``` + +The equations for X,Y,Z calculation are given on the assumption that R',G', and B' values are normalized to the range [0..1]. + +``` +R' = 3.240479 * X - 1.53715 * Y - 0.498535 * Z +G' = -0.969256 * X + 1.875991 * Y + 0.041556 * Z +B' = 0.055648 * X - 0.204043 * Y + 1.057311 * Z +``` + +The equations for R',G', and B' calculation are given on the assumption that X,Y, and Z values are in the range [0..1]. + +[1] Stockman, A., Sharpe, L. and Vries, R. (1999) _Color Vision: From Genes to Perception_. Cambridge University Press. + +[Rog85] David Rogers. Procedural Elements for Computer Graphics. McGraw-Hill, 1985. \ No newline at end of file diff --git a/content/docs/exercises/Depth Perception.md b/content/docs/exercises/Depth Perception.md new file mode 100644 index 00000000..98bf6d70 --- /dev/null +++ b/content/docs/exercises/Depth Perception.md @@ -0,0 +1,11 @@ +## Parallax effect +>**Prompt:** Take advantage of monocular cues to implement a 2D sketch to trick the eye into perceiving a 3D scene. +> + +Here we have the parallax effect, tricking our eye into perceiving depth between objects in a 2-D plane. + +Moon is following you at a slow speed and when the ball moves, the perspective of the buildings change. + +On this sketch you can change the speed of the movement using left and right arrow keys. + +{{< p5-iframe sketch="/showcase/sketches/exercises/depth_perception/parallax.js" width="430" height="430" >}} \ No newline at end of file diff --git a/content/docs/exercises/Mach Bands.md b/content/docs/exercises/Mach Bands.md new file mode 100644 index 00000000..6b610571 --- /dev/null +++ b/content/docs/exercises/Mach Bands.md @@ -0,0 +1,33 @@ +## Mach Bands +>**Prompt:** Develop a terrain visualization application. Check out the 3D terrain generation with Perlin noise coding train tutorial. +> +### [Voronoi diagram](https://en.wikipedia.org/wiki/Voronoi_diagram#:~:text=In%20mathematics%2C%20a%20Voronoi%20diagram,%2C%20sites%2C%20or%20generators).) +In mathematics, a Voronoi diagram is a partition of a plane into regions close to each of a given set of objects. + +### Implementation + +In the following p5 sketch we mixed Perlin terrain generation, mach bands and voronoi diagrams. + +First we create a set of random points in a plane, then we compute the voronoi diagram for this set of points using the [gorhill](https://github.com/gorhill/Javascript-Voronoi) javascript library. + +- You can see the resulting Voronoi diagram using the toggle control "voronoi diagram". + +Then we add perlin noise over the plane, this noise is going to be mixed with the inverse of the distance between each point and its respective polygon in the voronoi diagram. + +- You can see the terrain generated with the inverse of the distance to the center of each polygon using the toggle control "perlin" + +- When the "perlin" checkbox is marked, then the sketch shows the resulting perlin noise over the voronoi diagrams. + +Additionally, to improve visualization, we have two more toggle controls. +- "stroke" shows the stroke in the triangles that generates the surface. +- "band" make a coloring based on Mach bands for each triangle. + +The first slider changes the density or scale of the triangles, and also amplifies the terrain keeping the Voronoi diagram information. + +The second slider changes the speed of rotation. + +{{< p5-iframe sketch="/showcase/sketches/exercises/mach_bands/terrain.js" width="1000" height="1000" >}} + +This sketch is based on Daniel Shiffman terrain generation with perlin noise and Mach bands highlighting by Jean Pierre Charalambos. + +This work has [applications](https://www.redblobgames.com/x/2022-voronoi-maps-tutorial/) on the videogame terrrain generation area. diff --git a/content/docs/exercises/Masking.md b/content/docs/exercises/Masking.md new file mode 100644 index 00000000..4754e522 --- /dev/null +++ b/content/docs/exercises/Masking.md @@ -0,0 +1,87 @@ +# Coloring for colorblindness +>**Prompt:** Implement a color mapping application that helps people who are color blind see the colors around them. +> +In order to assess this problem, first we need to understand colorblindness and later, pick one of the many forms this may manifest. + +## What is colorblindness? +Color blindness is classified according to the number of _color channels_ that are available to a person to conveying color information into: [monochromacy](https://en.wikipedia.org/wiki/Monochromacy), [dichromacy](https://en.wikipedia.org/wiki/Dichromacy) and anomalous trichromacy. Refer to the [Ishihara test](https://en.wikipedia.org/wiki/Ishihara_test) for a color vision dificiency test. + +For this particular P5.js implementation, I choose ***deuteranopia*** as my colorblindess type of interest. **Deuteranopia** is a kind of green color blindness that nullifies the person's ability to recognize the color green, thus their color spectrum goes only from blue hues to yellow ones, as shown bellow. + +![undefined](https://upload.wikimedia.org/wikipedia/commons/5/5a/Rainbow_Deuteranopia.svg) + +Now, diving into the P5.js implementation, I used a fairly simple mapping function as the backbone of the color mapping process. This mapping function recieves an image as input, to process the image in the three color channels first we need to load them into a array. This is achieved by the next function: + + + newImg.loadPixels(); + +Now that the pixels are loaded to an array it is possible to separate them by the color channels given the RGB order. After having the individual pixels in their respective channels we can finally map them into ***deuteranopia-friendly*** colors thanks to the next function: + + // deuteranopia-friendly color transformation function + let rNew = 0.625 * r + 0.375 * g + 0.001 * b; + let gNew = 0.7 * r + 0.3 * g + 0 * b; + let bNew = 0 * r + 0.3 * g + 0.7 * b; + +The specific values used in the color mapping function are derived from studies [1] on the spectral sensitivity of the deuteranopia visual system. By applying these values to the original RGB color, the resulting color is transformed to a deuteranopia-friendly color that is easier for individuals with deuteranopia to differentiate from other colors. + +It's important to note that the color mapping function I provided assumes a moderate degree of deuteranopia and may not be appropriate for all cases of deuteranopia. Additionally, color perception can vary from person to person, so the resulting transformed color may not be perfectly accurate in simulating protanopia for all individuals. + +After this transformation takes place the image is rebuilt with the new pixels and it´s displayed besides the original one. + +Finally here's the complete P5.js implementation: + +[1] Stockman, A., Sharpe, L. and Vries, R. (1999) _Color Vision: From Genes to Perception_. Cambridge University Press. + +{{< p5-iframe sketch="/showcase/sketches/exercises/coloring/1.js" width="450" height="450" >}} + +## [Color modes](https://ww2.lacan.upc.edu/doc/intel/ipp/ipp_manual/IPPI/ippi_ch6/ch6_color_models.htm) +>**Prompt:** Research other color models such as HSL, HSB, XYZ. +> +### HSB, and HSL Color Models + +The HSL (hue, lightness, saturation) and HSB (hue, saturation, brightness) color models were developed to be more “intuitive” in manipulating with color and were designed to approximate the way humans perceive and interpret color. + +Hue defines the color itself. The values for the hue axis vary from 0 to 360 beginning and ending with red and running through green, blue and all intermediary colors. + +Saturation indicates the degree to which the hue differs from a neutral gray. The values run from 0, which means no color saturation, to 1, which is the fullest saturation of a given hue at a given illumination. + +Intensity component - lightness (HSL) or brightness (HSB), indicates the illumination level. Both vary from 0 (black, no light) to 1 (white, full illumination). The difference between the two is that maximum saturation of hue (S=1) is at brightness B=1 (full illumination) in the HSB color model, and at lightness L=0.5 in the HSL color model. + +The HSB color space is essentially a cylinder, but usually it is represented as a cone or hexagonal cone (hexcone) as shown in the Figure "HSB Solid", because the hexcone defines the subset of the HSB space with valid RGB values. The brightness B is the vertical axis, and the vertex B=0 corresponds to black color. Similarly, a color solid, or 3D-representation, of the HSL model is a double hexcone (Figure "HSB Solid") with lightness as the axis, and the vertex of the second hexcone corresponding to white. + +Both color models have intensity component decoupled from the color information. The HSB color space yields a greater dynamic range of saturation. + +#### HSB Solid +{{< p5-iframe sketch="/showcase/sketches/exercises/coloring/HSV.js" width="500" height="540" >}} + +#### HSL Solid +{{< p5-iframe sketch="/showcase/sketches/exercises/coloring/HSL.js" width="500" height="700" >}} + +### XYZ Color Model +The XYZ color space is an international standard developed by the CIE (Commission Internationale de l'Eclairage). This model is based on three hypothetical primaries, XYZ, and all visible colors can be represented by using only positive values of X, Y, and Z. The CIE XYZ primaries are hypothetical because they do not correspond to any real light wavelengths. The Y primary is intentionally defined to match closely to luminance, while X and Z primaries give color information. The main advantage of the CIE XYZ space (and any color space based on it) is that this space is completely device-independent. The chromaticity diagram in Figure ["CIE xyY Chromaticity Diagram and Color Gamut"](https://ww2.lacan.upc.edu/doc/intel/ipp/ipp_manual/IPPI/ippi_ch6/ch6_cie_diagram.htm#fig6-1) is in fact a two-dimensional projection of the CIE XYZ sub-space. Note that arbitrarily combining X, Y, and Z values within nominal ranges can easily lead to a "color" outside of the visible color spectrum. + +The position of the block of RGB-representable colors in the XYZ space is shown in Figure "RGB Colors Cube in the XYZ Color Space". +#### RGB Colors Cube in the XYZ Color Space +{{< p5-iframe sketch="/showcase/sketches/exercises/coloring/XYZ.js" width="500" height="400" >}} + +Use the following basic equations [Rogers85], to convert between gamma-corrected R'G'B' and CIE XYZ models: + +``` +X = 0.412453*R' + 0.35758 *G' + 0.180423*B' +Y = 0.212671*R' + 0.71516 *G' + 0.072169*B' +Z = 0.019334*R' + 0.119193*G' + 0.950227*B' +``` + +The equations for X,Y,Z calculation are given on the assumption that R',G', and B' values are normalized to the range [0..1]. + +``` +R' = 3.240479 * X - 1.53715 * Y - 0.498535 * Z +G' = -0.969256 * X + 1.875991 * Y + 0.041556 * Z +B' = 0.055648 * X - 0.204043 * Y + 1.057311 * Z +``` + +The equations for R',G', and B' calculation are given on the assumption that X,Y, and Z values are in the range [0..1]. + +[1] Stockman, A., Sharpe, L. and Vries, R. (1999) _Color Vision: From Genes to Perception_. Cambridge University Press. + +[Rog85] David Rogers. Procedural Elements for Computer Graphics. McGraw-Hill, 1985. \ No newline at end of file diff --git a/content/docs/exercises/Spatial Coherence.md b/content/docs/exercises/Spatial Coherence.md new file mode 100644 index 00000000..291654b8 --- /dev/null +++ b/content/docs/exercises/Spatial Coherence.md @@ -0,0 +1,177 @@ +# Spatial Coherence +>**Prompt:** Implement a pixelator video application and perform a benchmark of the results (color avg vs spatial coherence). How would you assess the visual quality of the results? +> + +The color averaging filter works smoothing the image loosing the pictures details, otherwise, the spacial coherence technique achieves to keep +details and shapes edges, this cant be done by color averaging smooth effect. +{{< details title="Code implementation" open=false >}} {{< highlight js >}} +let img; +let ratioInput; +let ratioValue; +let newImg, newImg2; +let updateBtn; + +function preload() { + img = loadImage("https://picsum.photos/400"); +} + +function setup() { + + createCanvas(img.width * 2, img.height * 2); + text = createP("Original"); + text.position(10, img.height/2 - 30); + text2 = createP("Color averaging"); + text2.position(img.width + 8, 0); + text3 = createP("Spatial coherence"); + text3.position(img.width + 8, img.height + 22); + ratioInput = createInput(10, "number"); + ratioInput.position(55, (img.height/2) + img.height + 40); + ratioInput.style('width', `${img.width / 2 - 70}px`); + + ratioValue = createDiv("Ratio: "); + ratioValue.position(10, (img.height/2) + img.height + 42); + + updateBtn = createButton('Update'); + updateBtn.position(10, (img.height/2) + img.height + 65); + updateBtn.style('background-color', '#76b5c5'); + updateBtn.style('border', 'none'); + updateBtn.style('color', 'white'); + updateBtn.style('padding', '8px 14px'); + updateBtn.style('text-align', 'center'); + updateBtn.style('text-decoration', 'none'); + updateBtn.style('display', 'inline-block'); + updateBtn.style('font-size', '16px'); + updateBtn.style('margin', '4px 2px'); + updateBtn.style('cursor', 'pointer'); + updateBtn.mousePressed(updateImages); + pixelDensity(calcPixelDensity()); + newImg = createImage(img.width, img.height); + newImg2 = createImage(img.width, img.height); + + updateImages(); + +} + +function draw() { + image(img, -10, img.height/2); + image(newImg, img.width, 30); + image(newImg2, img.width, img.height + 50); +} + +function updateImages() { + updateImage1(); + updateImage2(); + background("#eeeee4"); +} + +function updateImage1() { + let ratio = ratioInput.value(); + let avgColors = getAverageColors(img); + setPixels(newImg, avgColors, ratio); + image(newImg, img.width, 30); +} + +function updateImage2() { + let ratio = parseInt(ratioInput.value()); + newImg2.loadPixels(); + let new_pixels = []; + for (let y = 0; y < newImg2.height; y++) { + for (let x = 0; x < newImg2.width; x++) { + let randX = -1; + let randY = -1; + while (randX < 0 || randX >= img.width || randY < 0 || randY >= img.height){ + randX = x + int(random(parseInt(-ratio/2), parseInt(ratio/2))); + randY = y + int(random(parseInt(-ratio/2), parseInt(ratio/2))); + } + let index = (x + y * newImg2.width) * 4; + let new_index = (randX + randY * newImg2.width) * 4; + + newImg2.pixels[index] = img.pixels[new_index]; + newImg2.pixels[index + 1] = img.pixels[new_index+1]; + newImg2.pixels[index + 2] = img.pixels[new_index+2]; + newImg2.pixels[index + 3] = 255; + } + } + + newImg2.updatePixels(); + image(newImg2, img.width, img.height + 50); +} + +function getAverageColors(img) +{ + let avgColors = []; + img.loadPixels(); + + for (let y = 0; y < img.height; y++) { + for (let x = 0; x < img.width; x++) { + let index = (x + y * img.width) * 4; + let r = img.pixels[index]; + let g = img.pixels[index + 1]; + let b = img.pixels[index + 2]; + avgColors.push(color(r , g , b)); + } + } + img.updatePixels(); + + return avgColors; +} + +function setPixels(img, avgColors, ratio) { + img.loadPixels(); + + for (let y = 0; y < img.height; y++) { + for (let x = 0; x < img.width; x++) { + let index = (x + y * img.width) * 4; + let r = 0; + let g = 0; + let b = 0; + let count = 0; + + for (let i = 0; i <= ratio; i++) { + for (let j = 0; j <= ratio; j++) { + let neighborX = x + i; + let neighborY = y + j; + + if (neighborX >= 0 && neighborX < img.width && neighborY >= 0 && neighborY < img.height) { + let neighborIndex = (neighborX + neighborY * img.width) * 4; + r += red(avgColors[neighborIndex / 4]); + g += green(avgColors[neighborIndex / 4]); + b += blue(avgColors[neighborIndex / 4]); + count++; + } + } + } + + r /= count; + g /= count; + b /= count; + + img.pixels[index] = r; + img.pixels[index + 1] = g; + img.pixels[index + 2] = b; + img.pixels[index + 3] = 255; + } + } + + img.updatePixels(); +} + +function calcPixelDensity() { + let density = displayDensity(); + let w = img.width * density; + let h = img.height * density; + let ratio = w / h; + let pixelDensity = 1; + if (w > displayWidth || h > displayHeight) { + if (ratio > 1) { + pixelDensity = ceil(w / displayWidth); + + + } else { + pixelDensity = ceil(h / displayHeight); + } + } + return pixelDensity; +} +{{< /highlight >}} {{< /details >}} +{{< p5-iframe sketch="/showcase/sketches/exercises/spacial_coherence/2.js" width="735" height="830" >}} \ No newline at end of file diff --git a/content/docs/exercises/Temporal Coherence.md b/content/docs/exercises/Temporal Coherence.md new file mode 100644 index 00000000..737f91c5 --- /dev/null +++ b/content/docs/exercises/Temporal Coherence.md @@ -0,0 +1,30 @@ +# Temporal Coherence +>**Prompt:** Implement an animation with keyframes using the nub library for Processing (Java). +> +Even though **temporal coherence** is a physics term refering to the similarity of waves through time, it can be used as a analogy of animation and it´s coherent flow in the visual computing world. + +Now, to really drive this point home it´s necessary to talk about a very important thing regarding animation and it´s implementation in visual computing: we´re talking about **keyframes**. + +## Keyframes +As many of us may know, the term **keyframe** derives from the analog animation times in which the starting and finishing frames of an animation were called like this. This means that the keyframes were thw most important frames of an animation and the frames inbetween those were done with the **keyframes as templates.** +![undefined](https://what-when-how.com/wp-content/uploads/2012/07/tmpcd0074_thumb.png) + +In computer graphics the process is pretty much the same. Keyframes are determined instances of the animtion that detail a broad idea of what the animation should behave, then the inbetween frames are interpolated automatically usign the keyframes as templates. + +For this **Processing.JS** implementation of an animation that uses keyframes we´re prompted to use a library called **Nub**. This library adds a lot of functionality to the animation aspect of processing and really expands the posibilities for computer graphics animation. + +## Nub Library for Processing.JS +In the words of it´s authors, the library is: +>**"** nub is a simple, expressive, language-agnostic, and extensible visual computing library, featuring interaction, visualization and animation frameworks and supporting advanced (onscreen/offscreen) rendering techniques, such as view frustum culling **"** + +This library add some new components that help in the animation process, one of the most important ones is the ***node***. This nodes act as objects wich can be transformed in either their position, magnitude or orientation and, only when the ***node*** has a shape asigned to it, it is rendered on the scene. + +Another important part of the animation process is the aforementioned ***keyframes***. Keyframes in this library are the instances in time that determines a node´s position, orientation and magnitude in any given moment, thus being the building frame of the whole animation. For the inbetween frames, those are interpolated using the Catmull–Rom spline and the use of this interpolation technique results in really smooth movement of the object being animated. + +There are also some other usefull functions in the library that I actually used in the following code: + + - **node.enableHint(Node.KEYFRAMES):** This displays the axels that come out fo the shape´s keyframes. + - **node.setAnimationRecurrence(true):** This loops the aniamtion once it lands in the final keyframe. + - **node.addKeyFrame(Node.AXES | Node.SHAPE, i % 2 == 1 ? 1000 : 4000):** This is the function that defines the keyframes, in the first part it´s declared that the keyframe will have the axes enabled and that it´ll take the shape declared before. In the second part it´s declared how far in between the keyframes are generated. + +{{< p5-iframe sketch="/showcase/sketches/exercises/dithering/1.js" width="760" height="780" >}} diff --git a/content/docs/exercises/Visual Illusions.md b/content/docs/exercises/Visual Illusions.md new file mode 100644 index 00000000..d66598fb --- /dev/null +++ b/content/docs/exercises/Visual Illusions.md @@ -0,0 +1,77 @@ +# Visual illusions +>**Prompt:** Study, implement and discuss possible applications of some known visual phenomena and optical illusions. +> + +The Hermann grid is used to create the optical illusion of perceiving a ghost between the grid cells, it's +produced a neural process called "Lateral inhibition". In the image there are only white spaces and black blocks +but the eyes perceive some kind of contrasts creating "gosthlike" shapes. + +{{< details title="Herman Grid" open=false >}} {{< highlight js >}} + +function setup() { + createCanvas(750, 750); +} + +function draw() { + background(0); + + stroke('white'); + strokeWeight(7); + + for (var i = 0; i < 25; i++){ + line(width/25*i, 0, width/25*i, height) + } + + for (var i = 0; i < 25; i++){ + line(0, height/25*i, width, height/25*i) + } + +} + +{{< /highlight >}} {{< /details >}} + +{{< p5-iframe sketch="/showcase/sketches/exercises/1/herman_grill.js" width="730" height="780" >}} + +In the following image there are parallel lines in each row, but exists an illusion of a vertically displacement created by the +irregular columns sort. The cafe wall illusion has been utilized by neuropsychologists to investigate how the brain processes visual information. Additionally, the illusion has found practical applications in graphic design and art. + +{{< details title="Cafe Wall" open=false >}} {{< highlight js >}} + +var colors = []; + +function setup() { + createCanvas(725, 425); +} + +function draw() { + background(255); + x_jump = 55; + y_jump = 55; + stroke('grey'); + strokeWeight(3.3); + + for (var j = 0 ; j < 30; j++) { + for (var i = 0; i < 30; i++) { + if (j % 2 == 0) { + fill(0); + } else { + fill(255); + };if (i % 2 == 0) { + fill(0); + } else { + fill(255); + }; + if (j % 3 == 0){ + rect(i * x_jump - 10, j * y_jump, 80, 90); + } else if (j % 2 == 0){ + rect(i * x_jump - 30, j * y_jump, 100, 60); + } else { + rect(i * x_jump - 50, j * y_jump, 90, 60); + } + } + } +} + +{{< /highlight >}} {{< /details >}} + +{{< p5-iframe sketch="/showcase/sketches/exercises/1/cafe_wall.js" width="730" height="460" >}} \ No newline at end of file diff --git a/content/docs/shortcodes/_index.md b/content/docs/shortcodes/_index.md index 9bb0430c..e4b4a840 100644 --- a/content/docs/shortcodes/_index.md +++ b/content/docs/shortcodes/_index.md @@ -1,3 +1,8 @@ --- -bookFlatSection: true +bookCollapseSection: true +weight: 20 --- + +# Shortcodes + +{{
}} diff --git a/content/docs/team/_index.md b/content/docs/team/_index.md new file mode 100644 index 00000000..d288e171 --- /dev/null +++ b/content/docs/team/_index.md @@ -0,0 +1,10 @@ +--- +bookFlatSection: true +weight: 8 +--- + +# Team +This workbook was made by: +* Andrés Ortega - +* David Mateus - dmateusb@unal.edu.co +* Andryut Huertas - aahuertasca@unal.edu.co \ No newline at end of file diff --git a/content/sketches/exercises/1/cafe_wall.js b/content/sketches/exercises/1/cafe_wall.js new file mode 100644 index 00000000..693aa857 --- /dev/null +++ b/content/sketches/exercises/1/cafe_wall.js @@ -0,0 +1,34 @@ +var colors = []; + +function setup() { + createCanvas(725, 425); +} + +function draw() { + background(255); + x_jump = 55; + y_jump = 55; + stroke('grey'); + strokeWeight(3.3); + + for (var j = 0 ; j < 30; j++) { + for (var i = 0; i < 30; i++) { + if (j % 2 == 0) { + fill(0); + } else { + fill(255); + };if (i % 2 == 0) { + fill(0); + } else { + fill(255); + }; + if (j % 3 == 0){ + rect(i * x_jump - 10, j * y_jump, 80, 90); + } else if (j % 2 == 0){ + rect(i * x_jump - 30, j * y_jump, 100, 60); + } else { + rect(i * x_jump - 50, j * y_jump, 90, 60); + } + } + } +} \ No newline at end of file diff --git a/content/sketches/exercises/1/curved_grid.js b/content/sketches/exercises/1/curved_grid.js new file mode 100644 index 00000000..97e2b48c --- /dev/null +++ b/content/sketches/exercises/1/curved_grid.js @@ -0,0 +1,62 @@ +const CANVAS_SIZE = 800; +const BOX_AMOUNT_BY_ROW = 16; +const GRID_CELL = BOX_AMOUNT_BY_ROW / 4; + +const BOX_SIZE = CANVAS_SIZE / BOX_AMOUNT_BY_ROW; +const INSIDE_BOX_SIZE = BOX_SIZE / 4; +const INSIDE_BOX_PADDING = BOX_SIZE * 0.05; +const CIRCLE_SIZE = (BOX_AMOUNT_BY_ROW - 2) * BOX_SIZE; + +function setup() { + createCanvas(725, 425); + background(220); + noStroke(); + + let rotate = true; + + for (let k = 0; k < GRID_CELL; k++) { + for (let z = 0; z < GRID_CELL; z++) { + for (let i = 0; i < GRID_CELL; i++) { + for (let j = 0; j < GRID_CELL; j++) { + let bg = (i % 2) - (j % 2) == 0 ? 0 : 255; + let foreground = bg == 255 ? 0 : 255; + let x = z * GRID_CELL * BOX_SIZE + i * BOX_SIZE; + let y = k * GRID_CELL * BOX_SIZE + j * BOX_SIZE; + fill(bg); + rect(x, y, BOX_SIZE, BOX_SIZE); + + fill(foreground); + if (rotate) { + rect( + x + INSIDE_BOX_PADDING, + y + INSIDE_BOX_PADDING, + INSIDE_BOX_SIZE, + INSIDE_BOX_SIZE + ); + rect( + x + INSIDE_BOX_SIZE * 3 - INSIDE_BOX_PADDING, + y + INSIDE_BOX_SIZE * 3 - INSIDE_BOX_PADDING, + INSIDE_BOX_SIZE, + INSIDE_BOX_SIZE + ); + } else { + rect( + x + INSIDE_BOX_SIZE * 3 - INSIDE_BOX_PADDING, + y + INSIDE_BOX_PADDING, + INSIDE_BOX_SIZE, + INSIDE_BOX_SIZE + ); + rect( + x + INSIDE_BOX_PADDING, + y + INSIDE_BOX_SIZE * 3 - INSIDE_BOX_PADDING, + INSIDE_BOX_SIZE, + INSIDE_BOX_SIZE + ); + } + } + } + rotate = !rotate; + } + rotate = !rotate; + } +} \ No newline at end of file diff --git a/content/sketches/exercises/1/herman_grill.js b/content/sketches/exercises/1/herman_grill.js new file mode 100644 index 00000000..208f26b4 --- /dev/null +++ b/content/sketches/exercises/1/herman_grill.js @@ -0,0 +1,19 @@ +function setup() { + createCanvas(750, 750); +} + +function draw() { + background(0); + + stroke('white'); + strokeWeight(7); + + for (var i = 0; i < 25; i++){ + line(width/25*i, 0, width/25*i, height) + } + + for (var i = 0; i < 25; i++){ + line(0, height/25*i, width, height/25*i) + } + +} \ No newline at end of file diff --git a/content/sketches/exercises/coloring/0e5c038d-098c-4a45-87fd-b89630a313e9.jpg b/content/sketches/exercises/coloring/0e5c038d-098c-4a45-87fd-b89630a313e9.jpg new file mode 100644 index 00000000..feb8a7f9 Binary files /dev/null and b/content/sketches/exercises/coloring/0e5c038d-098c-4a45-87fd-b89630a313e9.jpg differ diff --git a/content/sketches/exercises/coloring/1.js b/content/sketches/exercises/coloring/1.js new file mode 100644 index 00000000..71aa4480 --- /dev/null +++ b/content/sketches/exercises/coloring/1.js @@ -0,0 +1,36 @@ +let img; + +function preload() { + + img = loadImage('showcase\content\sketches\exercises\coloring\0e5c038d-098c-4a45-87fd-b89630a313e9.jpg'); +} + +function setup() { + createCanvas(1200, 600); + image(img, 0, 0, width/2, height); + let newImg = deuteranopia(img); + image(newImg, width/2, 0, width/2, height); +} + +function deuteranopia(img) { + let newImg = createImage(img.width, img.height); + img.loadPixels(); + newImg.loadPixels(); + for (let i = 0; i < img.pixels.length; i += 4) { + let r = img.pixels[i]; + let g = img.pixels[i + 1]; + let b = img.pixels[i + 2]; + + + let rNew = 0.625 * r + 0.375 * g + 0.001 * b; + let gNew = 0.7 * r + 0.3 * g + 0 * b; + let bNew = 0 * r + 0.3 * g + 0.7 * b; + + newImg.pixels[i] = rNew; + newImg.pixels[i + 1] = gNew; + newImg.pixels[i + 2] = bNew; + newImg.pixels[i + 3] = img.pixels[i + 3]; + } + newImg.updatePixels(); + return newImg; +} \ No newline at end of file diff --git a/content/sketches/exercises/coloring/HSL-model.png b/content/sketches/exercises/coloring/HSL-model.png new file mode 100644 index 00000000..01d1eccf Binary files /dev/null and b/content/sketches/exercises/coloring/HSL-model.png differ diff --git a/content/sketches/exercises/coloring/HSL.js b/content/sketches/exercises/coloring/HSL.js new file mode 100644 index 00000000..f0a778c4 --- /dev/null +++ b/content/sketches/exercises/coloring/HSL.js @@ -0,0 +1,12 @@ +let img; +function preload() { + img = loadImage('/showcase/sketches/exercises/coloring/HSL-model.png'); +} + +function setup() { + createCanvas(windowWidth, windowHeight); + // Top-left corner of the img is at (10, 10) + // Width and height are 50×50 + img.resize(500, 0); + image(img, -20, 0); + } diff --git a/content/sketches/exercises/coloring/HSV-model.png b/content/sketches/exercises/coloring/HSV-model.png new file mode 100644 index 00000000..20c602c4 Binary files /dev/null and b/content/sketches/exercises/coloring/HSV-model.png differ diff --git a/content/sketches/exercises/coloring/HSV.js b/content/sketches/exercises/coloring/HSV.js new file mode 100644 index 00000000..ec3e1e19 --- /dev/null +++ b/content/sketches/exercises/coloring/HSV.js @@ -0,0 +1,12 @@ +let img; +function preload() { + img = loadImage('/showcase/sketches/exercises/coloring/HSV-model.png'); +} + +function setup() { + createCanvas(windowWidth, windowHeight); + // Top-left corner of the img is at (10, 10) + // Width and height are 50×50 + img.resize(500, 0); + image(img, -20, 0); + } diff --git a/content/sketches/exercises/coloring/XYZ-model.png b/content/sketches/exercises/coloring/XYZ-model.png new file mode 100644 index 00000000..33afee22 Binary files /dev/null and b/content/sketches/exercises/coloring/XYZ-model.png differ diff --git a/content/sketches/exercises/coloring/XYZ.js b/content/sketches/exercises/coloring/XYZ.js new file mode 100644 index 00000000..3a5ae6a9 --- /dev/null +++ b/content/sketches/exercises/coloring/XYZ.js @@ -0,0 +1,12 @@ +let img; +function preload() { + img = loadImage('/showcase/sketches/exercises/coloring/XYZ-model.png'); +} + +function setup() { + createCanvas(windowWidth, windowHeight); + // Top-left corner of the img is at (10, 10) + // Width and height are 50×50 + img.resize(500, 0); + image(img, -20, 0); + } diff --git a/content/sketches/exercises/depth_perception/parallax.js b/content/sketches/exercises/depth_perception/parallax.js new file mode 100644 index 00000000..81be377a --- /dev/null +++ b/content/sketches/exercises/depth_perception/parallax.js @@ -0,0 +1,112 @@ +var p1 = 0.03; +var p2 = -0.09; +var p3 = 1.5; +var m = 0; +var d = 1; +var s = 1; + +function setup() { + createCanvas(400, 400); +} + +function draw() { + background(150); + + stroke(127, 63, 120); + + + fill("black"); + rect(0,0, 400, 250); + fill(color(200)); + ellipse(20 + m*p1, 20, 200); + fill(color(180)); + noStroke(); + ellipse(30 + m*p1, 30, 30); + ellipse(40 + m*p1, 40, 10); + ellipse(40 + m*p1, 45, 5); + ellipse(40 + m*p1, 30, 10); + ellipse(45 + m*p1, 35, 5); + ellipse(20 + m*p1, 30, 10); + ellipse(15 + m*p1, 40, 5); + ellipse(20 + m*p1, 15, 10); + ellipse(25 + m*p1, 15, 5); + + ellipse(50 + m*p1, 30, 10); + ellipse(60 + m*p1, 45, 5); + ellipse(110 + m*p1, 50, 10); + ellipse(100 + m*p1, 50, 5); + ellipse(90 + m*p1, 40, 10); + ellipse(100 + m*p1, 55, 5); + + fill(color(160)); + + ellipse(30 + m*p1, 30, 20); + ellipse(40 + m*p1, 40, 5); + ellipse(40 + m*p1, 45, 3); + ellipse(40 + m*p1, 30, 8); + ellipse(45 + m*p1, 35, 2); + ellipse(20 + m*p1, 30, 7); + ellipse(15 + m*p1, 40, 4); + ellipse(20 + m*p1, 15, 6); + ellipse(25 + m*p1, 15, 2); + + ellipse(50 + m*p1, 30, 8); + ellipse(60 + m*p1, 45, 2); + ellipse(110 + m*p1, 50, 5); + ellipse(100 + m*p1, 50, 3); + ellipse(90 + m*p1, 40, 6); + ellipse(100 + m*p1, 55, 2); + + + stroke(127, 63, 120); + fill("white"); + rect(-50 + m*p2, 70, 200, 230); + rect(250 + m*p2, 30, 200, 270); + fill("black"); + rect(10 + m*p2, 90, 40, 40); + rect(80 + m*p2, 90, 40, 40); + rect(10 + m*p2, 150, 40, 40); + fill("yellow"); + rect(80 + m*p2, 150, 40, 40); + rect(10 + m*p2, 210, 40, 40); + fill("black"); + rect(80 + m*p2, 210, 40, 40); + + rect(280 + m*p2, 50, 40, 40); + fill("yellow"); + rect(350 + m*p2, 50, 40, 40); + rect(280 + m*p2, 110, 40, 40); + fill("black"); + rect(350 + m*p2, 110, 40, 40); + fill("yellow"); + rect(280 + m*p2, 170, 40, 40); + rect(350 + m*p2, 170, 40, 40); + fill("black"); + rect(280 + m*p2, 230, 40, 40); + fill("yellow"); + rect(350 + m*p2, 230, 40, 40); + fill("blue"); + ellipse(200 + m*p3, 350, 40); + + if(250 + m*p3 > 400 || 150 + m*p3 < 0) { + d *= -1; + } + + m += (d*s); +} + +function keyPressed() { + if (keyCode === LEFT_ARROW) { + if (s > 0.1) { + s -= 0.1; + } else { + s = 0.1; + } + } else if (keyCode === RIGHT_ARROW) { + if (s < 10) { + s += 0.1; + } else { + s = 10; + } + } +} \ No newline at end of file diff --git a/content/sketches/exercises/dithering/1.js b/content/sketches/exercises/dithering/1.js new file mode 100644 index 00000000..4b5d2b20 --- /dev/null +++ b/content/sketches/exercises/dithering/1.js @@ -0,0 +1,100 @@ +let img; +var factor=1; //If it is equal to 1 and there is a gray filter there will be only two colors. +var wd; +var d; + + +function preload() { + img = loadImage('cat.png'); +} + +function setup() { + let c=createCanvas(img.width,img.height); + background(255, 255, 255); + pixelDensity(1); + image(img,0,0); + filter(GRAY); + wd=img.width; + loadPixels(); + d = pixelDensity(); + for (let y = 0; y < height-1; y++) { + for (let x = 1; x < width-1; x++){ + + var index = getIndex(x,y); + + var r= pixels[index]; + var g= pixels[index + 1]; + var b= pixels[index + 2]; + var a= pixels[index + 3]; + + var newR=find_closest_palette_color(r); + var newG=find_closest_palette_color(g); + var newB=find_closest_palette_color(b); + var newA= find_closest_palette_color(a); + + pixels[index] = newR; + pixels[index + 1] = newG; + pixels[index + 2] = newB; + pixels[index + 3] = newA; + + + //Calculating quantization error of a pixel. + var quant_error_R=r-newR; + var quant_error_G=g-newG; + var quant_error_B=b-newB; + var quant_error_A=a-newA; + + // + index = getIndex(x+1,y); + r= pixels[index]+(quant_error_R*7/16.0); + g= pixels[index + 1]+(quant_error_G*7/16.0); + b= pixels[index + 2]+(quant_error_B*7/16.0); + a= pixels[index + 3]+(quant_error_A*7/16.0); + pixels[index] = r; + pixels[index + 1] = g; + pixels[index + 2] = b; + pixels[index + 3] = a; + + index = getIndex(x-1,y+1); + r= pixels[index]+(quant_error_R*3/16.0); + g= pixels[index + 1]+(quant_error_G*3/16.0); + b= pixels[index + 2]+(quant_error_B*3/16.0); + a= pixels[index + 3]+(quant_error_A*3/16.0); + pixels[index] = r; + pixels[index + 1] = g; + pixels[index + 2] = b; + pixels[index + 3] = a; + + index = getIndex(x,y+1); + r= pixels[index]+(quant_error_R*5/16.0); + g= pixels[index + 1]+(quant_error_G*5/16.0); + b= pixels[index + 2]+(quant_error_B*5/16.0); + a= pixels[index + 3]+(quant_error_A*5/16.0); + pixels[index] = r; + pixels[index + 1] = g; + pixels[index + 2] = b; + pixels[index + 3] = a; + + index = getIndex(x+1,y+1); + r= pixels[index]+(quant_error_R*1/16.0); + g= pixels[index + 1]+(quant_error_G*1/16.0); + b= pixels[index + 2]+(quant_error_B*1/16.0); + a= pixels[index + 3]+(quant_error_A*1/16.0); + pixels[index] = r; + pixels[index + 1] = g; + pixels[index + 2] = b; + pixels[index + 3] = a; + } + } + updatePixels(); +} + +//Operación de cuantización. +function find_closest_palette_color(old){ + return round(factor*old/255)*(255/factor) +} + +//Retorna la posición (x,y) de un píxel. +function getIndex(x,y){ + return (x+(y*wd))*4; +} diff --git a/content/sketches/exercises/mach_bands/terrain.js b/content/sketches/exercises/mach_bands/terrain.js new file mode 100644 index 00000000..3710f54a --- /dev/null +++ b/content/sketches/exercises/mach_bands/terrain.js @@ -0,0 +1,324 @@ +function Voronoi(){this.vertices=null;this.edges=null;this.cells=null;this.toRecycle=null;this.beachsectionJunkyard=[];this.circleEventJunkyard=[]; + this.vertexJunkyard=[];this.edgeJunkyard=[];this.cellJunkyard=[]}Voronoi.prototype.reset=function(){if(!this.beachline){this.beachline=new this.RBTree() + }if(this.beachline.root){var a=this.beachline.getFirst(this.beachline.root);while(a){this.beachsectionJunkyard.push(a);a=a.rbNext + }}this.beachline.root=null;if(!this.circleEvents){this.circleEvents=new this.RBTree()}this.circleEvents.root=this.firstCircleEvent=null; + this.vertices=[];this.edges=[];this.cells=[]};Voronoi.prototype.sqrt=Math.sqrt;Voronoi.prototype.abs=Math.abs;Voronoi.prototype.ε=Voronoi.ε=1e-9; + Voronoi.prototype.invε=Voronoi.invε=1/Voronoi.ε;Voronoi.prototype.equalWithEpsilon=function(d,c){return this.abs(d-c)<1e-9 + };Voronoi.prototype.greaterThanWithEpsilon=function(d,c){return d-c>1e-9};Voronoi.prototype.greaterThanOrEqualWithEpsilon=function(d,c){return c-d<1e-9 + };Voronoi.prototype.lessThanWithEpsilon=function(d,c){return c-d>1e-9};Voronoi.prototype.lessThanOrEqualWithEpsilon=function(d,c){return d-c<1e-9 + };Voronoi.prototype.RBTree=function(){this.root=null};Voronoi.prototype.RBTree.prototype.rbInsertSuccessor=function(e,a){var d; + if(e){a.rbPrevious=e;a.rbNext=e.rbNext;if(e.rbNext){e.rbNext.rbPrevious=a}e.rbNext=a;if(e.rbRight){e=e.rbRight;while(e.rbLeft){e=e.rbLeft + }e.rbLeft=a}else{e.rbRight=a}d=e}else{if(this.root){e=this.getFirst(this.root);a.rbPrevious=null;a.rbNext=e;e.rbPrevious=a; + e.rbLeft=a;d=e}else{a.rbPrevious=a.rbNext=null;this.root=a;d=null}}a.rbLeft=a.rbRight=null;a.rbParent=d;a.rbRed=true;var c,b; + e=a;while(d&&d.rbRed){c=d.rbParent;if(d===c.rbLeft){b=c.rbRight;if(b&&b.rbRed){d.rbRed=b.rbRed=false;c.rbRed=true;e=c}else{if(e===d.rbRight){this.rbRotateLeft(d); + e=d;d=e.rbParent}d.rbRed=false;c.rbRed=true;this.rbRotateRight(c)}}else{b=c.rbLeft;if(b&&b.rbRed){d.rbRed=b.rbRed=false;c.rbRed=true; + e=c}else{if(e===d.rbLeft){this.rbRotateRight(d);e=d;d=e.rbParent}d.rbRed=false;c.rbRed=true;this.rbRotateLeft(c)}}d=e.rbParent + }this.root.rbRed=false};Voronoi.prototype.RBTree.prototype.rbRemoveNode=function(f){if(f.rbNext){f.rbNext.rbPrevious=f.rbPrevious + }if(f.rbPrevious){f.rbPrevious.rbNext=f.rbNext}f.rbNext=f.rbPrevious=null;var e=f.rbParent,g=f.rbLeft,b=f.rbRight,d;if(!g){d=b + }else{if(!b){d=g}else{d=this.getFirst(b)}}if(e){if(e.rbLeft===f){e.rbLeft=d}else{e.rbRight=d}}else{this.root=d}var a;if(g&&b){a=d.rbRed; + d.rbRed=f.rbRed;d.rbLeft=g;g.rbParent=d;if(d!==b){e=d.rbParent;d.rbParent=f.rbParent;f=d.rbRight;e.rbLeft=f;d.rbRight=b;b.rbParent=d + }else{d.rbParent=e;e=d;f=d.rbRight}}else{a=f.rbRed;f=d}if(f){f.rbParent=e}if(a){return}if(f&&f.rbRed){f.rbRed=false;return + }var c;do{if(f===this.root){break}if(f===e.rbLeft){c=e.rbRight;if(c.rbRed){c.rbRed=false;e.rbRed=true;this.rbRotateLeft(e); + c=e.rbRight}if((c.rbLeft&&c.rbLeft.rbRed)||(c.rbRight&&c.rbRight.rbRed)){if(!c.rbRight||!c.rbRight.rbRed){c.rbLeft.rbRed=false; + c.rbRed=true;this.rbRotateRight(c);c=e.rbRight}c.rbRed=e.rbRed;e.rbRed=c.rbRight.rbRed=false;this.rbRotateLeft(e);f=this.root; + break}}else{c=e.rbLeft;if(c.rbRed){c.rbRed=false;e.rbRed=true;this.rbRotateRight(e);c=e.rbLeft}if((c.rbLeft&&c.rbLeft.rbRed)||(c.rbRight&&c.rbRight.rbRed)){if(!c.rbLeft||!c.rbLeft.rbRed){c.rbRight.rbRed=false; + c.rbRed=true;this.rbRotateLeft(c);c=e.rbLeft}c.rbRed=e.rbRed;e.rbRed=c.rbLeft.rbRed=false;this.rbRotateRight(e);f=this.root; + break}}c.rbRed=true;f=e;e=e.rbParent}while(!f.rbRed);if(f){f.rbRed=false}};Voronoi.prototype.RBTree.prototype.rbRotateLeft=function(b){var d=b,c=b.rbRight,a=d.rbParent; + if(a){if(a.rbLeft===d){a.rbLeft=c}else{a.rbRight=c}}else{this.root=c}c.rbParent=a;d.rbParent=c;d.rbRight=c.rbLeft;if(d.rbRight){d.rbRight.rbParent=d + }c.rbLeft=d};Voronoi.prototype.RBTree.prototype.rbRotateRight=function(b){var d=b,c=b.rbLeft,a=d.rbParent;if(a){if(a.rbLeft===d){a.rbLeft=c + }else{a.rbRight=c}}else{this.root=c}c.rbParent=a;d.rbParent=c;d.rbLeft=c.rbRight;if(d.rbLeft){d.rbLeft.rbParent=d}c.rbRight=d + };Voronoi.prototype.RBTree.prototype.getFirst=function(a){while(a.rbLeft){a=a.rbLeft}return a};Voronoi.prototype.RBTree.prototype.getLast=function(a){while(a.rbRight){a=a.rbRight + }return a};Voronoi.prototype.Diagram=function(a){this.site=a};Voronoi.prototype.Cell=function(a){this.site=a;this.halfedges=[]; + this.closeMe=false};Voronoi.prototype.Cell.prototype.init=function(a){this.site=a;this.halfedges=[];this.closeMe=false;return this + };Voronoi.prototype.createCell=function(b){var a=this.cellJunkyard.pop();if(a){return a.init(b)}return new this.Cell(b)}; + Voronoi.prototype.Cell.prototype.prepareHalfedges=function(){var a=this.halfedges,b=a.length,c;while(b--){c=a[b].edge;if(!c.vb||!c.va){a.splice(b,1) + }}a.sort(function(e,d){return d.angle-e.angle});return a.length};Voronoi.prototype.Cell.prototype.getNeighborIds=function(){var a=[],b=this.halfedges.length,c; + while(b--){c=this.halfedges[b].edge;if(c.lSite!==null&&c.lSite.voronoiId!=this.site.voronoiId){a.push(c.lSite.voronoiId)}else{if(c.rSite!==null&&c.rSite.voronoiId!=this.site.voronoiId){a.push(c.rSite.voronoiId) + }}}return a};Voronoi.prototype.Cell.prototype.getBbox=function(){var i=this.halfedges,d=i.length,a=Infinity,g=Infinity,c=-Infinity,b=-Infinity,h,f,e; + while(d--){h=i[d].getStartpoint();f=h.x;e=h.y;if(fc){c=f}if(e>b){b=e}}return{x:a,y:g,width:c-a,height:b-g} + };Voronoi.prototype.Cell.prototype.pointIntersection=function(a,h){var b=this.halfedges,c=b.length,f,g,e,d;while(c--){f=b[c]; + g=f.getStartpoint();e=f.getEndpoint();d=(h-g.y)*(e.x-g.x)-(a-g.x)*(e.y-g.y);if(!d){return 0}if(d>0){return -1}}return 1}; + Voronoi.prototype.Vertex=function(a,b){this.x=a;this.y=b};Voronoi.prototype.Edge=function(b,a){this.lSite=b;this.rSite=a; + this.va=this.vb=null};Voronoi.prototype.Halfedge=function(d,e,a){this.site=e;this.edge=d;if(a){this.angle=Math.atan2(a.y-e.y,a.x-e.x) + }else{var c=d.va,b=d.vb;this.angle=d.lSite===e?Math.atan2(b.x-c.x,c.y-b.y):Math.atan2(c.x-b.x,b.y-c.y)}};Voronoi.prototype.createHalfedge=function(b,c,a){return new this.Halfedge(b,c,a) + };Voronoi.prototype.Halfedge.prototype.getStartpoint=function(){return this.edge.lSite===this.site?this.edge.va:this.edge.vb + };Voronoi.prototype.Halfedge.prototype.getEndpoint=function(){return this.edge.lSite===this.site?this.edge.vb:this.edge.va + };Voronoi.prototype.createVertex=function(a,c){var b=this.vertexJunkyard.pop();if(!b){b=new this.Vertex(a,c)}else{b.x=a;b.y=c + }this.vertices.push(b);return b};Voronoi.prototype.createEdge=function(e,a,d,b){var c=this.edgeJunkyard.pop();if(!c){c=new this.Edge(e,a) + }else{c.lSite=e;c.rSite=a;c.va=c.vb=null}this.edges.push(c);if(d){this.setEdgeStartpoint(c,e,a,d)}if(b){this.setEdgeEndpoint(c,e,a,b) + }this.cells[e.voronoiId].halfedges.push(this.createHalfedge(c,e,a));this.cells[a.voronoiId].halfedges.push(this.createHalfedge(c,a,e)); + return c};Voronoi.prototype.createBorderEdge=function(d,c,a){var b=this.edgeJunkyard.pop();if(!b){b=new this.Edge(d,null) + }else{b.lSite=d;b.rSite=null}b.va=c;b.vb=a;this.edges.push(b);return b};Voronoi.prototype.setEdgeStartpoint=function(b,d,a,c){if(!b.va&&!b.vb){b.va=c; + b.lSite=d;b.rSite=a}else{if(b.lSite===a){b.vb=c}else{b.va=c}}};Voronoi.prototype.setEdgeEndpoint=function(b,d,a,c){this.setEdgeStartpoint(b,a,d,c) + };Voronoi.prototype.Beachsection=function(){};Voronoi.prototype.createBeachsection=function(a){var b=this.beachsectionJunkyard.pop(); + if(!b){b=new this.Beachsection()}b.site=a;return b};Voronoi.prototype.leftBreakPoint=function(e,f){var a=e.site,m=a.x,l=a.y,k=l-f; + if(!k){return m}var n=e.rbPrevious;if(!n){return -Infinity}a=n.site;var h=a.x,g=a.y,d=g-f;if(!d){return h}var c=h-m,j=1/k-1/d,i=c/d; + if(j){return(-i+this.sqrt(i*i-2*j*(c*c/(-2*d)-g+d/2+l-k/2)))/j+m}return(m+h)/2};Voronoi.prototype.rightBreakPoint=function(b,c){var d=b.rbNext; + if(d){return this.leftBreakPoint(d,c)}var a=b.site;return a.y===c?a.x:Infinity};Voronoi.prototype.detachBeachsection=function(a){this.detachCircleEvent(a); + this.beachline.rbRemoveNode(a);this.beachsectionJunkyard.push(a)};Voronoi.prototype.removeBeachsection=function(b){var a=b.circleEvent,j=a.x,h=a.ycenter,e=this.createVertex(j,h),f=b.rbPrevious,d=b.rbNext,l=[b],g=Math.abs; + this.detachBeachsection(b);var m=f;while(m.circleEvent&&g(j-m.circleEvent.x)<1e-9&&g(h-m.circleEvent.ycenter)<1e-9){f=m.rbPrevious; + l.unshift(m);this.detachBeachsection(m);m=f}l.unshift(m);this.detachCircleEvent(m);var c=d;while(c.circleEvent&&g(j-c.circleEvent.x)<1e-9&&g(h-c.circleEvent.ycenter)<1e-9){d=c.rbNext; + l.push(c);this.detachBeachsection(c);c=d}l.push(c);this.detachCircleEvent(c);var k=l.length,i;for(i=1;i1e-9){o=o.rbLeft}else{q=j-this.rightBreakPoint(o,n);if(q>1e-9){if(!o.rbRight){p=o; + break}o=o.rbRight}else{if(v>-1e-9){p=o.rbPrevious;m=o}else{if(q>-1e-9){p=o;m=o.rbNext}else{p=m=o}}break}}}var e=this.createBeachsection(l); + this.beachline.rbInsertSuccessor(p,e);if(!p&&!m){return}if(p===m){this.detachCircleEvent(p);m=this.createBeachsection(p.site); + this.beachline.rbInsertSuccessor(e,m);e.edge=m.edge=this.createEdge(p.site,e.site);this.attachCircleEvent(p);this.attachCircleEvent(m); + return}if(p&&!m){e.edge=this.createEdge(p.site,e.site);return}if(p!==m){this.detachCircleEvent(p);this.detachCircleEvent(m); + var h=p.site,k=h.x,i=h.y,t=l.x-k,r=l.y-i,a=m.site,c=a.x-k,b=a.y-i,u=2*(t*b-r*c),g=t*t+r*r,f=c*c+b*b,s=this.createVertex((b*g-r*f)/u+k,(t*f-c*g)/u+i); + this.setEdgeStartpoint(m.edge,h,a,s);e.edge=this.createEdge(h,l,undefined,s);m.edge=this.createEdge(l,a,undefined,s);this.attachCircleEvent(p); + this.attachCircleEvent(m);return}};Voronoi.prototype.CircleEvent=function(){this.arc=null;this.rbLeft=null;this.rbNext=null; + this.rbParent=null;this.rbPrevious=null;this.rbRed=false;this.rbRight=null;this.site=null;this.x=this.y=this.ycenter=0};Voronoi.prototype.attachCircleEvent=function(i){var r=i.rbPrevious,o=i.rbNext; + if(!r||!o){return}var k=r.site,u=i.site,c=o.site;if(k===c){return}var t=u.x,s=u.y,n=k.x-t,l=k.y-s,f=c.x-t,e=c.y-s;var v=2*(n*e-l*f); + if(v>=-2e-12){return}var h=n*n+l*l,g=f*f+e*e,m=(e*h-l*g)/v,j=(n*g-f*h)/v,b=j+s;var q=this.circleEventJunkyard.pop();if(!q){q=new this.CircleEvent() + }q.arc=i;q.site=u;q.x=m+t;q.y=b+this.sqrt(m*m+j*j);q.ycenter=b;i.circleEvent=q;var a=null,p=this.circleEvents.root;while(p){if(q.y=n){return false + }if(i>k){if(!c||c.y=d){return false}}b=this.createVertex(g,d)}else{if(!c||c.y>d){c=this.createVertex(g,d) + }else{if(c.y1){if(i>k){if(!c||c.y=d){return false}}b=this.createVertex((d-q)/m,d)}else{if(!c||c.y>d){c=this.createVertex((d-q)/m,d)}else{if(c.y=n){return false}}b=this.createVertex(n,m*n+q) + }else{if(!c||c.x>n){c=this.createVertex(n,m*n+q)}else{if(c.x0){if(a>e){return false}if(a>f){f=a + }}}c=i.xr-b;if(k===0&&c<0){return false}a=c/k;if(k<0){if(a>e){return false}if(a>f){f=a}}else{if(k>0){if(a0){if(a>e){return false + }if(a>f){f=a}}}c=i.yb-l;if(j===0&&c<0){return false}a=c/j;if(j<0){if(a>e){return false}if(a>f){f=a}}else{if(j>0){if(a0){d.va=this.createVertex(b+f*k,l+f*j)}if(e<1){d.vb=this.createVertex(b+e*k,l+e*j)}if(f>0||e<1){this.cells[d.lSite.voronoiId].closeMe=true; + this.cells[d.rSite.voronoiId].closeMe=true}return true};Voronoi.prototype.clipEdges=function(e){var a=this.edges,d=a.length,c,b=Math.abs; + while(d--){c=a[d];if(!this.connectEdge(c,e)||!this.clipEdge(c,e)||(b(c.va.x-c.vb.x)<1e-9&&b(c.va.y-c.vb.y)<1e-9)){c.va=c.vb=null; + a.splice(d,1)}}};Voronoi.prototype.closeCells=function(p){var g=p.xl,d=p.xr,m=p.yt,j=p.yb,q=this.cells,a=q.length,n,e,o,c,b,l,k,i,f,h=Math.abs; + while(a--){n=q[a];if(!n.prepareHalfedges()){continue}if(!n.closeMe){continue}o=n.halfedges;c=o.length;e=0;while(e=1e-9||h(l.y-i.y)>=1e-9){switch(true){case this.equalWithEpsilon(l.x,g)&&this.lessThanWithEpsilon(l.y,j):f=this.equalWithEpsilon(i.x,g); + k=this.createVertex(g,f?i.y:j);b=this.createBorderEdge(n.site,l,k);e++;o.splice(e,0,this.createHalfedge(b,n.site,null));c++; + if(f){break}l=k;case this.equalWithEpsilon(l.y,j)&&this.lessThanWithEpsilon(l.x,d):f=this.equalWithEpsilon(i.y,j);k=this.createVertex(f?i.x:d,j); + b=this.createBorderEdge(n.site,l,k);e++;o.splice(e,0,this.createHalfedge(b,n.site,null));c++;if(f){break}l=k;case this.equalWithEpsilon(l.x,d)&&this.greaterThanWithEpsilon(l.y,m):f=this.equalWithEpsilon(i.x,d); + k=this.createVertex(d,f?i.y:m);b=this.createBorderEdge(n.site,l,k);e++;o.splice(e,0,this.createHalfedge(b,n.site,null));c++; + if(f){break}l=k;case this.equalWithEpsilon(l.y,m)&&this.greaterThanWithEpsilon(l.x,g):f=this.equalWithEpsilon(i.y,m);k=this.createVertex(f?i.x:g,m); + b=this.createBorderEdge(n.site,l,k);e++;o.splice(e,0,this.createHalfedge(b,n.site,null));c++;if(f){break}l=k;f=this.equalWithEpsilon(i.x,g); + k=this.createVertex(g,f?i.y:j);b=this.createBorderEdge(n.site,l,k);e++;o.splice(e,0,this.createHalfedge(b,n.site,null));c++; + if(f){break}l=k;f=this.equalWithEpsilon(i.y,j);k=this.createVertex(f?i.x:d,j);b=this.createBorderEdge(n.site,l,k);e++;o.splice(e,0,this.createHalfedge(b,n.site,null)); + c++;if(f){break}l=k;f=this.equalWithEpsilon(i.x,d);k=this.createVertex(d,f?i.y:m);b=this.createBorderEdge(n.site,l,k);e++; + o.splice(e,0,this.createHalfedge(b,n.site,null));c++;if(f){break}default:throw"Voronoi.closeCells() > this makes no sense!" + }}e++}n.closeMe=false}};Voronoi.prototype.quantizeSites=function(c){var b=this.ε,d=c.length,a;while(d--){a=c[d];a.x=Math.floor(a.x/b)*b; + a.y=Math.floor(a.y/b)*b}};Voronoi.prototype.recycle=function(a){if(a){if(a instanceof this.Diagram){this.toRecycle=a}else{throw"Voronoi.recycleDiagram() > Need a Diagram object." + }}};Voronoi.prototype.compute=function(i,j){var d=new Date();this.reset();if(this.toRecycle){this.vertexJunkyard=this.vertexJunkyard.concat(this.toRecycle.vertices); + this.edgeJunkyard=this.edgeJunkyard.concat(this.toRecycle.edges);this.cellJunkyard=this.cellJunkyard.concat(this.toRecycle.cells); + this.toRecycle=null}var h=i.slice(0);h.sort(function(n,m){var o=m.y-n.y;if(o){return o}return m.x-n.x});var b=h.pop(),l=0,f,e,k=this.cells,a; + for(;;){a=this.firstCircleEvent;if(b&&(!a||b.y { + sites.push({x: Math.random()*W, y: Math.random()*H}) + }); + return voronoi.compute(sites, bbox); + } + + function getDistances(vertices, p) { + var distances = [] + vertices.forEach(v => { + distances.push({distance: Math.pow(v.x-p.x, 2) + Math.pow(v.y-p.y, 2), vertex:v}); + }); + return distances + } + + function nearest(vertices, p) { + var distances = getDistances(vertices, p); + distances = distances.sort((a, b) => {return a.distance - b.distance}); + return distances[0]; + } + + function farest(vertices, p) { + var distances = getDistances(vertices, p); + distances = distances.sort((a, b) => {return b.distance - a.distance}); + return distances[0]; + } + + function largestDistance(d) { + var largest = 0; + d.cells.forEach((cell) => { + var vertices = []; + cell.halfedges.forEach(e => { + vertices.push(e.getEndpoint()); + }); + var farestP = farest(vertices, cell.site); + largest = Math.max(largest, farestP.distance); + }); + return largest; + } + + function genTerrain() { + try { + cols = W/scl.value(); + rows = H/scl.value(); + var yoff = 0; + for (var x = 0; x < cols; x++) { + terrain[x] = []; + color[x] = []; + var xoff = 0; + for (var y = 0; y < rows; y++) { + let vh = nearest(sites, {x: x*scl.value(),y: y*scl.value()}).distance/largestDistance(diagram); + if (perlin.checked()) { + vh += noise(xoff, yoff); + } + let h = map(vh, 1, 0, -100, 100); //specify a default value for now + terrain[x][y] = h; + color[x][y] = map(h, -100, 100, 0, 255); + xoff += 0.2; + } + yoff += 0.2; + } + } catch (error) { + location.reload(); + } + } + + function init() { + bbox = {xl: 0, xr: W, yt: 0, yb: H}; + diagram = computeDiagram(); + genTerrain(); + } + + function setup() { + angleMode(RADIANS); + W = Math.min(windowWidth, windowHeight)/2 - 10; + H = W; + createCanvas(W*2, H*2, WEBGL); + + scl = createSlider(4, 80, 20, 2); + scl.position(10, 10); + scl.style('width', '80px'); + scl.input(genTerrain); + + speed = createSlider(1, 10, 1, 1); + speed.position(10, 40); + speed.style('width', '80px'); + + strk = createCheckbox('stroke', true); + strk.position(10, 70); + strk.style('color', '#1EBCC5'); + + voronoiDiagram = createCheckbox('voronoi diagram', false); + voronoiDiagram.position(10, 100); + voronoiDiagram.style('color', '#1EBCC5'); + + perlin = createCheckbox('perlin', true); + perlin.position(10, 130); + perlin.style('color', '#1EBCC5'); + perlin.input(genTerrain); + + band = createCheckbox('band', false); + band.position(10, 160); + band.style('color', '#1EBCC5'); + + init(); + } + + function windowResized() { + W = Math.min(windowWidth, windowHeight)/2 - 10; + H = W; + resizeCanvas(W*2, H*2); + init(); + } + + function draw() { + background(0); + translate(0, 50); + + rotateX(PI/3); + rotateZ(2*PI*(rotation/2000)); + translate(-W / 2, -H / 2); + + if (strk.checked()) { + stroke(0, 0, 0); + } else { + noStroke(); + } + + if (voronoiDiagram.checked()) { + background(255); + diagram.cells.forEach((cell) => { + stroke("black"); + strokeWeight(SW); + point(cell.site.x, cell.site.y, 0); + cell.halfedges.forEach(e => { + beginShape(LINES); + vertex(e.getStartpoint().x, e.getStartpoint().y, 0); + vertex(e.getEndpoint().x, e.getEndpoint().y, 0); + endShape(); + }); + }); + } else { + if (band.checked()) { + fill(200, 200, 200, 50); + var s = scl.value(); + for (let y = 0; y < rows - 1; y++) { + beginShape(TRIANGLES); + for (let x = 0; x < cols - 2; x++) { + fill((color[x][y] + color[x][y + 1] + color[x + 1][y]) / 3); + vertex(x * s , y * s , terrain[x][y]); + vertex(x * s , (y + 1) * s, terrain[x][y + 1]); + vertex((x + 1) * s, y * s , terrain[x + 1][y]); + fill((color[x + 1][y + 1] + color[x + 2][y] + color[x + 2][y + 1]) / 3); + vertex((x + 1) * s, (y + 1) * s, terrain[x + 1][y + 1]); + vertex((x + 2) * s, y * s , terrain[x + 2][y]); + vertex((x + 2) * s, (y + 1) * s, terrain[x + 2][y + 1]); + if (x == 0) { + fill((color[x][y + 1] + color[x + 1][y + 1] + color[x + 1][y]) / 3); + vertex(x * s , (y + 1) * s , terrain[x][y + 1]); + vertex((x + 1) * s, (y + 1) * s , terrain[x + 1][y + 1]); + vertex((x + 1) * s, y * s , terrain[x + 1][y]); + } + if (x > cols - 4) { + fill((color[x + 1][y] + color[x + 1][y + 1] + color[x + 2][y]) / 3); + vertex((x + 1) * s , y * s , terrain[x + 1][y]); + vertex((x + 1) * s , (y + 1) * s, terrain[x + 1][y + 1]); + vertex((x + 2) * s, y * s , terrain[x + 2][y]); + } + } + endShape(); + } + } else { + var s = scl.value(); + for (var y = 0; y < rows - 1; y++) { + beginShape(TRIANGLE_STRIP); + for (var x = 0; x < cols; x++) { + fill(color[x][y]); + vertex(x * s, y * s, terrain[x][y]); + fill(color[x][y+1]); + vertex(x * s, (y + 1) * s, terrain[x][y + 1]); + } + endShape(); + } + } + } + rotation += speed.value(); + rotation %= 2000; + } \ No newline at end of file diff --git a/content/sketches/exercises/masking/dithering.js b/content/sketches/exercises/masking/dithering.js new file mode 100644 index 00000000..4b5d2b20 --- /dev/null +++ b/content/sketches/exercises/masking/dithering.js @@ -0,0 +1,100 @@ +let img; +var factor=1; //If it is equal to 1 and there is a gray filter there will be only two colors. +var wd; +var d; + + +function preload() { + img = loadImage('cat.png'); +} + +function setup() { + let c=createCanvas(img.width,img.height); + background(255, 255, 255); + pixelDensity(1); + image(img,0,0); + filter(GRAY); + wd=img.width; + loadPixels(); + d = pixelDensity(); + for (let y = 0; y < height-1; y++) { + for (let x = 1; x < width-1; x++){ + + var index = getIndex(x,y); + + var r= pixels[index]; + var g= pixels[index + 1]; + var b= pixels[index + 2]; + var a= pixels[index + 3]; + + var newR=find_closest_palette_color(r); + var newG=find_closest_palette_color(g); + var newB=find_closest_palette_color(b); + var newA= find_closest_palette_color(a); + + pixels[index] = newR; + pixels[index + 1] = newG; + pixels[index + 2] = newB; + pixels[index + 3] = newA; + + + //Calculating quantization error of a pixel. + var quant_error_R=r-newR; + var quant_error_G=g-newG; + var quant_error_B=b-newB; + var quant_error_A=a-newA; + + // + index = getIndex(x+1,y); + r= pixels[index]+(quant_error_R*7/16.0); + g= pixels[index + 1]+(quant_error_G*7/16.0); + b= pixels[index + 2]+(quant_error_B*7/16.0); + a= pixels[index + 3]+(quant_error_A*7/16.0); + pixels[index] = r; + pixels[index + 1] = g; + pixels[index + 2] = b; + pixels[index + 3] = a; + + index = getIndex(x-1,y+1); + r= pixels[index]+(quant_error_R*3/16.0); + g= pixels[index + 1]+(quant_error_G*3/16.0); + b= pixels[index + 2]+(quant_error_B*3/16.0); + a= pixels[index + 3]+(quant_error_A*3/16.0); + pixels[index] = r; + pixels[index + 1] = g; + pixels[index + 2] = b; + pixels[index + 3] = a; + + index = getIndex(x,y+1); + r= pixels[index]+(quant_error_R*5/16.0); + g= pixels[index + 1]+(quant_error_G*5/16.0); + b= pixels[index + 2]+(quant_error_B*5/16.0); + a= pixels[index + 3]+(quant_error_A*5/16.0); + pixels[index] = r; + pixels[index + 1] = g; + pixels[index + 2] = b; + pixels[index + 3] = a; + + index = getIndex(x+1,y+1); + r= pixels[index]+(quant_error_R*1/16.0); + g= pixels[index + 1]+(quant_error_G*1/16.0); + b= pixels[index + 2]+(quant_error_B*1/16.0); + a= pixels[index + 3]+(quant_error_A*1/16.0); + pixels[index] = r; + pixels[index + 1] = g; + pixels[index + 2] = b; + pixels[index + 3] = a; + } + } + updatePixels(); +} + +//Operación de cuantización. +function find_closest_palette_color(old){ + return round(factor*old/255)*(255/factor) +} + +//Retorna la posición (x,y) de un píxel. +function getIndex(x,y){ + return (x+(y*wd))*4; +} diff --git a/content/sketches/exercises/masking/original_web_app.js b/content/sketches/exercises/masking/original_web_app.js new file mode 100644 index 00000000..f6cd2120 --- /dev/null +++ b/content/sketches/exercises/masking/original_web_app.js @@ -0,0 +1,266 @@ +let HW = 400; +let HH = 400; +let HP = 200; +let BG = 8; + +function drawRGBHistogram(imgBuffer) { + let histR = (new Array(256)).fill(0); + let histG = (new Array(256)).fill(0); + let histB = (new Array(256)).fill(0); + let maxB = 0; + for (let i = 0; i < imgBuffer.length; i += 4) { + histR[imgBuffer[i]]++; + histG[imgBuffer[i + 1]]++; + histB[imgBuffer[i + 2]]++; + } + + maxB = Math.max(Math.max(...histR), Math.max(...histG), Math.max(...histB)); + + const y1 = HP + HH; + const dx = HW / 255; + const dy = HW / maxB; + rect(0, HP, HW, HH); + strokeWeight(dx); + + for (let i = 0; i < 256; i++) { + let x = i * dx; + stroke(color(220,0,0,128)); + line(x, y1, x, y1 - histR[i] * dy); + stroke(color(0,210,0,128)); + line(x, y1, x, y1 - histG[i] * dy); + stroke(color(0,0,255,128)); + line(x, y1, x, y1 - histB[i] * dy); + stroke(color(i, i, i)); + line(x, y1, x, y1 + BG); + } +} + +function drawBrightnessHistogram(imgBuffer) { + let hist = (new Array(256)).fill(0); + let maxB = 0; + + for (let i = 0; i < imgBuffer.length; i++) { + // Ignoring alpha + if (i%4 != 3) { + hist[imgBuffer[i]]++; + } + } + + maxB = Math.max(...hist); + + const y1 = HP + HH; + const dx = HW / 255; + const dy = HW / maxB; + rect(0, HP, HW, HH); + strokeWeight(dx); + + for (let i = 0; i < 256; i++) { + let x = i * dx; + stroke(color("black")); + line(x, y1 + BG, x, y1 - hist[i] * dy); + stroke(color(i, i, i)); + line(x, y1, x, y1 + BG); + } +} + +function copyArray(a) { + c = Array(a.length); + i = a.length; + while(i--) c[i] = a[i]; + return a; +} + +function applyLuma(){ + let lumaImg = createImage(img.width, img.height); + lumaImg.loadPixels(); + for (let i = 0; i < img.pixels.length; i+=4) { + lumaImg.pixels[i] = (img.pixels[i] + img.pixels[i + 1] + img.pixels[i + 2]) / 3; + lumaImg.pixels[i + 1] = (img.pixels[i] + img.pixels[i + 1] + img.pixels[i + 2]) / 3; + lumaImg.pixels[i + 2] = (img.pixels[i] + img.pixels[i + 1] + img.pixels[i + 2]) / 3; + lumaImg.pixels[i + 3] = 255; + } + lumaImg.updatePixels(); + lumaImg.save('luma', 'png'); +} + +function convolve(name, matrix) { + let convolvedImage = createImage(img.width, img.height); + convolvedImage.loadPixels(); + for (let x = 0; x < img.width; x++) { + for (let y = 0; y < img.height; y++) { + let c = convolution(x, y, matrix, img); + + let loc = (x + y * img.width) * 4; + convolvedImage.pixels[loc] = red(c); + convolvedImage.pixels[loc + 1] = green(c); + convolvedImage.pixels[loc + 2] = blue(c); + convolvedImage.pixels[loc + 3] = alpha(c); + } + } + convolvedImage.updatePixels(); + convolvedImage.save(name, 'png'); +} + +function convolution(x, y, matrix) { + let rtotal = 0.0; + let gtotal = 0.0; + let btotal = 0.0; + const offset = Math.floor(matrix.length / 2); + for (let i = 0; i < matrix.length; i++){ + for (let j = 0; j < matrix.length; j++){ + + // What pixel are we testing + const xloc = (x + i - offset); + const yloc = (y + j - offset); + let loc = (xloc + yloc * img.width) * 4; + + // Make sure we haven't walked off our image, we could do better here + loc = constrain(loc, 0 , img.pixels.length - 1); + + // Calculate the convolution + // retrieve RGB values + rtotal += (img.pixels[loc]) * matrix[i][j]; + gtotal += (img.pixels[loc + 1]) * matrix[i][j]; + btotal += (img.pixels[loc + 2]) * matrix[i][j]; + } + } + // Make sure RGB is within range + rtotal = constrain(rtotal, 0, 255); + gtotal = constrain(gtotal, 0, 255); + btotal = constrain(btotal, 0, 255); + + // Return the resulting color + return color(rtotal, gtotal, btotal); +} + +function applySharpen() { + convolve("Sharpen", kernels["Sharpen"]); +} + +function applyBoxBlur() { + convolve("BoxBlur", kernels["BoxBlur"]); +} + +function applyGaussianBlur3x3() { + convolve("GaussianBlur3x3", kernels["GaussianBlur3x3"]); +} + +function applyGaussianBlur5x5() { + convolve("GaussianBlur5x5", kernels["GaussianBlur5x5"]); +} + +function applyUnsharp5x5() { + convolve("Unsharp5x5", kernels["Unsharp5x5"]); +} + +let kernels = { + "Sharpen" : [[0,-1,0], + [-1,5,-1], + [0,-1,0]], + "BoxBlur" : [[1/9,1/9,1/9], + [1/9,1/9,1/9], + [1/9,1/9,1/9]], + "GaussianBlur3x3" : [[1/16,1/8,1/16], + [1/8,1/4,1/8], + [1/16,1/8,1/16]], + "GaussianBlur5x5" : [[1/256,4/256,6/256,4/256,1/256], + [4/256,16/256,1/256,16/256,4/256], + [6/256,24/256,36/256,24/256,6/256], + [4/256,16/256,1/256,16/256,4/256], + [1/256,4/256,6/256,4/256,1/256]], + "Unsharp5x5" : [[-1/256,-4/256,-6/256,-4/256,-1/256], + [-4/256,-16/256,-1/256,-16/256,-4/256], + [-6/256,-24/256,476/256,-24/256,-6/256], + [-4/256,-16/256,-1/256,-16/256,-4/256], + [-1/256,-4/256,-6/256,-4/256,-1/256]], +}; + +let img; +let input; +let histogramTypeSelect; +let sharpenButton; +let boxBlurButton; +let gaussianBlur3x3Button; +let gaussianBlur5x5Button; +let unsharpMasking5x5Button; +let lumaButton; + + +function handleFiles() { + const fileList = this.files; /* now you can work with the file list */ + const file = fileList[0] + + var reader = new FileReader(); + + reader.onload = function(e) { + if (file.type === 'image/png' || file.type === 'image/jpeg') { + img = loadImage(e.target.result, ''); + img.loadPixels(); + } else { + img = null; + } + } + + reader.readAsDataURL(file); +} + +function setup() { + createCanvas(HW+100, HH+100 + HP); + input = document.getElementById("file-input"); + input.addEventListener("change", handleFiles, false); + + histogramTypeSelect = createSelect(); + histogramTypeSelect.position(HP, HP-50); + histogramTypeSelect.option('RGB'); + histogramTypeSelect.option('Brightness'); + histogramTypeSelect.hide(); + + sharpenButton = createButton('Sharpen'); + sharpenButton.position(10, 30); + sharpenButton.mousePressed(applySharpen); + sharpenButton.hide(); + + boxBlurButton = createButton('Box blur'); + boxBlurButton.position(10, 70); + boxBlurButton.mousePressed(applyBoxBlur); + boxBlurButton.hide(); + + gaussianBlur3x3Button = createButton('Gaussian blur(3x3)'); + gaussianBlur3x3Button.position(10, 110); + gaussianBlur3x3Button.mousePressed(applyGaussianBlur3x3); + gaussianBlur3x3Button.hide(); + + gaussianBlur5x5Button = createButton('Gaussian blur(5x5)'); + gaussianBlur5x5Button.position(200, 30); + gaussianBlur5x5Button.mousePressed(applyGaussianBlur5x5); + gaussianBlur5x5Button.hide(); + + unsharpMasking5x5Button = createButton('Unsharp'); + unsharpMasking5x5Button.position(200, 70); + unsharpMasking5x5Button.mousePressed(applyUnsharp5x5); + unsharpMasking5x5Button.hide(); + + lumaButton = createButton('Luminance'); + lumaButton.position(200, 110); + lumaButton.mousePressed(applyLuma); + lumaButton.hide(); +} + +function draw() { + background(0); + if (img) { + histogramTypeSelect.show(); + sharpenButton.show(); + boxBlurButton.show(); + gaussianBlur3x3Button.show(); + gaussianBlur5x5Button.show(); + unsharpMasking5x5Button.show(); + lumaButton.show(); + img.loadPixels(); + if (histogramTypeSelect.value() === 'RGB') { + drawRGBHistogram(img.pixels); + } else { + drawBrightnessHistogram(img.pixels) + } + } +} \ No newline at end of file diff --git a/content/sketches/exercises/masking/web_app.js b/content/sketches/exercises/masking/web_app.js new file mode 100644 index 00000000..de487f02 --- /dev/null +++ b/content/sketches/exercises/masking/web_app.js @@ -0,0 +1,279 @@ +let HW = 400; +let HH = 400; +let HP = 200; +let BG = 8; + +let lumaImg; +let convolvedImage; +let img; + +function drawRGBHistogram(imgBuffer) { + let histR = (new Array(256)).fill(0); + let histG = (new Array(256)).fill(0); + let histB = (new Array(256)).fill(0); + let maxB = 0; + for (let i = 0; i < imgBuffer.length; i += 4) { + histR[imgBuffer[i]]++; + histG[imgBuffer[i + 1]]++; + histB[imgBuffer[i + 2]]++; + } + + maxB = Math.max(Math.max(...histR), Math.max(...histG), Math.max(...histB)); + + const y1 = HP + HH; + const dx = HW / 255; + const dy = HW / maxB; + rect(0, HP, HW, HH); + strokeWeight(dx); + + for (let i = 0; i < 256; i++) { + let x = i * dx; + stroke(color(220,0,0,128)); + line(x, y1, x, y1 - histR[i] * dy); + stroke(color(0,210,0,128)); + line(x, y1, x, y1 - histG[i] * dy); + stroke(color(0,0,255,128)); + line(x, y1, x, y1 - histB[i] * dy); + stroke(color(i, i, i)); + line(x, y1, x, y1 + BG); + } +} + +function drawBrightnessHistogram(imgBuffer) { + let hist = (new Array(256)).fill(0); + let maxB = 0; + + for (let i = 0; i < imgBuffer.length; i++) { + // Ignoring alpha + if (i%4 != 3) { + hist[imgBuffer[i]]++; + } + } + + maxB = Math.max(...hist); + + const y1 = HP + HH; + const dx = HW / 255; + const dy = HW / maxB; + rect(0, HP, HW, HH); + strokeWeight(dx); + + for (let i = 0; i < 256; i++) { + let x = i * dx; + stroke(color("black")); + line(x, y1 + BG, x, y1 - hist[i] * dy); + stroke(color(i, i, i)); + line(x, y1, x, y1 + BG); + } +} + +function copyArray(a) { + c = Array(a.length); + i = a.length; + while(i--) c[i] = a[i]; + return a; +} + +function applyLuma(){ + lumaImg = createImage(img.width, img.height); + lumaImg.loadPixels(); + for (let i = 0; i < img.pixels.length; i+=4) { + lumaImg.pixels[i] = (img.pixels[i] + img.pixels[i + 1] + img.pixels[i + 2]) / 3; + lumaImg.pixels[i + 1] = (img.pixels[i] + img.pixels[i + 1] + img.pixels[i + 2]) / 3; + lumaImg.pixels[i + 2] = (img.pixels[i] + img.pixels[i + 1] + img.pixels[i + 2]) / 3; + lumaImg.pixels[i + 3] = 255; + } + lumaImg.updatePixels(); + imageType = "Luma"; +} + +function convolve(name, matrix) { + convolvedImage = createImage(img.width, img.height); + convolvedImage.loadPixels(); + for (let x = 0; x < img.width; x++) { + for (let y = 0; y < img.height; y++) { + let c = convolution(x, y, matrix, img); + + let loc = (x + y * img.width) * 4; + convolvedImage.pixels[loc] = red(c); + convolvedImage.pixels[loc + 1] = green(c); + convolvedImage.pixels[loc + 2] = blue(c); + convolvedImage.pixels[loc + 3] = alpha(c); + } + } + convolvedImage.updatePixels(); + imageType = "Convolved"; +} + +function convolution(x, y, matrix) { + let rtotal = 0.0; + let gtotal = 0.0; + let btotal = 0.0; + const offset = Math.floor(matrix.length / 2); + for (let i = 0; i < matrix.length; i++){ + for (let j = 0; j < matrix.length; j++){ + + // What pixel are we testing + const xloc = (x + i - offset); + const yloc = (y + j - offset); + let loc = (xloc + yloc * img.width) * 4; + + // Make sure we haven't walked off our image, we could do better here + loc = constrain(loc, 0 , img.pixels.length - 1); + + // Calculate the convolution + // retrieve RGB values + rtotal += (img.pixels[loc]) * matrix[i][j]; + gtotal += (img.pixels[loc + 1]) * matrix[i][j]; + btotal += (img.pixels[loc + 2]) * matrix[i][j]; + } + } + // Make sure RGB is within range + rtotal = constrain(rtotal, 0, 255); + gtotal = constrain(gtotal, 0, 255); + btotal = constrain(btotal, 0, 255); + + // Return the resulting color + return color(rtotal, gtotal, btotal); +} + +function applySharpen() { + convolve("Sharpen", kernels["Sharpen"]); +} + +function applyBoxBlur() { + convolve("BoxBlur", kernels["BoxBlur"]); +} + +function applyGaussianBlur3x3() { + convolve("GaussianBlur3x3", kernels["GaussianBlur3x3"]); +} + +function applyGaussianBlur5x5() { + convolve("GaussianBlur5x5", kernels["GaussianBlur5x5"]); +} + +function applyUnsharp5x5() { + convolve("Unsharp5x5", kernels["Unsharp5x5"]); +} + +function showOriginalImage() { + imageType = "Original"; +} + +function resetImage() { + img = loadImage("https://picsum.photos/400"); +} + +let kernels = { + "Sharpen" : [[0,-1,0], + [-1,5,-1], + [0,-1,0]], + "BoxBlur" : [[1/9,1/9,1/9], + [1/9,1/9,1/9], + [1/9,1/9,1/9]], + "GaussianBlur3x3" : [[1/16,1/8,1/16], + [1/8,1/4,1/8], + [1/16,1/8,1/16]], + "GaussianBlur5x5" : [[1/256,4/256,6/256,4/256,1/256], + [4/256,16/256,1/256,16/256,4/256], + [6/256,24/256,36/256,24/256,6/256], + [4/256,16/256,1/256,16/256,4/256], + [1/256,4/256,6/256,4/256,1/256]], + "Unsharp5x5" : [[-1/256,-4/256,-6/256,-4/256,-1/256], + [-4/256,-16/256,-1/256,-16/256,-4/256], + [-6/256,-24/256,476/256,-24/256,-6/256], + [-4/256,-16/256,-1/256,-16/256,-4/256], + [-1/256,-4/256,-6/256,-4/256,-1/256]], +}; + +let input; +let showTypeSelect; +let sharpenButton; +let boxBlurButton; +let gaussianBlur3x3Button; +let gaussianBlur5x5Button; +let unsharpMasking5x5Button; +let lumaButton; +let originalButton; +let imageType = "Original"; + +function setup() { + createCanvas(HW+100, HH+100 + HP); + img = loadImage("https://picsum.photos/400"); + + showTypeSelect = createSelect(); + showTypeSelect.position(HP, HP-50); + showTypeSelect.value("Mode"); + showTypeSelect.option('RGB Histogram'); + showTypeSelect.option('Brightness Histogram'); + showTypeSelect.option('Image'); + showTypeSelect.hide(); + + sharpenButton = createButton('Sharpen'); + sharpenButton.position(10, 30); + sharpenButton.mousePressed(applySharpen); + sharpenButton.hide(); + + boxBlurButton = createButton('Box blur'); + boxBlurButton.position(10, 70); + boxBlurButton.mousePressed(applyBoxBlur); + boxBlurButton.hide(); + + gaussianBlur3x3Button = createButton('Gaussian blur(3x3)'); + gaussianBlur3x3Button.position(10, 110); + gaussianBlur3x3Button.mousePressed(applyGaussianBlur3x3); + gaussianBlur3x3Button.hide(); + + gaussianBlur5x5Button = createButton('Gaussian blur(5x5)'); + gaussianBlur5x5Button.position(200, 30); + gaussianBlur5x5Button.mousePressed(applyGaussianBlur5x5); + gaussianBlur5x5Button.hide(); + + unsharpMasking5x5Button = createButton('Unsharp'); + unsharpMasking5x5Button.position(200, 70); + unsharpMasking5x5Button.mousePressed(applyUnsharp5x5); + unsharpMasking5x5Button.hide(); + + lumaButton = createButton('Luminance'); + lumaButton.position(200, 110); + lumaButton.mousePressed(applyLuma); + lumaButton.hide(); + + originalButton = createButton('Original'); + originalButton.position(10, HP-50); + originalButton.mousePressed(showOriginalImage); + originalButton.hide(); + + refreshButton = createButton('Refresh'); + refreshButton.position(HP+200, HP - 50); + refreshButton.mousePressed(resetImage); + refreshButton.hide(); +} + +function draw() { + background(0); + if (img) { + showTypeSelect.show(); + sharpenButton.show(); + boxBlurButton.show(); + gaussianBlur3x3Button.show(); + gaussianBlur5x5Button.show(); + unsharpMasking5x5Button.show(); + lumaButton.show(); + originalButton.show(); + refreshButton.show(); + img.loadPixels(); + if (showTypeSelect.value() === 'RGB Histogram') { + drawRGBHistogram(img.pixels); + } else if (showTypeSelect.value() === 'Brightness Histogram') { + drawBrightnessHistogram(img.pixels) + } else if (imageType === "Original") { + image(img, 10, HP); + } else if (imageType === "Luma") { + image(lumaImg, 10, HP); + } else if (imageType === "Convolved") { + image(convolvedImage, 10, HP); + } + } +} diff --git a/content/sketches/exercises/spacial_coherence/2.js b/content/sketches/exercises/spacial_coherence/2.js new file mode 100644 index 00000000..25658d26 --- /dev/null +++ b/content/sketches/exercises/spacial_coherence/2.js @@ -0,0 +1,168 @@ +let img; +let ratioInput; +let ratioValue; +let newImg, newImg2; +let updateBtn; + +function preload() { + img = loadImage("https://picsum.photos/400"); +} + +function setup() { + + createCanvas(img.width * 2, img.height * 2); + text = createP("Original"); + text.position(10, img.height/2 - 30); + text2 = createP("Color averaging"); + text2.position(img.width + 8, 0); + text3 = createP("Spatial coherence"); + text3.position(img.width + 8, img.height + 22); + ratioInput = createInput(10, "number"); + ratioInput.position(55, (img.height/2) + img.height + 40); + ratioInput.style('width', `${img.width / 2 - 70}px`); + + ratioValue = createDiv("Ratio: "); + ratioValue.position(10, (img.height/2) + img.height + 42); + + updateBtn = createButton('Update'); + updateBtn.position(10, (img.height/2) + img.height + 65); + updateBtn.style('background-color', '#76b5c5'); + updateBtn.style('border', 'none'); + updateBtn.style('color', 'white'); + updateBtn.style('padding', '8px 14px'); + updateBtn.style('text-align', 'center'); + updateBtn.style('text-decoration', 'none'); + updateBtn.style('display', 'inline-block'); + updateBtn.style('font-size', '16px'); + updateBtn.style('margin', '4px 2px'); + updateBtn.style('cursor', 'pointer'); + updateBtn.mousePressed(updateImages); + pixelDensity(calcPixelDensity()); + newImg = createImage(img.width, img.height); + newImg2 = createImage(img.width, img.height); + + updateImages(); + +} + +function draw() { + image(img, -10, img.height/2); + image(newImg, img.width, 30); + image(newImg2, img.width, img.height + 50); +} + +function updateImages() { + updateImage1(); + updateImage2(); + background("#eeeee4"); +} + +function updateImage1() { + let ratio = ratioInput.value(); + let avgColors = getAverageColors(img); + setPixels(newImg, avgColors, ratio); + image(newImg, img.width, 30); +} + +function updateImage2() { + let ratio = parseInt(ratioInput.value()); + newImg2.loadPixels(); + let new_pixels = []; + for (let y = 0; y < newImg2.height; y++) { + for (let x = 0; x < newImg2.width; x++) { + let randX = -1; + let randY = -1; + while (randX < 0 || randX >= img.width || randY < 0 || randY >= img.height){ + randX = x + int(random(parseInt(-ratio/2), parseInt(ratio/2))); + randY = y + int(random(parseInt(-ratio/2), parseInt(ratio/2))); + } + let index = (x + y * newImg2.width) * 4; + let new_index = (randX + randY * newImg2.width) * 4; + + newImg2.pixels[index] = img.pixels[new_index]; + newImg2.pixels[index + 1] = img.pixels[new_index+1]; + newImg2.pixels[index + 2] = img.pixels[new_index+2]; + newImg2.pixels[index + 3] = 255; + } + } + + newImg2.updatePixels(); + image(newImg2, img.width, img.height + 50); +} + +function getAverageColors(img) +{ + let avgColors = []; + img.loadPixels(); + + for (let y = 0; y < img.height; y++) { + for (let x = 0; x < img.width; x++) { + let index = (x + y * img.width) * 4; + let r = img.pixels[index]; + let g = img.pixels[index + 1]; + let b = img.pixels[index + 2]; + avgColors.push(color(r , g , b)); + } + } + img.updatePixels(); + + return avgColors; +} + +function setPixels(img, avgColors, ratio) { + img.loadPixels(); + + for (let y = 0; y < img.height; y++) { + for (let x = 0; x < img.width; x++) { + let index = (x + y * img.width) * 4; + let r = 0; + let g = 0; + let b = 0; + let count = 0; + + for (let i = 0; i <= ratio; i++) { + for (let j = 0; j <= ratio; j++) { + let neighborX = x + i; + let neighborY = y + j; + + if (neighborX >= 0 && neighborX < img.width && neighborY >= 0 && neighborY < img.height) { + let neighborIndex = (neighborX + neighborY * img.width) * 4; + r += red(avgColors[neighborIndex / 4]); + g += green(avgColors[neighborIndex / 4]); + b += blue(avgColors[neighborIndex / 4]); + count++; + } + } + } + + r /= count; + g /= count; + b /= count; + + img.pixels[index] = r; + img.pixels[index + 1] = g; + img.pixels[index + 2] = b; + img.pixels[index + 3] = 255; + } + } + + img.updatePixels(); +} + +function calcPixelDensity() { + let density = displayDensity(); + let w = img.width * density; + let h = img.height * density; + let ratio = w / h; + let pixelDensity = 1; + if (w > displayWidth || h > displayHeight) { + if (ratio > 1) { + pixelDensity = ceil(w / displayWidth); + + + } else { + pixelDensity = ceil(h / displayHeight); + } + } + return pixelDensity; +} \ No newline at end of file diff --git a/content/sketches/exercises/visual_illusions/kinegram.js b/content/sketches/exercises/visual_illusions/kinegram.js new file mode 100644 index 00000000..c02c3f5a --- /dev/null +++ b/content/sketches/exercises/visual_illusions/kinegram.js @@ -0,0 +1,49 @@ +let startButton, stopButton; +let xOffset, xSpeed; +let img; + +function preload() { + img = loadImage('/showcase/lobo.jpg'); +} + +function setup() { + createCanvas(700, 400); + text = createP("Speed"); + startButton = createButton('Start'); + startButton.mousePressed(startGrid); + stopButton = createButton('Stop'); + stopButton.mousePressed(stopGrid); + input = createInput(); + input.value(2.5); + startGrid(); +} + +function draw() { + background(img); + if (xOffset) { + xOffset += xSpeed; + if (xOffset > width) { + xOffset = -width; + } + } + drawLines(); +} + +function drawLines() { + stroke(0); + strokeWeight(5); + let lineSpacing = 10; + for (let x = -width*1000; x < width*1000; x += lineSpacing) { + line(x + xOffset, 0, x + xOffset, height); + } +} + +function startGrid() { + let val = input.value(); + xSpeed = parseFloat(val); + xOffset = -width; +} + +function stopGrid() { + xOffset = null; +} diff --git a/content/sketches/exercises/visual_illusions/moire_pattern1.js b/content/sketches/exercises/visual_illusions/moire_pattern1.js new file mode 100644 index 00000000..1b841820 --- /dev/null +++ b/content/sketches/exercises/visual_illusions/moire_pattern1.js @@ -0,0 +1,50 @@ +let figuresCount = 100; +let shapeSize = 4; + +function setup() { + createCanvas(400, 400); + shapesCount = createInput(figuresCount); + shapesCount.value(100); + + shapeSizesInput = createInput(shapeSize); + shapeSizesInput.value(10); + + shapesCount.input(() => { + figuresCount = parseInt(shapesCount.value()); + }); + + shapeSizesInput.input(() => { + shapeSize = parseInt(shapeSizesInput.value()); + }); + +} + +let rotationAngle = 0; + +function draw() { + background(220); + noFill(); + strokeWeight(2); + + let centerX = width / 2; + let centerY = height / 2; + + for (let i = 0; i < figuresCount; i++) { + let squareX = centerX - (shapeSize * (figuresCount - i)) / 2; + let squareY = centerY - (shapeSize * (figuresCount - i)) / 2; + let squareW = shapeSize * (figuresCount - i); + + push(); + translate(squareX + squareW/2, squareY + squareW/2); + rotate(radians(rotationAngle)); + stroke("blue"); + rectMode(CENTER); + rect(-squareW/2, -squareW/2, squareW, squareW); + + stroke("red"); + let circleR = (shapeSize * (figuresCount - i)) ; + ellipse(centerX, centerY, circleR, circleR); + } + + rotationAngle += 1; +} diff --git a/content/sketches/exercises/visual_illusions/moire_pattern2.js b/content/sketches/exercises/visual_illusions/moire_pattern2.js new file mode 100644 index 00000000..da48899d --- /dev/null +++ b/content/sketches/exercises/visual_illusions/moire_pattern2.js @@ -0,0 +1,60 @@ +let numShapes = 100; +let shapeSize = 10; +let angle = 0; + +function setup() { + createCanvas(400, 400); + numShapesInput = createInput(numShapes); + numShapesInput.value(100); + + shapeSizesInput = createInput(shapeSize); + shapeSizesInput.value(10); + + numShapesInput.input(() => { + numShapes = parseInt(numShapesInput.value()); + }); + + shapeSizesInput.input(() => { + shapeSize = parseInt(shapeSizesInput.value()); + }); +} + +function draw() { + background(220); + noFill(); + + // Center the shapes in the canvas + let centerX = width / 2; + let centerY = height / 2; + + // Draw a loop of triangles + stroke("blue"); + for (let i = 0; i < numShapes; i++) { + let triangleX = centerX; + let triangleY = centerY; + let triangleR = shapeSize * (numShapes - i); + let triangleA = angle + i * (360 / numShapes); + push(); + translate(triangleX, triangleY); + rotate(radians(triangleA)); + triangle(0, 0, triangleR, triangleR * sqrt(3) / 2, -triangleR, triangleR * sqrt(3) / 2); + pop(); + } + + // Draw a loop of squares + stroke("red"); + for (let i = 0; i < numShapes; i++) { + let squareX = centerX; + let squareY = centerY; + let squareW = shapeSize * (numShapes - i); + let squareA = angle - i * (360 / numShapes); + push(); + translate(squareX, squareY); + rotate(radians(squareA)); + rectMode(CENTER); + rect(0, 0, squareW, squareW); + pop(); + } + + angle += 1; +} diff --git a/layouts/shortcodes/p5-file-input-iframe.html b/layouts/shortcodes/p5-file-input-iframe.html new file mode 100644 index 00000000..49a4ceee --- /dev/null +++ b/layouts/shortcodes/p5-file-input-iframe.html @@ -0,0 +1,30 @@ +{{ $defver := `1.5.0` }} +{{ $sketch := .Get `sketch` }} +{{ $lib1 := .Get `lib1` }} +{{ $lib2 := .Get `lib2` }} +{{ $lib3 := .Get `lib3` }} +{{ $lib4 := .Get `lib4` }} +{{ $lib5 := .Get `lib5` }} + + diff --git a/resources/.DS_Store b/resources/.DS_Store new file mode 100644 index 00000000..7a842af6 Binary files /dev/null and b/resources/.DS_Store differ diff --git a/resources/_gen/.DS_Store b/resources/_gen/.DS_Store new file mode 100644 index 00000000..06b07e78 Binary files /dev/null and b/resources/_gen/.DS_Store differ diff --git a/resources/_gen/assets/scss/book.scss_e129fe35b8d0a70789c8a08429469073.content b/resources/_gen/assets/scss/book.scss_e129fe35b8d0a70789c8a08429469073.content new file mode 100644 index 00000000..721229eb --- /dev/null +++ b/resources/_gen/assets/scss/book.scss_e129fe35b8d0a70789c8a08429469073.content @@ -0,0 +1 @@ +@charset "UTF-8";:root{--gray-100:rgba(255, 255, 255, 0.1);--gray-200:rgba(255, 255, 255, 0.2);--gray-500:rgba(255, 255, 255, 0.5);--color-link:#84b2ff;--color-visited-link:#b88dff;--body-background:#343a40;--body-font-color:#e9ecef;--icon-filter:brightness(0) invert(1);--hint-color-info:#6bf;--hint-color-warning:#fd6;--hint-color-danger:#f66}/*!normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css*/html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{border-style:none;padding:0}button:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none}.flex{display:flex}.flex-auto{flex:auto}.flex-even{flex:1 1}.flex-wrap{flex-wrap:wrap}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.align-center{align-items:center}.mx-auto{margin:0 auto}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.hidden{display:none}input.toggle{height:0;width:0;overflow:hidden;opacity:0;position:absolute}.clearfix::after{content:"";display:table;clear:both}html{font-size:16px;scroll-behavior:smooth;touch-action:manipulation}body{min-width:20rem;color:var(--body-font-color);background:var(--body-background);letter-spacing:.33px;font-weight:400;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;box-sizing:border-box}body *{box-sizing:inherit}h1,h2,h3,h4,h5{font-weight:400}a{text-decoration:none;color:var(--color-link)}img{vertical-align:baseline}:focus{outline-style:auto;outline-color:currentColor;outline-color:-webkit-focus-ring-color}aside nav ul{padding:0;margin:0;list-style:none}aside nav ul li{margin:1em 0;position:relative}aside nav ul a{display:block}aside nav ul a:hover{opacity:.5}aside nav ul ul{padding-inline-start:1rem}ul.pagination{display:flex;justify-content:center;list-style-type:none;padding-inline-start:0}ul.pagination .page-item a{padding:1rem}.container{max-width:80rem;margin:0 auto}.book-icon{filter:var(--icon-filter)}.book-brand{margin-top:0;margin-bottom:1rem}.book-brand img{height:1.5em;width:1.5em;margin-inline-end:.5rem}.book-menu{flex:0 0 16rem;font-size:.875rem}.book-menu .book-menu-content{width:16rem;padding:1rem;background:var(--body-background);position:fixed;top:0;bottom:0;overflow-x:hidden;overflow-y:auto}.book-menu a,.book-menu label{color:inherit;cursor:pointer;word-wrap:break-word}.book-menu a.active{color:var(--color-link)}.book-menu input.toggle+label+ul{display:none}.book-menu input.toggle:checked+label+ul{display:block}.book-menu input.toggle+label::after{content:"▸"}.book-menu input.toggle:checked+label::after{content:"▾"}body[dir=rtl] .book-menu input.toggle+label::after{content:"◂"}body[dir=rtl] .book-menu input.toggle:checked+label::after{content:"▾"}.book-section-flat{margin:2rem 0}.book-section-flat>a,.book-section-flat>span,.book-section-flat>label{font-weight:bolder}.book-section-flat>ul{padding-inline-start:0}.book-page{min-width:20rem;flex-grow:1;padding:1rem}.book-post{margin-bottom:3rem}.book-header{display:none;margin-bottom:1rem}.book-header label{line-height:0}.book-header img.book-icon{height:1.5em;width:1.5em}.book-search{position:relative;margin:1rem 0;border-bottom:1px solid transparent}.book-search input{width:100%;padding:.5rem;border:0;border-radius:.25rem;background:var(--gray-100);color:var(--body-font-color)}.book-search input:required+.book-search-spinner{display:block}.book-search .book-search-spinner{position:absolute;top:0;margin:.5rem;margin-inline-start:calc(100% - 1.5rem);width:1rem;height:1rem;border:1px solid transparent;border-top-color:var(--body-font-color);border-radius:50%;animation:spin 1s ease infinite}@keyframes spin{100%{transform:rotate(360deg)}}.book-search small{opacity:.5}.book-toc{flex:0 0 16rem;font-size:.75rem}.book-toc .book-toc-content{width:16rem;padding:1rem;position:fixed;top:0;bottom:0;overflow-x:hidden;overflow-y:auto}.book-toc img{height:1em;width:1em}.book-toc nav>ul>li:first-child{margin-top:0}.book-footer{padding-top:1rem;font-size:.875rem}.book-footer img{height:1em;width:1em;margin-inline-end:.5rem}.book-comments{margin-top:1rem}.book-languages{margin-block-end:2rem}.book-languages .book-icon{height:1em;width:1em;margin-inline-end:.5em}.book-languages ul{padding-inline-start:1.5em}.book-menu-content,.book-toc-content,.book-page,.book-header aside,.markdown{transition:.2s ease-in-out;transition-property:transform,margin,opacity,visibility;will-change:transform,margin,opacity}@media screen and (max-width:56rem){#menu-control,#toc-control{display:inline}.book-menu{visibility:hidden;margin-inline-start:-16rem;font-size:16px;z-index:1}.book-toc{display:none}.book-header{display:block}#menu-control:focus~main label[for=menu-control]{outline-style:auto;outline-color:currentColor;outline-color:-webkit-focus-ring-color}#menu-control:checked~main .book-menu{visibility:initial}#menu-control:checked~main .book-menu .book-menu-content{transform:translateX(16rem);box-shadow:0 0 .5rem rgba(0,0,0,.1)}#menu-control:checked~main .book-page{opacity:.25}#menu-control:checked~main .book-menu-overlay{display:block;position:absolute;top:0;bottom:0;left:0;right:0}#toc-control:focus~main label[for=toc-control]{outline-style:auto;outline-color:currentColor;outline-color:-webkit-focus-ring-color}#toc-control:checked~main .book-header aside{display:block}body[dir=rtl] #menu-control:checked~main .book-menu .book-menu-content{transform:translateX(-16rem)}}@media screen and (min-width:80rem){.book-page,.book-menu .book-menu-content,.book-toc .book-toc-content{padding:2rem 1rem}}@font-face{font-family:roboto;font-style:normal;font-weight:400;font-display:swap;src:local(""),url(fonts/roboto-v27-latin-regular.woff2)format("woff2"),url(fonts/roboto-v27-latin-regular.woff)format("woff")}@font-face{font-family:roboto;font-style:normal;font-weight:700;font-display:swap;src:local(""),url(fonts/roboto-v27-latin-700.woff2)format("woff2"),url(fonts/roboto-v27-latin-700.woff)format("woff")}@font-face{font-family:roboto mono;font-style:normal;font-weight:400;font-display:swap;src:local(""),url(fonts/roboto-mono-v13-latin-regular.woff2)format("woff2"),url(fonts/roboto-mono-v13-latin-regular.woff)format("woff")}body{font-family:roboto,sans-serif}code{font-family:roboto mono,monospace}@media print{.book-menu,.book-footer,.book-toc{display:none}.book-header,.book-header aside{display:block}main{display:block!important}}.markdown{line-height:1.6}.markdown>:first-child{margin-top:0}.markdown h1,.markdown h2,.markdown h3,.markdown h4,.markdown h5,.markdown h6{font-weight:400;line-height:1;margin-top:1.5em;margin-bottom:1rem}.markdown h1 a.anchor,.markdown h2 a.anchor,.markdown h3 a.anchor,.markdown h4 a.anchor,.markdown h5 a.anchor,.markdown h6 a.anchor{opacity:0;font-size:.75em;vertical-align:middle;text-decoration:none}.markdown h1:hover a.anchor,.markdown h1 a.anchor:focus,.markdown h2:hover a.anchor,.markdown h2 a.anchor:focus,.markdown h3:hover a.anchor,.markdown h3 a.anchor:focus,.markdown h4:hover a.anchor,.markdown h4 a.anchor:focus,.markdown h5:hover a.anchor,.markdown h5 a.anchor:focus,.markdown h6:hover a.anchor,.markdown h6 a.anchor:focus{opacity:initial}.markdown h4,.markdown h5,.markdown h6{font-weight:bolder}.markdown h5{font-size:.875em}.markdown h6{font-size:.75em}.markdown b,.markdown optgroup,.markdown strong{font-weight:bolder}.markdown a{text-decoration:none}.markdown a:hover{text-decoration:underline}.markdown a:visited{color:var(--color-visited-link)}.markdown img{max-width:100%;height:auto}.markdown code{padding:0 .25rem;background:var(--gray-200);border-radius:.25rem;font-size:.875em}.markdown pre{padding:1rem;background:var(--gray-100);border-radius:.25rem;overflow-x:auto}.markdown pre code{padding:0;background:0 0}.markdown p{word-wrap:break-word}.markdown blockquote{margin:1rem 0;padding:.5rem 1rem .5rem .75rem;border-inline-start:.25rem solid var(--gray-200);border-radius:.25rem}.markdown blockquote :first-child{margin-top:0}.markdown blockquote :last-child{margin-bottom:0}.markdown table{overflow:auto;display:block;border-spacing:0;border-collapse:collapse;margin-top:1rem;margin-bottom:1rem}.markdown table tr th,.markdown table tr td{padding:.5rem 1rem;border:1px solid var(--gray-200)}.markdown table tr:nth-child(2n){background:var(--gray-100)}.markdown hr{height:1px;border:none;background:var(--gray-200)}.markdown ul,.markdown ol{padding-inline-start:2rem}.markdown dl dt{font-weight:bolder;margin-top:1rem}.markdown dl dd{margin-inline-start:0;margin-bottom:1rem}.markdown .highlight table tr td:nth-child(1) pre{margin:0;padding-inline-end:0}.markdown .highlight table tr td:nth-child(2) pre{margin:0;padding-inline-start:0}.markdown details{padding:1rem;border:1px solid var(--gray-200);border-radius:.25rem}.markdown details summary{line-height:1;padding:1rem;margin:-1rem;cursor:pointer}.markdown details[open] summary{margin-bottom:0}.markdown figure{margin:1rem 0}.markdown figure figcaption p{margin-top:0}.markdown-inner>:first-child{margin-top:0}.markdown-inner>:last-child{margin-bottom:0}.markdown .book-expand{margin-top:1rem;margin-bottom:1rem;border:1px solid var(--gray-200);border-radius:.25rem;overflow:hidden}.markdown .book-expand .book-expand-head{background:var(--gray-100);padding:.5rem 1rem;cursor:pointer}.markdown .book-expand .book-expand-content{display:none;padding:1rem}.markdown .book-expand input[type=checkbox]:checked+.book-expand-content{display:block}.markdown .book-tabs{margin-top:1rem;margin-bottom:1rem;border:1px solid var(--gray-200);border-radius:.25rem;overflow:hidden;display:flex;flex-wrap:wrap}.markdown .book-tabs label{display:inline-block;padding:.5rem 1rem;border-bottom:1px transparent;cursor:pointer}.markdown .book-tabs .book-tabs-content{order:999;width:100%;border-top:1px solid var(--gray-100);padding:1rem;display:none}.markdown .book-tabs input[type=radio]:checked+label{border-bottom:1px solid var(--color-link)}.markdown .book-tabs input[type=radio]:checked+label+.book-tabs-content{display:block}.markdown .book-tabs input[type=radio]:focus+label{outline-style:auto;outline-color:currentColor;outline-color:-webkit-focus-ring-color}.markdown .book-columns{margin-left:-1rem;margin-right:-1rem}.markdown .book-columns>div{margin:1rem 0;min-width:10rem;padding:0 1rem}.markdown a.book-btn{display:inline-block;font-size:.875rem;color:var(--color-link);line-height:2rem;padding:0 1rem;border:1px solid var(--color-link);border-radius:.25rem;cursor:pointer}.markdown a.book-btn:hover{text-decoration:none}.markdown .book-hint.info{border-color:#6bf;background-color:rgba(102,187,255,.1)}.markdown .book-hint.warning{border-color:#fd6;background-color:rgba(255,221,102,.1)}.markdown .book-hint.danger{border-color:#f66;background-color:rgba(255,102,102,.1)} \ No newline at end of file diff --git a/resources/_gen/assets/scss/book.scss_e129fe35b8d0a70789c8a08429469073.json b/resources/_gen/assets/scss/book.scss_e129fe35b8d0a70789c8a08429469073.json new file mode 100644 index 00000000..49851cac --- /dev/null +++ b/resources/_gen/assets/scss/book.scss_e129fe35b8d0a70789c8a08429469073.json @@ -0,0 +1 @@ +{"Target":"book.min.4b35fed0bea034bbc19c89c71e14b73fb9c68cfcc586b9382adfb9b7b103ba06.css","MediaType":"text/css","Data":{"Integrity":"sha256-SzX+0L6gNLvBnInHHhS3P7nGjPzFhrk4Kt+5t7EDugY="}} \ No newline at end of file diff --git a/resources/_gen/images/HSL-model.png b/resources/_gen/images/HSL-model.png new file mode 100644 index 00000000..01d1eccf Binary files /dev/null and b/resources/_gen/images/HSL-model.png differ diff --git a/resources/_gen/images/HSV-model.png b/resources/_gen/images/HSV-model.png new file mode 100644 index 00000000..20c602c4 Binary files /dev/null and b/resources/_gen/images/HSV-model.png differ diff --git a/resources/_gen/images/SLb7y4Xj_o.png b/resources/_gen/images/SLb7y4Xj_o.png new file mode 100644 index 00000000..48bedb56 Binary files /dev/null and b/resources/_gen/images/SLb7y4Xj_o.png differ diff --git a/resources/_gen/images/XYZ-model.png b/resources/_gen/images/XYZ-model.png new file mode 100644 index 00000000..33afee22 Binary files /dev/null and b/resources/_gen/images/XYZ-model.png differ diff --git a/resources/_gen/images/engrane.png b/resources/_gen/images/engrane.png new file mode 100644 index 00000000..55dc6d9a Binary files /dev/null and b/resources/_gen/images/engrane.png differ diff --git a/resources/_gen/images/fall.png b/resources/_gen/images/fall.png new file mode 100644 index 00000000..51daa730 Binary files /dev/null and b/resources/_gen/images/fall.png differ diff --git a/resources/_gen/images/gato.jpg b/resources/_gen/images/gato.jpg new file mode 100644 index 00000000..5d3383b5 Binary files /dev/null and b/resources/_gen/images/gato.jpg differ diff --git a/resources/_gen/images/image1.png b/resources/_gen/images/image1.png new file mode 100644 index 00000000..70ea87e5 Binary files /dev/null and b/resources/_gen/images/image1.png differ diff --git a/resources/_gen/images/image10.png b/resources/_gen/images/image10.png new file mode 100644 index 00000000..953fad1b Binary files /dev/null and b/resources/_gen/images/image10.png differ diff --git a/resources/_gen/images/image2.png b/resources/_gen/images/image2.png new file mode 100644 index 00000000..534588a7 Binary files /dev/null and b/resources/_gen/images/image2.png differ diff --git a/resources/_gen/images/image3.png b/resources/_gen/images/image3.png new file mode 100644 index 00000000..49927418 Binary files /dev/null and b/resources/_gen/images/image3.png differ diff --git a/resources/_gen/images/image4.png b/resources/_gen/images/image4.png new file mode 100644 index 00000000..d658bfc6 Binary files /dev/null and b/resources/_gen/images/image4.png differ diff --git a/resources/_gen/images/image5.png b/resources/_gen/images/image5.png new file mode 100644 index 00000000..8e18b3a2 Binary files /dev/null and b/resources/_gen/images/image5.png differ diff --git a/resources/_gen/images/image6.png b/resources/_gen/images/image6.png new file mode 100644 index 00000000..3aae6f25 Binary files /dev/null and b/resources/_gen/images/image6.png differ diff --git a/resources/_gen/images/image7.png b/resources/_gen/images/image7.png new file mode 100644 index 00000000..a1bd0c32 Binary files /dev/null and b/resources/_gen/images/image7.png differ diff --git a/resources/_gen/images/image8.png b/resources/_gen/images/image8.png new file mode 100644 index 00000000..f0b3f137 Binary files /dev/null and b/resources/_gen/images/image8.png differ diff --git a/resources/_gen/images/image9.png b/resources/_gen/images/image9.png new file mode 100644 index 00000000..4731538c Binary files /dev/null and b/resources/_gen/images/image9.png differ diff --git a/resources/_gen/images/lobo.jpg b/resources/_gen/images/lobo.jpg new file mode 100644 index 00000000..03f32f58 Binary files /dev/null and b/resources/_gen/images/lobo.jpg differ