/* gcc -std=c99 graphics.c -lm && ./a.out | mpv --no-correct-pts --container-fps-override=30 - * gcc -std=c99 graphics.c -lm && ./a.out | ffmpeg -r 30 -i - output.mp4 */ #include #include #include #include #include inline int OK(int x) { return x == 0; } typedef uint8_t bool; #define false 0 #define true 1 typedef int32_t i32; typedef uint32_t u32; ////RGBImage//// typedef uint8_t byte; typedef uint16_t byte2; typedef uint32_t byte4; typedef struct ARGB { byte a; byte r; byte g; byte b; } ARGB; void ARGB_set(ARGB* rgb, byte4 color) { rgb->a = (0xFF000000 & color) >> 24; rgb->r = (0x00FF0000 & color) >> 16; rgb->g = (0x0000FF00 & color) >> 8; rgb->b = (0x000000FF & color); } void ARGB_merge(ARGB* bottom, ARGB top) { bottom->a = 255; double perc = top.a / 255.0; bottom->r = (bottom->r * (1.0 - perc)) + (top.r * perc); bottom->g = (bottom->g * (1.0 - perc)) + (top.g * perc); bottom->b = (bottom->b * (1.0 - perc)) + (top.b * perc); } typedef struct RGBImage { u32 width; u32 height; ARGB* img; } RGBImage; RGBImage RGBImage_new(u32 width, u32 height) { return (RGBImage){ .width = width, .height = height, .img = malloc(sizeof(ARGB) * width * height) }; } void RGBImage_free(RGBImage img) { free(img.img); } void RGBImage_delete(RGBImage* img) { RGBImage_free(*img); img->width = img->height = 0; img->img = NULL; } ARGB* RGBImage_at(RGBImage img, int row, int col) { return img.img + (row % img.height) * img.width + col % img.width; } int ppm6_write(FILE* f, RGBImage img) { fprintf(f, "P6\n%d %d\n255\n", img.width, img.height); size_t size = img.width * img.height; for (size_t i = 0; i < size; ++i) { fputc(img.img[i].r, f); fputc(img.img[i].g, f); fputc(img.img[i].b, f); } return fflush(f); } RGBImage ppm_read(FILE* f) { u32 type, width, height, colors; fscanf(f, "P%d\n%d %d\n%d\n", &type, &width, &height, &colors); RGBImage img = RGBImage_new(width, height); ARGB* pixel = img.img; for (size_t i = img.width * img.height; i > 0; --i, ++pixel) { if (type == 3) { pixel->a = 0xFF; fscanf(f, "%d %d %d ", &pixel->r, &pixel->g, &pixel->b); } } return img; } ////Animation//// struct AnimationEventNode; typedef struct AnimationEventNode AnimationEventNode; typedef struct Animation { u32 width; u32 height; u32 frameCount; u32 duplicateFrames; RGBImage frameBuffer; AnimationEventNode* events; } Animation; typedef ARGB (*PixelRenderer)(const Animation* anim, u32 currentFrame, ARGB pixel, u32 row, u32 col, void* priv); struct AnimationEventNode { AnimationEventNode* next; u32 startFrame; u32 endFrame; PixelRenderer renderCallback; void* privateData; }; AnimationEventNode* AnimationEventNode_new(u32 startFrame, u32 endFrame, PixelRenderer renderCallback, void* privateData) { AnimationEventNode* aen = (AnimationEventNode*)malloc(sizeof(AnimationEventNode)); aen->next = NULL; aen->startFrame = startFrame; aen->endFrame = endFrame; aen->renderCallback = renderCallback; aen->privateData = privateData; return aen; } Animation Animation_new(u32 width, u32 height, u32 frameCount) { return (Animation){ .width = width, .height = height, .frameCount = frameCount, .duplicateFrames = 0, .frameBuffer = RGBImage_new(width, height), .events = NULL, }; } void Animation_free(Animation anim) { RGBImage_free(anim.frameBuffer); while (anim.events != NULL) { AnimationEventNode* toFree = anim.events; anim.events = anim.events->next; free(toFree); } } void Animation_delete(Animation* anim) { Animation_free(*anim); anim->width = anim->height = anim->frameCount = 0; RGBImage_delete(&anim->frameBuffer); } void Animation_pushEvent(Animation* anim, u32 startFrame, u32 endFrame, PixelRenderer renderer, void* privateData) { if (anim->events == NULL) { anim->events = AnimationEventNode_new(startFrame, endFrame, renderer, privateData); return; } AnimationEventNode* prevNode = NULL, *currNode = anim->events; while (currNode != NULL) { if (startFrame < currNode->startFrame) { prevNode->next = AnimationEventNode_new(startFrame, endFrame, renderer, privateData); prevNode->next->next = currNode; return; } prevNode = currNode; currNode = currNode->next; } prevNode->next = AnimationEventNode_new(startFrame, endFrame, renderer, privateData); } void Animation_render(Animation* anim) { for (int currFrame = 0; currFrame < anim->frameCount; ++currFrame) { if (anim->events != NULL) for (int r = 0; r < anim->frameBuffer.width; ++r) for (int c = 0; c < anim->frameBuffer.height; ++c) { for (AnimationEventNode* aen = anim->events; aen != NULL && aen->startFrame <= currFrame; aen = aen->next) { ARGB* pixel = RGBImage_at(anim->frameBuffer, r, c); ARGB newPixel = aen->renderCallback(anim, currFrame, *pixel, r, c, aen->privateData); ARGB_merge(pixel, newPixel); } AnimationEventNode* prevNode = NULL, *currNode = anim->events; while (currNode != NULL && currNode->startFrame <= currFrame) { if (currNode->endFrame <= currFrame) { if (prevNode != NULL) prevNode->next = currNode->next; else anim->events = NULL; free(currNode); } else { prevNode = currNode; currNode = currNode->next; } } } ppm6_write(stdout, anim->frameBuffer); for (u32 duplicates = 0; duplicates < anim->duplicateFrames; ++duplicates) ppm6_write(stdout, anim->frameBuffer); } } ////main//// struct CheckerPattern { u32 squareSize; byte4 colorA; byte4 colorB; }; ARGB CheckerPattern(const Animation* anim, u32 frameIndex, ARGB pixel, u32 r, u32 c, void* priv) { struct CheckerPattern chp = *(struct CheckerPattern*)priv; r /= chp.squareSize; c /= chp.squareSize; if ((r + c) % 2 == 0) { ARGB_set(&pixel, 0xFF000000); } else { ARGB_set(&pixel, 0xFFFF00FF); } return pixel; } struct SquareSettings { u32 width; u32 height; byte4 color; }; ARGB Square(const Animation* anim, u32 frameIndex, ARGB pixel, u32 r, u32 c, void* priv) { struct SquareSettings* set = (struct SquareSettings*)priv; if (0 <= r && r <= set->width && 0 <= c && c <= set->height) { ARGB_set(&pixel, set->color); } return pixel; } struct Image { RGBImage img; double zoom; bool noRepeat; }; ARGB Image(const Animation* anim, u32 frameIndex, ARGB pixel, u32 r, u32 c, void* priv) { struct Image img = *(struct Image*)priv; if (img.noRepeat == true && (img.img.width * img.zoom <= c || img.img.height * img.zoom <= r)) { return pixel; } return *RGBImage_at(*(RGBImage*)priv, r / img.zoom, c / img.zoom); } struct Line { double a; double b; double c; double width; byte4 color; }; ARGB Line(const Animation* anim, u32 frameIndex, ARGB pixel, u32 r, u32 c, void* priv) { struct Line l = *(struct Line*)priv; if (fabs(l.a * c + l.b * r + l.c) / sqrt(l.a * l.a + l.b * l.b) < l.width) { ARGB_set(&pixel, l.color); } return pixel; } #define NUM_INTERPOLATE(a, b, perc) (a * (1.0 - (perc)) + b * (perc)) byte4 colorInterpolate(byte4 ina, byte4 inb, double percentage) { ARGB a, b; ARGB_set(&a, ina); ARGB_set(&b, inb); a.a = NUM_INTERPOLATE(a.a, b.a, percentage); a.r = NUM_INTERPOLATE(a.r, b.r, percentage); a.g = NUM_INTERPOLATE(a.g, b.g, percentage); a.b = NUM_INTERPOLATE(a.b, b.b, percentage); return a.a << 24 | a.r << 16 | a.g << 8 | a.b; } void LineInterpolate(void* valueLine, const void* initialLine, const void* finalLine, double percentage) { struct Line* value = valueLine; const struct Line* initial = initialLine, *final = finalLine; value->a = NUM_INTERPOLATE(initial->a, final->a, percentage); value->b = NUM_INTERPOLATE(initial->b, final->b, percentage); value->c = NUM_INTERPOLATE(initial->c, final->c, percentage); value->width = NUM_INTERPOLATE(initial->width, final->width, percentage); value->color = colorInterpolate(initial->color, final->color, percentage); } struct MoveLinear { PixelRenderer callback; void* priv; u32 startRow; u32 startCol; float dRow; float dCol; bool wrapCoordinates; }; ARGB MoveLinear(const Animation* anim, u32 frameIndex, ARGB pixel, u32 r, u32 c, void* priv) { struct MoveLinear ml = *(struct MoveLinear*)priv; r = (r - ml.startRow - (u32)(frameIndex * ml.dRow)); c = (c - ml.startCol - (u32)(frameIndex * ml.dCol)); if (ml.wrapCoordinates == true) { r %= anim->width; c %= anim->height; } return ml.callback(anim, frameIndex, pixel, r, c, ml.priv); } struct MoveRotate { PixelRenderer callback; void* priv; float radius; float theta; }; ARGB MoveRotate(const Animation* anim, u32 frameIndex, ARGB pixel, u32 r, u32 c, void* priv) { struct MoveRotate mr = *(struct MoveRotate*)priv; r = (r + (u32)(mr.radius * cos(mr.theta * frameIndex))) % anim->width; c = (c + (u32)(mr.radius * sin(mr.theta * frameIndex))) % anim->height; return mr.callback(anim, frameIndex, pixel, r, c, mr.priv); } struct Interpolate { PixelRenderer callback; void* priv; u32 startFrame; u32 endFrame; void* value; void* initialState; void* finalState; void (*interpolate)(void* value, const void* initialState, const void* finalState, double percentage); }; ARGB Interpolate(const Animation* anim, u32 frameIndex, ARGB pixel, u32 r, u32 c, void* priv) { struct Interpolate in = *(struct Interpolate*)priv; double perc = (double)(frameIndex - in.startFrame) / (in.endFrame - in.startFrame); if (perc < 0.0) perc = 0.0; if (perc > 1.0) perc = 1.0; in.interpolate(in.value, in.initialState, in.finalState, perc); return in.callback(anim, frameIndex, pixel, r, c, in.priv); } int main() { Animation anim = Animation_new(512, 512, 120); // Background struct CheckerPattern chp1 = { .squareSize = 32, .colorA = 0xFF000000, .colorB = 0xFFFF00FF, }; struct MoveLinear ml1 = { .startRow = 0, .startCol = 0, .dRow = -1, .dCol = -1, .callback = CheckerPattern, .priv = &chp1, }; Animation_pushEvent(&anim, 0, 120, MoveLinear, &ml1); // Image FILE* fbclc = fopen("bclc.ppm", "r"); struct Image bclc; struct MoveLinear ml10; if (fbclc != NULL) { bclc.img = ppm_read(fbclc); bclc.noRepeat = true; bclc.zoom = 0.4; ml10 = (struct MoveLinear){ .startRow = 0, .startCol = 0, .dRow = 0.5, .dCol = 0.5, .callback = Image, .priv = &bclc, }; Animation_pushEvent(&anim, 0, 100, MoveLinear, &ml10); } // Line struct Line l1start = { .a = -8, .b = 6, .c = -128, .width = 0.5, .color = 0xFF50F0F5 }; struct Line l1end = { .a = 8, .b = -4, .c = 0, .width = 10, .color = 0xFFF550F0 }; struct Line l1; struct Interpolate i1 = { .startFrame = 0, .endFrame = 120, .value = &l1, .initialState = &l1start, .finalState = &l1end, .interpolate = LineInterpolate, .callback = Line, .priv = &l1, }; Animation_pushEvent(&anim, 0, 120, Interpolate, &i1); // Yellow square struct SquareSettings s1 = { .width = 20, .height = 20, .color = 0xFFFFFF00, }; struct MoveRotate mr1 = { .radius = 20, .theta = 3.14159 / 20, .callback = Square, .priv = &s1, }; struct MoveLinear ml3 = { .startRow = 0, .startCol = 0, .dRow = -3, .dCol = 3, .callback = MoveRotate, .priv = &mr1, }; Animation_pushEvent(&anim, 0, 80, MoveLinear, &ml3); // Yellow square struct SquareSettings s4 = { .width = 20, .height = 20, .color = 0xFFFFFFFF, }; struct MoveLinear ml4 = { .startRow = 40, .startCol = 40, .dRow = 2, .dCol = 2, .wrapCoordinates = true, .callback = Square, .priv = &s4, }; Animation_pushEvent(&anim, 0, 80, MoveLinear, &ml4); // Green square struct SquareSettings s2 = { .width = 35, .height = 50, .color = 0xA009FAA5, }; struct MoveLinear ml2 = { .startRow = 20, .startCol = 5, .dRow = -4, .dCol = -2, .wrapCoordinates = true, .callback = Square, .priv = &s2, }; Animation_pushEvent(&anim, 30, 110, MoveLinear, &ml2); Animation_render(&anim); fclose(fbclc); RGBImage_delete(&bclc.img); Animation_delete(&anim); return 0; }