Last active
July 23, 2023 17:00
-
-
Save henkman/5b9729ebb17ccdba38c8439163f3fa56 to your computer and use it in GitHub Desktop.
img slide window with gif support made with raylib
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// gcc -o imgslide.exe imgslide.c -lraylib -Lraylib -lgdi32 -lwinmm -lgif -Lgiflib | |
#include <stdint.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include "giflib/gif_lib.h" | |
#include "raylib/raylib.h" | |
static size_t nextPowerOfTwo(size_t v) { | |
v--; | |
v |= v >> 1; | |
v |= v >> 2; | |
v |= v >> 4; | |
v |= v >> 8; | |
v |= v >> 16; | |
v++; | |
return v; | |
} | |
typedef struct { | |
size_t count, capacity; | |
char **strings; | |
} Strings; | |
static void StringsInit(Strings *self) { | |
self->count = 0; | |
self->capacity = 2; | |
self->strings = malloc(sizeof(char *) * self->capacity); | |
} | |
static void StringsAppend(Strings *self, const char *str) { | |
if (self->count == self->capacity) { | |
self->capacity = nextPowerOfTwo(self->count + 1); | |
self->strings = realloc(self->strings, sizeof(char *) * self->capacity); | |
} | |
size_t n = strlen(str); | |
char *nstr = malloc(sizeof(char) * (n + 1)); | |
memcpy(nstr, str, n); | |
nstr[n] = 0; | |
self->strings[self->count++] = nstr; | |
} | |
typedef struct { | |
float delay; | |
float counter; | |
} Timer; | |
static void TimerReset(Timer *t, float delay) { | |
t->delay = delay; | |
t->counter = 0; | |
} | |
static void TimerUpdate(Timer *t, float delta) { t->counter += delta; } | |
static bool TimerCheck(Timer *t) { | |
bool reset = t->counter > t->delay; | |
if (reset) | |
t->counter -= t->delay; | |
return reset; | |
} | |
typedef struct { | |
size_t frames; | |
float *delays; | |
Texture2D *textures; | |
int width, height; | |
Timer timer; | |
size_t current; | |
} Animation; | |
static void AnimationFree(Animation *anim) { | |
free(anim->delays); | |
for (size_t i = 0; i < anim->frames; i++) { | |
UnloadTexture(anim->textures[i]); | |
} | |
free(anim->textures); | |
} | |
typedef struct { | |
Strings files; | |
Timer nextImage; | |
size_t current; | |
bool paused; | |
bool animated; | |
Texture2D basic; | |
Animation anim; | |
} ImgSlide; | |
static void FindGraphicsControlBlock(GraphicsControlBlock *gcb, | |
SavedImage *image) { | |
gcb->DelayTime = 0; | |
gcb->TransparentColor = NO_TRANSPARENT_COLOR; | |
ExtensionBlock *end = image->ExtensionBlocks + image->ExtensionBlockCount; | |
for (ExtensionBlock *eb = image->ExtensionBlocks; eb != end; eb++) { | |
if (eb->Function == GRAPHICS_EXT_FUNC_CODE) { | |
DGifExtensionToGCB(eb->ByteCount, eb->Bytes, gcb); | |
break; | |
} | |
} | |
} | |
typedef struct { | |
int x, y, width, height; | |
} RectI; | |
typedef struct { | |
unsigned char *data; | |
int x, y; | |
int width, height; | |
int stride; | |
} Canvas; | |
static void CanvasInit(Canvas *canvas, int width, int height) { | |
canvas->x = canvas->y = 0; | |
canvas->width = width; | |
canvas->height = height; | |
canvas->stride = width * 4; | |
canvas->data = malloc(width * height * 4 * sizeof(unsigned char)); | |
} | |
static size_t CanvasPixOffset(Canvas *canvas, size_t x, size_t y) { | |
return (y + canvas->y) * canvas->stride + (x + canvas->x) * 4; | |
} | |
static void CanvasSub(Canvas *canvas, RectI *rect, Canvas *sub) { | |
sub->data = canvas->data; | |
sub->x = rect->x; | |
sub->y = rect->y; | |
sub->width = rect->width; | |
sub->height = rect->height; | |
sub->stride = canvas->stride; | |
} | |
static void CanvasFree(Canvas *canvas) { free(canvas->data); } | |
static void PalettedToRGBA(SavedImage *frame, Canvas *canvas, | |
ColorMapObject *colorTable, int transparent) { | |
for (size_t y = 0; y < canvas->height; y++) | |
for (size_t x = 0; x < canvas->width; x++) { | |
size_t ro = (y * canvas->width) + x; | |
unsigned char pixelIndex = frame->RasterBits[ro]; | |
size_t o = CanvasPixOffset(canvas, x, y); | |
if (pixelIndex == transparent) { | |
canvas->data[o + 0] = 0; | |
canvas->data[o + 1] = 0; | |
canvas->data[o + 2] = 0; | |
canvas->data[o + 3] = 0; | |
} else { | |
GifColorType rgb = colorTable->Colors[pixelIndex]; | |
canvas->data[o + 0] = rgb.Red; | |
canvas->data[o + 1] = rgb.Green; | |
canvas->data[o + 2] = rgb.Blue; | |
canvas->data[o + 3] = 0xFF; | |
} | |
} | |
} | |
static void BlendPalettedToRGBA(SavedImage *frame, Canvas *canvas, | |
ColorMapObject *colorTable, int transparent) { | |
for (size_t y = 0; y < canvas->height; y++) | |
for (size_t x = 0; x < canvas->width; x++) { | |
size_t ro = (y * canvas->width) + x; | |
unsigned char pixelIndex = frame->RasterBits[ro]; | |
if (pixelIndex == transparent) | |
continue; | |
GifColorType rgb = colorTable->Colors[pixelIndex]; | |
size_t o = CanvasPixOffset(canvas, x, y); | |
canvas->data[o + 0] = rgb.Red; | |
canvas->data[o + 1] = rgb.Green; | |
canvas->data[o + 2] = rgb.Blue; | |
canvas->data[o + 3] = 0xFF; | |
} | |
} | |
static void ImgSlideLoadImage(ImgSlide *is, const char *file) { | |
const char *ext = GetFileExtension(file); | |
if (ext == NULL) | |
return; | |
// printf("Loading %s\n", is->files.strings[is->current]); | |
if (memcmp(".gif", ext, sizeof(char) * 4) == 0) { | |
Animation anim = {0}; | |
int error = 0; | |
GifFileType *gif = DGifOpenFileName(file, &error); | |
if (gif == NULL || DGifSlurp(gif) != GIF_OK) | |
return; | |
anim.width = gif->SWidth; | |
anim.height = gif->SHeight; | |
anim.frames = gif->ImageCount; | |
anim.delays = malloc(sizeof(float) * anim.frames); | |
ColorMapObject *globalTable = gif->SColorMap; | |
anim.textures = malloc(sizeof(Texture2D) * anim.frames); | |
Canvas canvas; | |
CanvasInit(&canvas, anim.width, anim.height); | |
// printf("image %dx%d %d\n", anim.width, anim.height, anim.frames); | |
{ | |
SavedImage *frame = &gif->SavedImages[0]; | |
GraphicsControlBlock gcb; | |
FindGraphicsControlBlock(&gcb, frame); | |
ColorMapObject *colorTable = | |
frame->ImageDesc.ColorMap ? frame->ImageDesc.ColorMap : globalTable; | |
PalettedToRGBA(frame, &canvas, colorTable, gcb.TransparentColor); | |
Image image = (Image){canvas.data, anim.width, anim.height, 1, | |
PIXELFORMAT_UNCOMPRESSED_R8G8B8A8}; | |
anim.textures[0] = LoadTextureFromImage(image); | |
anim.delays[0] = gcb.DelayTime ? ((float)gcb.DelayTime) / 100.f : 0.01f; | |
} | |
int noDisposeIndex = -1; | |
for (int i = 1; i < gif->ImageCount; i++) { | |
SavedImage *frame = &gif->SavedImages[i]; | |
GifImageDesc desc = frame->ImageDesc; | |
RectI rect = {desc.Left, desc.Top, desc.Width, desc.Height}; | |
// printf("frame %d %dx%d %dx%d\n", i, rect.x, rect.y, rect.width, | |
// rect.height); | |
Canvas sub; | |
CanvasSub(&canvas, &rect, &sub); | |
GraphicsControlBlock gcb = {0}; | |
FindGraphicsControlBlock(&gcb, frame); | |
switch (gcb.DisposalMode) { | |
case DISPOSAL_UNSPECIFIED: | |
noDisposeIndex = i - 1; | |
break; | |
case DISPOSE_PREVIOUS: | |
if (noDisposeIndex >= 0) { | |
SavedImage *frame = &gif->SavedImages[noDisposeIndex]; | |
GifImageDesc desc = frame->ImageDesc; | |
RectI rect = {desc.Left, desc.Top, desc.Width, desc.Height}; | |
Canvas sub; | |
CanvasSub(&canvas, &rect, &sub); | |
GraphicsControlBlock gcb = {0}; | |
FindGraphicsControlBlock(&gcb, frame); | |
ColorMapObject *colorTable = frame->ImageDesc.ColorMap | |
? frame->ImageDesc.ColorMap | |
: globalTable; | |
PalettedToRGBA(frame, &sub, colorTable, gcb.TransparentColor); | |
} | |
break; | |
} | |
ColorMapObject *colorTable = | |
frame->ImageDesc.ColorMap ? frame->ImageDesc.ColorMap : globalTable; | |
BlendPalettedToRGBA(frame, &sub, colorTable, gcb.TransparentColor); | |
Image image = (Image){sub.data, anim.width, anim.height, 1, | |
PIXELFORMAT_UNCOMPRESSED_R8G8B8A8}; | |
anim.textures[i] = LoadTextureFromImage(image); | |
anim.delays[i] = gcb.DelayTime ? ((float)gcb.DelayTime) / 100.f : 0.01f; | |
} | |
CanvasFree(&canvas); | |
DGifCloseFile(gif, &error); | |
TimerReset(&anim.timer, anim.delays[0]); | |
is->anim = anim; | |
is->animated = true; | |
} else if (memcmp(".jpg", ext, sizeof(char) * 4) == 0 || | |
memcmp(".jpeg", ext, sizeof(char) * 5) == 0 || | |
memcmp(".png", ext, sizeof(char) * 4) == 0) { | |
Image image = LoadImage(file); | |
Texture2D tex = LoadTextureFromImage(image); | |
UnloadImage(image); | |
is->basic = tex; | |
is->animated = false; | |
} | |
} | |
static void ImgSlideLoadImageDirectory(ImgSlide *is, const char *dir) { | |
FilePathList files = LoadDirectoryFiles(dir); | |
for (size_t i = 0; i < files.count; i++) { | |
const char *file = files.paths[i]; | |
if (DirectoryExists(file)) | |
ImgSlideLoadImageDirectory(is, file); | |
else if (FileExists(file)) | |
StringsAppend(&is->files, file); | |
} | |
UnloadDirectoryFiles(files); | |
} | |
static void ImgSlideNextImage(ImgSlide *is) { | |
size_t r = 0; | |
if (is->files.count > 1) { | |
do { | |
r = GetRandomValue(0, is->files.count - 1); | |
} while (is->current == r); | |
// printf("Unloading %s\n", is->files.strings[is->current]); | |
if (is->animated) | |
AnimationFree(&is->anim); | |
else | |
UnloadTexture(is->basic); | |
} | |
is->current = r; | |
ImgSlideLoadImage(is, is->files.strings[r]); | |
} | |
static void DrawTextureHandleLarge(Texture2D tex) { | |
Rectangle source = {0, 0, tex.width, tex.height}; | |
Rectangle dest = {0, 0, tex.width, tex.height}; | |
int width = GetScreenWidth(); | |
int height = GetScreenHeight(); | |
float rw = ((float)width) / ((float)tex.width); | |
float rh = ((float)height) / ((float)tex.height); | |
if (rw < 1.0f || rh < 1.0f) { | |
if (rw < rh) { | |
dest.width *= rw; | |
dest.height *= rw; | |
} else { | |
dest.width *= rh; | |
dest.height *= rh; | |
} | |
} | |
dest.x = (float)(width - dest.width) / 2; | |
dest.y = (float)(height - dest.height) / 2; | |
Vector2 origin = {0, 0}; | |
DrawTexturePro(tex, source, dest, origin, 0, WHITE); | |
} | |
static void ImgSlideDraw(ImgSlide *is, float delta) { | |
if (is->files.count == 0) | |
return; | |
if (!is->paused) { | |
TimerUpdate(&is->nextImage, delta); | |
if (TimerCheck(&is->nextImage)) | |
ImgSlideNextImage(is); | |
} | |
if (is->animated) { | |
TimerUpdate(&is->anim.timer, delta); | |
if (TimerCheck(&is->anim.timer)) | |
is->anim.current = (is->anim.current + 1) % is->anim.frames; | |
Texture2D tex = is->anim.textures[is->anim.current]; | |
DrawTextureHandleLarge(tex); | |
} else | |
DrawTextureHandleLarge(is->basic); | |
if (is->paused) { | |
char text[4] = {0}; | |
sprintf(text, "%.1f", is->nextImage.delay); | |
DrawText(text, 10, 10, 30, GREEN); | |
} | |
} | |
int main(int argc, char **argv) { | |
ImgSlide is = {0}; | |
StringsInit(&is.files); | |
TimerReset(&is.nextImage, 3.0f); | |
SetTraceLogLevel(LOG_NONE); | |
InitWindow(1024, 768, "imgslide"); | |
SetWindowState(FLAG_WINDOW_RESIZABLE | FLAG_VSYNC_HINT); | |
SetTargetFPS(60); | |
while (!WindowShouldClose()) { | |
if (IsKeyPressed(KEY_UP) && is.nextImage.delay > 0.5f) { | |
TimerReset(&is.nextImage, is.nextImage.delay - 0.5f); | |
} else if (IsKeyPressed(KEY_DOWN) && is.nextImage.delay < 99.5f) { | |
TimerReset(&is.nextImage, is.nextImage.delay + 0.5f); | |
} else if (IsKeyPressed(KEY_SPACE)) { | |
is.paused = !is.paused; | |
} else if (IsKeyPressed(KEY_F)) { | |
if (!IsWindowMaximized()) | |
MaximizeWindow(); | |
else | |
RestoreWindow(); | |
} else if (IsKeyPressed(KEY_RIGHT)) { | |
TimerReset(&is.nextImage, is.nextImage.delay); | |
ImgSlideNextImage(&is); | |
} | |
if (IsFileDropped()) { | |
FilePathList files = LoadDroppedFiles(); | |
for (size_t i = 0; i < files.count; i++) { | |
const char *file = files.paths[i]; | |
if (DirectoryExists(file)) | |
ImgSlideLoadImageDirectory(&is, file); | |
else if (FileExists(file)) | |
StringsAppend(&is.files, file); | |
} | |
UnloadDroppedFiles(files); | |
TimerReset(&is.nextImage, is.nextImage.delay); | |
ImgSlideNextImage(&is); | |
} | |
BeginDrawing(); | |
{ | |
ClearBackground(RAYWHITE); | |
ImgSlideDraw(&is, GetFrameTime()); | |
} | |
EndDrawing(); | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment