Skip to content

Instantly share code, notes, and snippets.

@henkman
Last active July 23, 2023 17:00
Show Gist options
  • Save henkman/5b9729ebb17ccdba38c8439163f3fa56 to your computer and use it in GitHub Desktop.
Save henkman/5b9729ebb17ccdba38c8439163f3fa56 to your computer and use it in GitHub Desktop.
img slide window with gif support made with raylib
// 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