Skip to content

Commit 4b1c89c

Browse files
committed
can save animated gifs
1 parent 9de04ef commit 4b1c89c

File tree

5 files changed

+972
-4
lines changed

5 files changed

+972
-4
lines changed

expose.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ struct sd_generation_inputs
204204
const char * sample_method = nullptr;
205205
const int clip_skip = -1;
206206
const int vid_req_frames = 1;
207+
const int vid_req_avi = 0;
207208
};
208209
struct sd_generation_outputs
209210
{

koboldcpp.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,8 @@ class sd_generation_inputs(ctypes.Structure):
317317
("seed", ctypes.c_int),
318318
("sample_method", ctypes.c_char_p),
319319
("clip_skip", ctypes.c_int),
320-
("vid_req_frames", ctypes.c_int)]
320+
("vid_req_frames", ctypes.c_int),
321+
("vid_req_avi", ctypes.c_int)]
321322

322323
class sd_generation_outputs(ctypes.Structure):
323324
_fields_ = [("status", ctypes.c_int),
@@ -1824,6 +1825,7 @@ def sd_generate(genparams):
18241825
clip_skip = tryparseint(genparams.get("clip_skip", -1),-1)
18251826
vid_req_frames = tryparseint(genparams.get("frames", 1),1)
18261827
vid_req_frames = 1 if (not vid_req_frames or vid_req_frames < 1) else vid_req_frames
1828+
vid_req_avi = 1 if genparams.get("avi_video", False) else 0
18271829
extra_images_arr = genparams.get("extra_images", [])
18281830
extra_images_arr = ([] if not extra_images_arr else extra_images_arr)
18291831
extra_images_arr = [img for img in extra_images_arr if img not in (None, "")]
@@ -1856,6 +1858,7 @@ def sd_generate(genparams):
18561858
inputs.sample_method = sample_method.lower().encode("UTF-8")
18571859
inputs.clip_skip = clip_skip
18581860
inputs.vid_req_frames = vid_req_frames
1861+
inputs.vid_req_avi = vid_req_avi
18591862
ret = handle.sd_generate(inputs)
18601863
outstr = ""
18611864
if ret.status==1:

otherarch/sdcpp/avi_writer.h

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include <string.h>
88

99
#include "stable-diffusion.h"
10+
#include "./gif.h" // charlietangora/gif-h
1011

1112
#ifndef INCLUDE_STB_IMAGE_WRITE_H
1213
#include "stb_image_write.h"
@@ -390,4 +391,107 @@ int create_mjpg_avi_membuf_from_sd_images(sd_image_t* images, int num_images, in
390391
return 0;
391392
}
392393

394+
/// kcpp gif writer
395+
396+
// ---------------- Helper: create_gif_buf_from_sd_images ----------------
397+
// Builds a GIF in memory from an array of sd_image_t. Returns 0 on success, -1 on failure.
398+
// Caller must free(*out_data) when done.
399+
int create_gif_buf_from_sd_images(sd_image_t* images, int num_images, int fps, int quality, uint8_t** out_data, size_t *out_len)
400+
{
401+
if(!images || num_images <= 0 || !out_data || !out_len) return -1;
402+
403+
// basic parameter heuristics
404+
if(fps <= 0) fps = 16;
405+
uint32_t delay = (uint32_t)(100 / fps); // hundredths of a second per frame
406+
407+
// map quality [1..100] to bitDepth and dithering
408+
if(quality < 1) quality = 1;
409+
if(quality > 100) quality = 100;
410+
int bitDepth = 8;
411+
bool dither = false;
412+
// if(quality >= 80) { bitDepth = 8; dither = false; }
413+
// else if(quality >= 50) { bitDepth = 6; dither = false; }
414+
// else { bitDepth = 5; dither = true; }
415+
416+
// assume all images same size; use first
417+
uint32_t width = images[0].width;
418+
uint32_t height = images[0].height;
419+
420+
GifWriter gw;
421+
memset(&gw, 0, sizeof(gw));
422+
423+
if(!GifBegin(&gw, width, height, delay, bitDepth, dither))
424+
{
425+
if(gw.oldImage) GIF_FREE(gw.oldImage);
426+
427+
fprintf(stderr, "Error: GifBegin failed.\n");
428+
return -1;
429+
}
430+
431+
// Feed frames
432+
for (int i = 0; i < num_images; i++)
433+
{
434+
sd_image_t* img = &images[i];
435+
436+
if (img->width != width || img->height != height) {
437+
fprintf(stderr, "Frame %d has mismatched dimensions.\n", i);
438+
GifEnd(&gw);
439+
memfile_free(&gw.mem);
440+
return -1;
441+
}
442+
443+
// gif-h expects 4 channels (RGBA) or 3 channels (RGB). It quantizes internally.
444+
// If your images have 3 channels, that’s fine. If 4 channels, it also works.
445+
int channels = img->channel;
446+
if (channels != 3 && channels != 4) {
447+
fprintf(stderr, "Unsupported channel count: %d\n", channels);
448+
GifEnd(&gw);
449+
memfile_free(&gw.mem);
450+
return -1;
451+
}
452+
453+
// gif-h requires 4 channels (RGBA). If you only have RGB, add opaque alpha.
454+
uint8_t* frame_rgba = NULL;
455+
if (channels == 3) {
456+
frame_rgba = (uint8_t*)malloc(width * height * 4);
457+
for (int p = 0; p < width * height; p++) {
458+
frame_rgba[p*4+0] = img->data[p*3+0];
459+
frame_rgba[p*4+1] = img->data[p*3+1];
460+
frame_rgba[p*4+2] = img->data[p*3+2];
461+
frame_rgba[p*4+3] = 255;
462+
}
463+
} else {
464+
frame_rgba = img->data; // already RGBA
465+
}
466+
467+
if(!GifWriteFrame(&gw, frame_rgba, width, height, delay, bitDepth, dither))
468+
{
469+
fprintf(stderr, "GIF Write Failed\n");
470+
GifEnd(&gw);
471+
memfile_free(&gw.mem);
472+
return -1;
473+
}
474+
475+
if (channels == 3) {
476+
free(frame_rgba);
477+
}
478+
}
479+
480+
if(!GifEnd(&gw))
481+
{
482+
memfile_free(&gw.mem);
483+
return -1;
484+
}
485+
486+
uint8_t* buf = memfile_detach(&gw.mem, out_len);
487+
if(!buf)
488+
{
489+
memfile_free(&gw.mem);
490+
return -1;
491+
}
492+
493+
*out_data = buf;
494+
return 0;
495+
}
496+
393497
#endif // __AVI_WRITER_H__

0 commit comments

Comments
 (0)