{"id":26213313,"url":"https://github.com/douglau/footile","last_synced_at":"2026-03-04T09:31:05.630Z","repository":{"id":57631406,"uuid":"105448421","full_name":"DougLau/footile","owner":"DougLau","description":"A 2D vector graphics library written in Rust","archived":false,"fork":false,"pushed_at":"2025-04-06T15:29:34.000Z","size":364,"stargazers_count":40,"open_issues_count":7,"forks_count":4,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-04-15T16:59:11.023Z","etag":null,"topics":["2d","graphics","rust","vector"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/DougLau.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE-APACHE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-10-01T14:34:20.000Z","updated_at":"2025-04-06T15:29:38.000Z","dependencies_parsed_at":"2025-04-06T16:25:06.593Z","dependency_job_id":"def5ba55-d306-4800-afa0-0e07599474fd","html_url":"https://github.com/DougLau/footile","commit_stats":null,"previous_names":[],"tags_count":19,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DougLau%2Ffootile","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DougLau%2Ffootile/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DougLau%2Ffootile/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DougLau%2Ffootile/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/DougLau","download_url":"https://codeload.github.com/DougLau/footile/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249116231,"owners_count":21215142,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["2d","graphics","rust","vector"],"created_at":"2025-03-12T09:18:40.003Z","updated_at":"2026-03-04T09:31:05.360Z","avatar_url":"https://github.com/DougLau.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# footile\n\nA 2D vector graphics library written in Rust.  It uses [pix] for the underlying\nraster images.\n\nSee the [documentation] for examples and API usage.\n\n[documentation]: https://docs.rs/footile\n[pix]: https://docs.rs/pix\n\n## Rasterizing: Bird's Eye View\n\nThere is nothing novel here — this is merely a guide to the code.\n\nWe have a 2D *path* made up of lines, bézier splines, arcs, etc., and we want to\nmake a high-quality raster image out of it.  But how?\n\n### Modules\n\n* `path`: Defines path operations and `Path2D`s\n* `plotter`: Defines `Plotter` struct and flattens curves\n* `stroker`: Creates *stroked* paths for plotter\n* `fixed`: Defines `Fixed` struct used by `fig` module\n* `fig`: Rasterizes paths\n\n### Curve Flattening\n\n*Flattening* refers to approximating a curve with a series of line segments.\n\nWe use the recursive algorithm described by De Casteljau.  There might be\nopportunities for optimization here if we ever determine this is a bottleneck.\nOne other thing to note: this method could cause a stack overflow with the wrong\ninput data.\n\nOnce complete, we have a series of line segments forming one or more closed\npolygons.\n\n### Sorting Vertices\n\nFor the next step, we create a sorted list of the vertices in (Y, X) order.\nThis is needed because we will *scan* the polygon onto a grid one row at a time.\n\nEvery path has a **winding order**: either *clockwise* or the other direction,\ntypically called *counter-* or *anti-clockwise*.  Let's avoid that debate by\ncalling it *widdershins*, since clocks rarely go backwards.\n\nThe first vertex must be on the outside of the path, so we can check the angle\nbetween its neighbors to determine the winding order.\n\n### Active Edges\n\nAs rows are scanned from top to bottom, we keep track of a list of *active\nedges*.  If an edge crosses the current row, it is added to the list, otherwise,\nit is removed.  Since horizontal edges cannot *cross* a row, they can safely be\nignored.\n\nFor each row, vertices from the list are compared to its top and bottom.  If\nthe new vertex is above the bottom, one or more edges are *added*.  When the\nnew vertex is above the top, existing edges are *removed*.\n\n```bob\n           v0\n           /\\\n          /  \\ (a)\n         /    \\      v2\n    (b) /      +-----+\n       /      v1      \\\n      /                \\ (c)\n     /                  \\\n    +--------------------+\n     v3                  v4\n```\n\nExample:\n* Starting with `v0`, add edges `(a)` and `(b)`\n* Scan until the bottom of the current row is below `v1` / `v2`\n* Add edge `(c)`\n* Scan until the row top is below `v1`\n* Remove edge `(a)`\n* Scan until the row top is below `v3` / `v4`\n* Remove edges `(b)` and `(c)`\n\n### Signed Area\n\nThe active edges are used to find the *signed area* at any point.  Count the\nnumber of edge crossings needed to reach the exterior of the path.  For example:\n\n```bob\n    +------+\n    |      |\n    |  +1  |\n    |      |\n    +------+\n```\n\nA self-intersecting polygon looks like this:\n\n```bob\n    +-----------------+\n    |                 |\n    |      +------+   |\n    |      |       \\ /\n    |  +1  |  +2    X\n    |      |       / \\\n    |      +------+   |\n    |                 |\n    +-----------------+\n```\n\nWhat about *sub-paths* with opposite winding order?  In that case, subtract\ninstead of adding:\n\n```bob\n    +----------------+\n    |      +-----+   |\n    |      |     |   |\n    |  +1  |  0  |   |\n    |      |     |   |\n    |      +-----+   |\n    |        \u003c--     |\n    |                |\n    +----------------+\n          --\u003e\n```\n\n### Scanning Rows\n\nWhen scanning a row, the signed area is sampled for each pixel.  The direction\nof each edge from top to bottom determines whether it adds to or subtracts from\nthe area.  In the normal winding order, it adds to the area; otherwise, it\nsubtracts.\n\nThis row is 4 pixels wide:\n\n```bob\n      -  -  - | -  -  -   -  -  - | -  -  -\n    |         |         |         |         |\n              | +1                | -1\n    |         |         |         |         |\n              |                   |      \n    | -  -  - | -  -  - | -  -  - | -  -  - |\n          0         1         1         0\n```\n\nThe *cumulative sum* of these values is the signed area of each pixel.\n\n#### Anti-Aliasing\n\nSometimes edges don't fall on pixel boundaries.  In this case, the trick is to\nuse fractional numbers for anti-aliasing.\n\n```bob\n      -  -  - | -  |  -   -  -  -   -  -  -\n    |         |    |    |         |         |\n              | +1 | -½   -½\n    |         |    |    |         |         |\n              |    |                     \n    | -  -  - | -  |  - | -  -  - | -  -  - |\n          0         ½         0         0\n```\n\nNotice how the remainder of the second edge coverage is added to the pixel to\nthe right (third pixel).  This is necessary to keep the cumulative sum correct.\n\n```bob\n      -  -  - | -  \\  -   -  -  -   -  -  -\n    |         |     \\   |         |         |\n              | +1   \\-¼  -¾\n    |         |       \\ |         |         |\n              |        \\\n    | -  -  - | -  -  - \\ -  -  - | -  -  - |\n          0         ¾         0         0\n```\n\n### Compositing\n\nThe signed area buffer can be composited with a raster, using a source color.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdouglau%2Ffootile","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdouglau%2Ffootile","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdouglau%2Ffootile/lists"}