├── .gitmodules ├── CHANGELOG.md ├── LICENSE ├── README.md ├── jaggies.c ├── jaggies.h └── test ├── .gitignore ├── Makefile ├── test.c └── test.gif /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "test/tigr"] 2 | path = test/tigr 3 | url = https://github.com/erkkah/tigr 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [1.0.0] - 2020-03-03 4 | ### Fixed 5 | - Optimized range checks 6 | - Slimmer pixel setter API 7 | - Changed polygon API from end markers to explicit vertex counts 8 | ### Added 9 | - Multi-color support 10 | 11 | ## [0.0.0] - 2018-12 12 | ### Unversioned first release 13 | 14 | [1.0.0]: https://github.com/erkkah/jaggies/compare/v0.0.0...v1.0.0 15 | [0.0.0]: https://github.com/erkkah/jaggies/releases/tag/v0.0.0 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Erik Agsjö 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Jaggies - a tiny vector graphics library 2 | 3 | ```asciiart 4 | | o 5 | |,---.,---.,---..,---.,---. 6 | |,---|| || |||---'`---. 7 | `---'`---^`---|`---|``---'`---' 8 | `---'`---' 9 | ``` 10 | 11 | Jaggies is a tiny C library for drawing filled polygons 12 | and lines using only integer math, static allocations 13 | and the standard C library. 14 | 15 | This makes Jaggies well suited for running on micro 16 | controllers with limited memory. 17 | 18 | Jaggies uses a global state, which is built up by 19 | adding polygons and lines. 20 | 21 | The global state is then rendered in one continuous flow, 22 | one pixel at a time, row by row. Polygons are renderer 23 | back to front, where latest added polygons will overdraw 24 | earlier. Lines are always drawn on top. 25 | 26 | This makes it possible to draw basic vector graphics 27 | to destinations without random access frame buffer 28 | access. No frame buffer is used internally either, 29 | keeping memory requirements low. 30 | 31 | Jaggies is distributed under the MIT license. 32 | 33 | ## Getting started 34 | 35 | Basic example: 36 | 37 | ```C 38 | void setPixel(void* context, unsigned char color) { 39 | SomeBitmap* bmp = (SomeBitmap*) context; 40 | // implementation here! 41 | } 42 | 43 | SomeBitmap *bmp = createBitmapSomehow(); 44 | 45 | // Clear the global state 46 | jaggieClear(); 47 | 48 | // A square 49 | jaggiePoint square[] = { 50 | {4, 1}, 51 | 52 | {10, 10}, 53 | {100, 10}, 54 | {100, 100}, 55 | {10, 100} 56 | }; 57 | 58 | // Add a polygon 59 | jaggiePoly(square); 60 | 61 | // Render using `setPixel` above 62 | jaggieRender(150, 150, 0, setPixel, bmp); 63 | ``` 64 | 65 | The full five function API is documented in [jaggies.h](jaggies.h). 66 | 67 | Check out the example in the [test](test) folder. 68 | 69 | ![Animated demo](test/test.gif) 70 | 71 | > NOTE: The example uses the awesome [tigr](https://github.com/erkkah/tigr) library, which is included as a submodule, but Jaggies itself has no external dependencies. 72 | 73 | ## Memory requirement and other tweaks 74 | 75 | Since required structures are statically allocated, the overall memory requirement is defined by the coordinate type and the maximum number of lines and polygons in the global render state. 76 | 77 | These are set to relatively low levels (`short int` coordinates, 16 polygons and 48 lines) by default, and can be overridden by setting the `JAGGIE_INT`, `JAGGIE_MAX_POLYS` and `JAGGIE_MAX_LINES` preprocessor defines. 78 | 79 | The pixel type is `unsigned char` by default, but can be changed by setting the `JAGGIE_COLOR` define. 80 | 81 | If you are really low on memory and cycles, multi-color support can be turned off by setting the `JAGGIE_SINGLE_COLOR` define. 82 | This causes all primitives to be drawn in the same color, using the last value set by `jaggieColor()`. 83 | 84 | ## Known issues 85 | 86 | * Jaggies is designed for limited memory situations, it is not your fastest pixel pushing friend. But that's OK. 87 | 88 | * Jaggies uses a global state which makes drawing several scenes at the same time impossible. Also fine. 89 | -------------------------------------------------------------------------------- /jaggies.c: -------------------------------------------------------------------------------- 1 | #include "jaggies.h" 2 | #include 3 | 4 | typedef jaggiePoint Point; 5 | 6 | typedef struct Line { 7 | JAGGIE_INT x1, y1; 8 | JAGGIE_INT x2, y2; 9 | 10 | // Line interpolation state 11 | JAGGIE_INT x, y; 12 | JAGGIE_INT y0; 13 | JAGGIE_INT dx, dy; 14 | char sx; 15 | JAGGIE_INT err0, err; 16 | 17 | // Polygon owner, -1 for free lines. 18 | int owner; 19 | 20 | #ifndef JAGGIE_SINGLE_COLOR 21 | JAGGIE_COLOR color; 22 | #endif 23 | 24 | union { 25 | // This polygon line starts at a horizontal peak 26 | JAGGIE_INT hpeak; 27 | 28 | // The number of pixels to draw to complete this free line 29 | JAGGIE_INT lrem; 30 | }; 31 | } Line; 32 | 33 | typedef struct Poly { 34 | JAGGIE_INT inside; 35 | } Poly; 36 | 37 | static Poly polys[JAGGIE_MAX_POLYS]; 38 | static Line lines[JAGGIE_MAX_LINES]; 39 | static Line* sortedLines[JAGGIE_MAX_LINES]; 40 | static JAGGIE_COLOR color; 41 | 42 | static int polyEnd = 0; 43 | static int lineEnd = 0; 44 | static int sorted = 0; 45 | 46 | static int lineCompareY0(const void* a, const void* b) { 47 | Line* l1 = *(Line**)a; 48 | Line* l2 = *(Line**)b; 49 | 50 | JAGGIE_INT aTop = l1->y0; 51 | JAGGIE_INT bTop = l2->y0; 52 | 53 | return aTop - bTop; 54 | } 55 | 56 | static void sortLines() { 57 | if(!sorted) { 58 | qsort(sortedLines, lineEnd, sizeof(Line*), lineCompareY0); 59 | sorted = 1; 60 | } 61 | } 62 | 63 | 64 | /* 65 | 66 | !!! Must keep lines in cw order and use half 67 | open intervals for lines to make sure scanline 68 | hits at polygon vertices do not generate 69 | double "hits". 70 | 71 | And cover the case where top or bottom sharp corners 72 | get hit by a scan line. If not, this leads to fill 73 | lines bleeding to the right. 74 | 75 | */ 76 | 77 | static Line* addLinePrimitive(JAGGIE_INT x1, JAGGIE_INT y1, JAGGIE_INT x2, JAGGIE_INT y2, int owner) { 78 | if(lineEnd == JAGGIE_MAX_LINES) { 79 | return 0; 80 | } 81 | 82 | int lineID = lineEnd++; 83 | Line* line = lines + lineID; 84 | line->x1 = x1; 85 | line->y1 = y1; 86 | line->x2 = x2; 87 | line->y2 = y2; 88 | line->owner = owner; 89 | #ifndef JAGGIE_SINGLE_COLOR 90 | line->color = color; 91 | #endif 92 | 93 | // Set up int interpolation 94 | if(y1 < y2) { 95 | line->y0 = y1; 96 | 97 | line->dx = x2 - x1; 98 | line->dy = y2 - y1; 99 | } else { 100 | line->y0 = y2; 101 | 102 | line->dx = x1 - x2; 103 | line->dy = y1 - y2; 104 | } 105 | 106 | if(line->dx >= 0) { 107 | line->sx = 1; 108 | } else { 109 | line->dx = -line->dx; 110 | line->sx = -1; 111 | } 112 | 113 | line->err0 = (line->dx > line->dy) ? line->dx : -line->dy; 114 | line->err0 /= 2; 115 | 116 | line->hpeak = 0; 117 | 118 | sortedLines[lineID] = line; 119 | sorted = 0; 120 | 121 | return line; 122 | } 123 | 124 | static void setHorizontalPeak(Line* current, Line* prev) { 125 | // Assume current(x1, y1) == prev(x2, y2) 126 | 127 | int currentDir = current->y2 - current->y1; 128 | int prevDir = prev->y1 - prev->y2; 129 | if( (currentDir > 0 && prevDir > 0) || 130 | (currentDir < 0 && prevDir < 0) ) 131 | { 132 | current->hpeak = 1; 133 | } 134 | } 135 | 136 | // First point contains vertex count and if the 137 | // current segment is the last. 138 | // 139 | // To draw a 4 vertice polygon, the first point is 140 | // {4, 1} 141 | // 142 | // To draw multi segment polygons, all segments 143 | // before the last should be {x, 0}. 144 | // 145 | // Keep points in clock-wise order. 146 | // Auto closes between start and end points. 147 | JAGGIE_INT jaggiePoly(Point* points) { 148 | if(polyEnd == JAGGIE_MAX_POLYS) { 149 | return 0; 150 | } 151 | 152 | int polyID = polyEnd++; 153 | Poly* poly = polys + polyID; 154 | poly->inside = 0; 155 | 156 | Point* polyStart = points; 157 | 158 | int done = 0; 159 | while(!done) { 160 | Line* first = 0; 161 | Line* current = 0; 162 | Line* prev = 0; 163 | 164 | JAGGIE_INT count = polyStart->x; 165 | if (count < 3) { 166 | return 0; 167 | } 168 | JAGGIE_INT cutout = polyStart->y; 169 | done = !cutout; 170 | 171 | Point* p1 = polyStart + 1; 172 | Point* p2 = p1 + 1; 173 | 174 | for (JAGGIE_INT i = 0; i < count - 1; i++) { 175 | current = addLinePrimitive(p1->x, p1->y, p2->x, p2->y, polyID); 176 | if(!current) { 177 | return 0; 178 | } 179 | if(first == 0){ 180 | first = current; 181 | } else { 182 | // prev is always != 0 here 183 | setHorizontalPeak(current, prev); 184 | } 185 | prev = current; 186 | p1 = p2; 187 | p2++; 188 | } 189 | 190 | current = addLinePrimitive(p1->x, p1->y, first->x1, first->y1, polyID); 191 | if(!current){ 192 | return 0; 193 | } 194 | 195 | setHorizontalPeak(current, prev); 196 | setHorizontalPeak(first, current); 197 | 198 | polyStart = p2; 199 | } 200 | 201 | return 1; 202 | } 203 | 204 | JAGGIE_INT jaggieLine(JAGGIE_INT x1, JAGGIE_INT y1, JAGGIE_INT x2, JAGGIE_INT y2) { 205 | Line* line = addLinePrimitive(x1, y1, x2, y2, -1); 206 | return line != 0; 207 | } 208 | 209 | void jaggieColor(JAGGIE_COLOR c) { 210 | color = c; 211 | } 212 | 213 | void jaggieClear() { 214 | polyEnd = 0; 215 | lineEnd = 0; 216 | } 217 | 218 | static int doesPixelCrossLine(JAGGIE_INT x, JAGGIE_INT y, Line* l) { 219 | // Special case covers half open interval start 220 | // and horizontal peaks. 221 | if(x == l->x1 && y == l->y1) { 222 | return !l->hpeak; 223 | } 224 | 225 | // Special case for horizontal line 226 | if(l->y1 == l->y2) { 227 | return 0; 228 | } 229 | 230 | // Line in y - range? 231 | if(l->y1 < l->y2) { 232 | if(! ((y > l->y1) && (y < l->y2)) ) { 233 | return 0; 234 | } 235 | } else { 236 | if(! ((y > l->y2) && (y < l->y1)) ) { 237 | return 0; 238 | } 239 | } 240 | 241 | JAGGIE_INT hitX = -1; 242 | 243 | // Special case for vertical line 244 | if(l->x1 == l->x2) { 245 | hitX = l->x1; 246 | } else { 247 | // Line in x - range? 248 | if(l->x1 < l->x2) { 249 | if(x + 1 < l->x1) { 250 | return 0; 251 | } 252 | } else { 253 | if(x + 1 < l->x2) { 254 | return 0; 255 | } 256 | } 257 | // Integer line interpolation 258 | while(l->y < y) { 259 | JAGGIE_INT e = l->err; 260 | if(e > -l->dx) { 261 | l->err -= l->dy; 262 | l->x += l->sx; 263 | } 264 | if(e < l->dy){ 265 | l->err += l->dx; 266 | l->y++; 267 | } 268 | } 269 | 270 | hitX = l->x; 271 | } 272 | 273 | if ((x <= hitX) && (x + 1 > hitX)) { 274 | return 1; 275 | } else { 276 | return 0; 277 | } 278 | } 279 | 280 | static JAGGIE_INT rowPixelsInLine(JAGGIE_INT x, JAGGIE_INT y, Line* l) { 281 | // Horizontal line special case 282 | if(y == l->y1 && l->y1 == l->y2){ 283 | if(l->x1 < l->x2) { 284 | return (x >= l->x1) && (x <= l->x2); 285 | } else { 286 | return (x >= l->x2) && (x <= l->x1); 287 | } 288 | } 289 | 290 | JAGGIE_INT startX = 0; 291 | 292 | // Check end cases 293 | if(l->y1 < l->y2) { 294 | startX = l->x1; 295 | if(y > l->y2){ 296 | return 0; 297 | } 298 | } else { 299 | startX = l->x2; 300 | if(y > l->y1){ 301 | return 0; 302 | } 303 | } 304 | 305 | if(l->x1 < l->x2) { 306 | if(x < l->x1 || x > l->x2){ 307 | return 0; 308 | } 309 | } else { 310 | if(x < l->x2 || x > l->x1){ 311 | return 0; 312 | } 313 | } 314 | 315 | // Integer line interpolation 316 | // Work up to current line 317 | while(l->y < y) { 318 | JAGGIE_INT e = l->err; 319 | if(e > -l->dx) { 320 | l->err -= l->dy; 321 | l->x += l->sx; 322 | l->lrem++; 323 | } 324 | if(e < l->dy){ 325 | l->err += l->dx; 326 | l->y++; 327 | } 328 | } 329 | 330 | // Make sure to draw the first pixel 331 | if(startX == x && l->y0 == y) { 332 | return 1; 333 | } 334 | 335 | // Find the number of pixels to draw in this row 336 | JAGGIE_INT lx = l->x; 337 | JAGGIE_INT err = l->err; 338 | JAGGIE_INT result = 0; 339 | 340 | while(1){ 341 | if(lx == x) { 342 | l->x = lx; 343 | l->err = err; 344 | result += l->lrem; 345 | l->lrem = 0; 346 | return result == 0 ? 1 : result; 347 | } 348 | JAGGIE_INT e = err; 349 | if(e > -l->dx) { 350 | err -= l->dy; 351 | lx += l->sx; 352 | result++; 353 | } 354 | if(e < l->dy){ 355 | return 0; 356 | } 357 | } 358 | 359 | return 0; 360 | } 361 | 362 | void jaggieRender(JAGGIE_INT width, JAGGIE_INT height, JAGGIE_COLOR bg, pixelSetter setter, void* context) { 363 | if(lineEnd == 0) { 364 | return; 365 | } 366 | 367 | // Reset line states 368 | for(Line* l = lines; l < lines + lineEnd; l++) { 369 | if(l->y1 < l->y2) { 370 | l->x = l->x1; 371 | l->y = l->y1; 372 | } else { 373 | l->x = l->x2; 374 | l->y = l->y2; 375 | } 376 | l->err = l->err0; 377 | if(l->owner == -1) { 378 | l->lrem = 0; 379 | } 380 | } 381 | 382 | // Sort lines in Y direction 383 | sortLines(); 384 | Line** lStart = sortedLines; 385 | Line** lEnd = lStart; 386 | Line** lLast = lStart + lineEnd; 387 | 388 | // Render one scanline at a time 389 | for(JAGGIE_INT y = 0; y < height; y++) { 390 | 391 | if(lStart == lLast || ((*lStart)->y0 > y)) { 392 | // No lines here, just clear scanline 393 | for(JAGGIE_INT x = 0; x < width; x++) { 394 | setter(context, bg); 395 | } 396 | continue; 397 | } 398 | 399 | while(lStart != lLast && ((*lStart)->y0 + (*lStart)->dy < y)) { 400 | lStart++; 401 | } 402 | 403 | while(lEnd != lLast && (*lEnd)->y0 <= y) { 404 | lEnd++; 405 | } 406 | 407 | // Clear "inside" state 408 | for(Poly* p = polys; p < polys + polyEnd; p++) { 409 | p->inside = 0; 410 | } 411 | 412 | JAGGIE_INT inside = 0; 413 | JAGGIE_INT inLine = 0; 414 | JAGGIE_COLOR lineColor; 415 | #ifndef JAGGIE_SINGLE_COLOR 416 | lineColor = bg; 417 | #else 418 | lineColor = color; 419 | #endif 420 | 421 | for(JAGGIE_INT x = 0; x < width; x++) { 422 | 423 | for(Line** l = lStart; l < lEnd; l++) { 424 | Line* line = *l; 425 | int owner = line->owner; 426 | if(owner == -1) { 427 | if(inLine == 0) { 428 | inLine = rowPixelsInLine(x, y, line); 429 | #ifndef JAGGIE_SINGLE_COLOR 430 | lineColor = line->color; 431 | #endif 432 | } 433 | } else { 434 | if(doesPixelCrossLine(x, y, line)) { 435 | Poly* poly = polys + owner; 436 | poly->inside ^= 1; 437 | inside += poly->inside ? 1 : -1; 438 | } 439 | } 440 | } 441 | if(inLine > 0){ 442 | setter(context, lineColor); 443 | inLine--; 444 | } else { 445 | JAGGIE_COLOR polyColor = bg; 446 | if (inside) { 447 | #ifndef JAGGIE_SINGLE_COLOR 448 | int maxOwner = -1; 449 | for (maxOwner = polyEnd - 1; maxOwner >= 0; maxOwner--) { 450 | if (polys[maxOwner].inside) { 451 | break; 452 | } 453 | } 454 | for (Line** cLine = lStart; cLine < lEnd; cLine++) { 455 | int cOwner = (*cLine)->owner; 456 | if (cOwner == maxOwner) { 457 | polyColor = (*cLine)->color; 458 | break; 459 | } 460 | } 461 | #else 462 | polyColor = color; 463 | #endif 464 | } 465 | setter(context, polyColor); 466 | } 467 | } 468 | } 469 | } 470 | -------------------------------------------------------------------------------- /jaggies.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Jaggies - a tiny vector graphics library 4 | 5 | Example use: 6 | 7 | void setPixel(void* context, char color) { 8 | SomeBitmap* bmp = (SomeBitmap*) context; 9 | ... 10 | } 11 | 12 | SomeBitmap *bmp = createBitmapSomehow(); 13 | 14 | // Clear the global scene.. 15 | jaggieClear(); 16 | 17 | jaggiePoint square[] = { 18 | {4, 0}, // 4 points follow 19 | 20 | {10, 10}, 21 | {100, 10}, 22 | {100, 100}, 23 | {10, 100} 24 | }; 25 | 26 | // ..add a polygon.. 27 | jaggiePoly(square); 28 | 29 | // ..and render! 30 | jaggieRender(150, 150, 0, setPixel, bmp); 31 | 32 | */ 33 | 34 | #ifndef JAGGIE_INT 35 | #define JAGGIE_INT short 36 | #endif 37 | 38 | #ifndef JAGGIE_COLOR 39 | #define JAGGIE_COLOR unsigned char 40 | #endif 41 | 42 | #ifndef JAGGIE_MAX_POLYS 43 | #define JAGGIE_MAX_POLYS 16 44 | #endif 45 | 46 | #ifndef JAGGIE_MAX_LINES 47 | #define JAGGIE_MAX_LINES (JAGGIE_MAX_POLYS*3) 48 | #endif 49 | 50 | /* 51 | Point type 52 | */ 53 | typedef struct Point { 54 | JAGGIE_INT x, y; 55 | } jaggiePoint; 56 | 57 | 58 | /* 59 | Adds a polygon to the render state. 60 | Polygons are described by a list of points 61 | prefixed with a (count, last) point. 62 | 63 | Polygons are automatically closed, there is no need 64 | to repeat the first point. 65 | 66 | Multiple sections can optionally be added to the same 67 | polygon by setting last=0 in the prefix point. 68 | This can be used to cut a hole in a polygon. 69 | 70 | Returns zero on failure. 71 | */ 72 | JAGGIE_INT jaggiePoly(jaggiePoint* points); 73 | 74 | /* 75 | Adds a line to the render state. 76 | 77 | The start and end pixels are included in the line drawn. 78 | 79 | Returns zero on failure. 80 | */ 81 | JAGGIE_INT jaggieLine(JAGGIE_INT x1, JAGGIE_INT y1, JAGGIE_INT x2, JAGGIE_INT y2); 82 | 83 | /* 84 | Sets the render state color. 85 | */ 86 | void jaggieColor(JAGGIE_COLOR color); 87 | 88 | /* 89 | Clears the render state 90 | */ 91 | void jaggieClear(); 92 | 93 | /* 94 | Pixel setter callback. 95 | 96 | Will always be called sequentially, row by row, from top to bottom, 97 | to cover the whole frame specified by the current call to `jaggieRender`. 98 | */ 99 | typedef void(*pixelSetter)(void* context, JAGGIE_COLOR color); 100 | 101 | /* 102 | Renders the current state to a frame of size (width, height) 103 | using the provided pixel setter and background color. 104 | 105 | The generic context will be passed on to the pixel setter. 106 | */ 107 | void jaggieRender(JAGGIE_INT width, JAGGIE_INT height, JAGGIE_COLOR bg, pixelSetter, void* context); 108 | -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | test -------------------------------------------------------------------------------- /test/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS = -I.. -I./tigr -DGL_SILENCE_DEPRECATION 2 | LDFLAGS = -lm 3 | ifeq ($(OS),Windows_NT) 4 | LDFLAGS += -ld3d9 5 | else 6 | UNAME_S := $(shell uname -s) 7 | ifeq ($(UNAME_S),Darwin) 8 | LDFLAGS += -framework OpenGL -framework Cocoa 9 | else ifeq ($(UNAME_S),Linux) 10 | LDFLAGS += -lGLU -lGL -lX11 11 | endif 12 | endif 13 | 14 | all: test 15 | 16 | tigr/tigr.c: 17 | git submodule update --init 18 | 19 | test : test.c ./tigr/tigr.c ../jaggies.c 20 | gcc $^ -g -o $@ $(CFLAGS) $(LDFLAGS) 21 | -------------------------------------------------------------------------------- /test/test.c: -------------------------------------------------------------------------------- 1 | #include "tigr.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "jaggies.h" 8 | 9 | typedef struct JContext { 10 | TPixel* bmp; 11 | } JContext; 12 | 13 | TPixel colors[5] = { 14 | { 15 | 16, 16, 16, 255 16 | }, 17 | { 18 | 150, 150, 150, 255 19 | }, 20 | { 21 | 200, 55, 55, 255 22 | }, 23 | { 24 | 55, 200, 55, 255 25 | }, 26 | { 27 | 55, 55, 200, 255 28 | } 29 | }; 30 | 31 | void setPixel(void* context, JAGGIE_COLOR c) { 32 | JContext* jc = (JContext*) context; 33 | *(jc->bmp) = colors[c % 5]; 34 | jc->bmp++; 35 | } 36 | 37 | void animate(Tigr* screen, float time) { 38 | static float pos1 = 0; 39 | static float pos2 = 0; 40 | 41 | const float speed1 = (2 * M_PI) / 4; 42 | const float speed2 = (2 * M_PI) / 3; 43 | 44 | pos1 += time * speed1; 45 | pos1 = fmodf(pos1, 2 * M_PI); 46 | 47 | pos2 -= time * speed2; 48 | pos2 = fmodf(pos2, 2 * M_PI); 49 | 50 | const int outerEdges = 5; 51 | const int innerEdges = 4; 52 | jaggiePoint points[1 + outerEdges + 1 + innerEdges]; 53 | jaggiePoint* p = points; 54 | jaggieClear(); 55 | 56 | { 57 | float edgePhase = pos1; 58 | float edgeInc = (2 * M_PI) / outerEdges; 59 | 60 | p->x = outerEdges; 61 | p->y = 1; 62 | p++; 63 | 64 | for(int i = 0; i < outerEdges; i++, p++){ 65 | float x = cosf(edgePhase); 66 | float y = sinf(edgePhase); 67 | p->x = x * 90 + 100; 68 | p->y = y * 90 + 100; 69 | edgePhase += edgeInc; 70 | } 71 | } 72 | 73 | { 74 | float edgePhase = pos2; 75 | float edgeInc = (2 * M_PI) / innerEdges; 76 | p->x = innerEdges; 77 | p->y = 0; 78 | p++; 79 | 80 | for(int i = 0; i < innerEdges; i++, p++){ 81 | float x = cosf(edgePhase); 82 | float y = sinf(edgePhase); 83 | p->x = x * 95 + 100; 84 | p->y = y * 95 + 100; 85 | edgePhase += edgeInc; 86 | } 87 | } 88 | 89 | jaggieColor(1); 90 | jaggiePoly(points); 91 | 92 | jaggiePoint hbar[] = { 93 | {4, 0}, 94 | {5, 95}, 95 | {195, 95}, 96 | {195, 105}, 97 | {5, 105}, 98 | }; 99 | jaggieColor(2); 100 | jaggiePoly(hbar); 101 | 102 | jaggiePoint vbar[] = { 103 | {4, 0}, 104 | {95, 5}, 105 | {105, 5}, 106 | {105, 195}, 107 | {95, 195}, 108 | }; 109 | jaggieColor(3); 110 | jaggiePoly(vbar); 111 | 112 | 113 | int lineStart = (int)(100.f * sinf(pos2) + 100.f); 114 | int lineEnd = 199 - lineStart; 115 | 116 | jaggieColor(4); 117 | jaggieLine(10, lineStart, 189, lineEnd); 118 | 119 | jaggieColor(1); 120 | jaggieLine(0, 5, 5, 0); 121 | jaggieLine(0, 0, 5, 5); 122 | 123 | jaggieLine(7, 0, 7, 16); 124 | jaggieLine(0, 8, 16, 8); 125 | 126 | jaggieLine(5, 11, 0, 16); 127 | jaggieLine(5, 16, 0, 11); 128 | } 129 | 130 | static void renderImages(const char* prefix) { 131 | int len = strlen(prefix); 132 | char* buf = (char*) malloc(len + 10); 133 | 134 | Tigr* bmp = tigrBitmap(200, 200); 135 | float totalTime = 12; 136 | float step = 1.0/15.0; 137 | int pic = 0; 138 | for (float time = step; time < totalTime; time += step) { 139 | 140 | tigrClear(bmp, tigrRGB(0, 0, 0)); 141 | animate(bmp, step); 142 | JContext jc = { 143 | bmp->pix 144 | }; 145 | jaggieRender(200, 200, 0, setPixel, &jc); 146 | sprintf(buf, "%s_%03d.png", prefix, pic); 147 | tigrSaveImage(buf, bmp); 148 | 149 | pic++; 150 | } 151 | 152 | free(buf); 153 | tigrFree(bmp); 154 | } 155 | 156 | static void renderScreen() { 157 | Tigr *screen = tigrWindow(200, 200, "jaggies", TIGR_FIXED); 158 | 159 | int pause = 0; 160 | int markers = 0; 161 | 162 | while (!tigrClosed(screen) && !tigrKeyDown(screen, TK_ESCAPE)) 163 | { 164 | float time = tigrTime(); 165 | int key = tigrReadChar(screen); 166 | if(key == ' ') { 167 | pause ^= 1; 168 | } else if (key == 'm') { 169 | markers ^= 1; 170 | } 171 | tigrClear(screen, tigrRGB(0, 0, 0)); 172 | if(pause == 0) { 173 | animate(screen, time); 174 | } 175 | 176 | JContext jc = { 177 | screen->pix 178 | }; 179 | jaggieRender(200, 200, 0, setPixel, &jc); 180 | 181 | if(markers) { 182 | tigrPlot(screen, 0, 0, tigrRGB(0xff, 0, 0)); 183 | tigrPlot(screen, 199, 0, tigrRGB(0xff, 0, 0)); 184 | tigrPlot(screen, 199, 199, tigrRGB(0xff, 0, 0)); 185 | tigrPlot(screen, 0, 199, tigrRGB(0xff, 0, 0)); 186 | } 187 | 188 | tigrUpdate(screen); 189 | } 190 | tigrFree(screen); 191 | } 192 | 193 | int main(int argc, char** argv) { 194 | if (argc == 2) { 195 | const char* savePrefix = argv[1]; 196 | 197 | renderImages(savePrefix); 198 | } else { 199 | renderScreen(); 200 | } 201 | 202 | return 0; 203 | } 204 | 205 | -------------------------------------------------------------------------------- /test/test.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erkkah/jaggies/4554a17a9c2e7289c90e4e4ec4b6d06f0eea185e/test/test.gif --------------------------------------------------------------------------------