00. Proposal and Literature Research
                     
                     
                    
                        In the beginning, when I found Daniel Shiffman's new
                        coding challenge about stippling, I thought of
                        Pointillism, a painting technique pioneered by French
                        artist Georges Seurat during the Post-Impressionist
                        period. Therefore, my initial project proposal was to
                        extend the stippling project into a Pointillism filter
                        app—just applying the color method Seurat used in his
                        works. However, after a deeper dive into some papers
                        about digital Pointillism, I realized that stippling and
                        Pointillism are totally different painting methods, and
                        therefore the digital methods to generate them with a
                        computer are also totally different. The methods
                        computer scientists use to generate Pointillism-style
                        images are too complex for me and for this course
                        project, so I decided to focus only on stippling.
                    
                
                
                    
                        01. Build the Web App Framework and First Try with
                        Delaunator
                    
                     
                    
                        Initially, I wanted to create my project as a
                        single-page web app that allows users to upload images
                        and get filtered stippling images with a control panel
                        to adjust some parameters. I quickly made this with
                        Next.js. For manipulating pixels, I chose p5.js, which
                        I'm familiar with and is popular in the web development
                        field. For calculating Delaunay triangles based on a
                        group of points, I chose Mapbox's Delaunator because it
                        performs the fastest in comparison.
                    
                    
                        The first step was to generate a group of random points
                        from the input image by setting a brightness threshold
                        for each pixel. Then, I used the Delaunator library's
                        function to easily get the Delaunay triangles.
                    
                    
                        result was quite good, and it really ran fast as
                        expected!
                    
                
                
                    
                        02. Calculate Voronoi Diagram Based on Delaunay
                        Triangles
                    
                     
                    
                        Compared with d3-delaunay, which was used by Daniel
                        Shiffman in his project, Mapbox's Delaunator is a
                        relatively low-level library without an API to generate
                        Voronoi diagrams directly. Therefore, I decided to code
                        the algorithm myself.
                    
                    
                        The Vertices of Voronoi polygons are just the
                        circumcenters of Delaunay triangles. Using the formula
                        on this website: [Circumcenter of a
                        Triangle](https://byjus.com/maths/circumcenter-of-a-triangle/),
                        I completed the calculateVoronoi function.
                    
                    The result was good as follows.
                
                
                    
                        03. Optimize Points Distribution with Lloyd's Algorithm
                    
                    
                        The most important part of the paper
                        Weighted Voronoi Stippling is about Lloyd's
                        algorithm. The basic idea is to calculate the centroids
                        of each Voronoi cell or polygon and move the
                        corresponding point to its location, then generate a new
                        Voronoi diagram and repeat this process until a stable
                        point distribution is achieved, resulting in the final
                        stippling image.
                    
                    
                        To implement this in code, I got help from
                        Paul Bourke's website
                        on how to calculate the centroid coordinate of a
                        polygon.
                    
                     
                    
                        To calculate the weights of each pixel based on its
                        brightness, I referred to
                        W3C's definition of Relative Luminance: "For the sRGB color space, the relative luminance of
                        a color is defined as L = 0.2126 * R + 0.7152 * G +
                        0.0722 * B".
                    
                    
                        With the knowledge above, I finished the updatePoints
                        function. The final program could run, but it ran very
                        slowly, which I thought might be related to some
                        performance or compatibility issues with p5.js. It might
                        only perform better on its official web editor. So, I
                        migrated my algorithm to a p5.js web editor sketch. The
                        final result was relatively satisfactory.
                    
                     
                    
                        The face part of this portrait is not good, which I
                        think is because of insufficient grayscale contrast. An
                        image with stronger grayscale contrast may perform
                        better.
                    
                     
                    Yes it is! Bye! :)