diff --git a/expose.h b/expose.h index ba6df5ce2..659a0faa9 100644 --- a/expose.h +++ b/expose.h @@ -204,6 +204,7 @@ struct sd_generation_inputs const char * sample_method = nullptr; const int clip_skip = -1; const int vid_req_frames = 1; + const int vid_req_avi = 0; }; struct sd_generation_outputs { diff --git a/koboldcpp.py b/koboldcpp.py index 5efd063c5..e51635514 100644 --- a/koboldcpp.py +++ b/koboldcpp.py @@ -317,7 +317,8 @@ class sd_generation_inputs(ctypes.Structure): ("seed", ctypes.c_int), ("sample_method", ctypes.c_char_p), ("clip_skip", ctypes.c_int), - ("vid_req_frames", ctypes.c_int)] + ("vid_req_frames", ctypes.c_int), + ("vid_req_avi", ctypes.c_int)] class sd_generation_outputs(ctypes.Structure): _fields_ = [("status", ctypes.c_int), @@ -1824,6 +1825,7 @@ def sd_generate(genparams): clip_skip = tryparseint(genparams.get("clip_skip", -1),-1) vid_req_frames = tryparseint(genparams.get("frames", 1),1) vid_req_frames = 1 if (not vid_req_frames or vid_req_frames < 1) else vid_req_frames + vid_req_avi = 1 if genparams.get("avi_video", False) else 0 extra_images_arr = genparams.get("extra_images", []) extra_images_arr = ([] if not extra_images_arr else extra_images_arr) extra_images_arr = [img for img in extra_images_arr if img not in (None, "")] @@ -1856,6 +1858,7 @@ def sd_generate(genparams): inputs.sample_method = sample_method.lower().encode("UTF-8") inputs.clip_skip = clip_skip inputs.vid_req_frames = vid_req_frames + inputs.vid_req_avi = vid_req_avi ret = handle.sd_generate(inputs) outstr = "" if ret.status==1: diff --git a/otherarch/sdcpp/avi_writer.h b/otherarch/sdcpp/avi_writer.h index 7d46865b6..d0d452384 100644 --- a/otherarch/sdcpp/avi_writer.h +++ b/otherarch/sdcpp/avi_writer.h @@ -7,6 +7,7 @@ #include #include "stable-diffusion.h" +#include "./gif.h" // charlietangora/gif-h #ifndef INCLUDE_STB_IMAGE_WRITE_H #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 return 0; } +/// kcpp gif writer + +// ---------------- Helper: create_gif_buf_from_sd_images ---------------- +// Builds a GIF in memory from an array of sd_image_t. Returns 0 on success, -1 on failure. +// Caller must free(*out_data) when done. +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) +{ + if(!images || num_images <= 0 || !out_data || !out_len) return -1; + + // basic parameter heuristics + if(fps <= 0) fps = 16; + uint32_t delay = (uint32_t)(100 / fps); // hundredths of a second per frame + + // map quality [1..100] to bitDepth and dithering + if(quality < 1) quality = 1; + if(quality > 100) quality = 100; + int bitDepth = 8; + bool dither = false; + // if(quality >= 80) { bitDepth = 8; dither = false; } + // else if(quality >= 50) { bitDepth = 6; dither = false; } + // else { bitDepth = 5; dither = true; } + + // assume all images same size; use first + uint32_t width = images[0].width; + uint32_t height = images[0].height; + + GifWriter gw; + memset(&gw, 0, sizeof(gw)); + + if(!GifBegin(&gw, width, height, delay, bitDepth, dither)) + { + if(gw.oldImage) GIF_FREE(gw.oldImage); + + fprintf(stderr, "Error: GifBegin failed.\n"); + return -1; + } + + // Feed frames + for (int i = 0; i < num_images; i++) + { + sd_image_t* img = &images[i]; + + if (img->width != width || img->height != height) { + fprintf(stderr, "Frame %d has mismatched dimensions.\n", i); + GifEnd(&gw); + memfile_free(&gw.mem); + return -1; + } + + // gif-h expects 4 channels (RGBA) or 3 channels (RGB). It quantizes internally. + // If your images have 3 channels, that’s fine. If 4 channels, it also works. + int channels = img->channel; + if (channels != 3 && channels != 4) { + fprintf(stderr, "Unsupported channel count: %d\n", channels); + GifEnd(&gw); + memfile_free(&gw.mem); + return -1; + } + + // gif-h requires 4 channels (RGBA). If you only have RGB, add opaque alpha. + uint8_t* frame_rgba = NULL; + if (channels == 3) { + frame_rgba = (uint8_t*)malloc(width * height * 4); + for (int p = 0; p < width * height; p++) { + frame_rgba[p*4+0] = img->data[p*3+0]; + frame_rgba[p*4+1] = img->data[p*3+1]; + frame_rgba[p*4+2] = img->data[p*3+2]; + frame_rgba[p*4+3] = 255; + } + } else { + frame_rgba = img->data; // already RGBA + } + + if(!GifWriteFrame(&gw, frame_rgba, width, height, delay, bitDepth, dither)) + { + fprintf(stderr, "GIF Write Failed\n"); + GifEnd(&gw); + memfile_free(&gw.mem); + return -1; + } + + if (channels == 3) { + free(frame_rgba); + } + } + + if(!GifEnd(&gw)) + { + memfile_free(&gw.mem); + return -1; + } + + uint8_t* buf = memfile_detach(&gw.mem, out_len); + if(!buf) + { + memfile_free(&gw.mem); + return -1; + } + + *out_data = buf; + return 0; +} + #endif // __AVI_WRITER_H__ \ No newline at end of file diff --git a/otherarch/sdcpp/gif.h b/otherarch/sdcpp/gif.h new file mode 100644 index 000000000..5698e4c89 --- /dev/null +++ b/otherarch/sdcpp/gif.h @@ -0,0 +1,827 @@ +// +// gif_memory.h +// Modified from charlietangora/gif-h to write GIFs entirely to memory (no disk IO) +// Public domain adaptation +// +// Changes: +// - All file I/O replaced with an in-memory writer (growable buffer). +// - GifBegin now initializes an in-memory GIF writer. +// - Provided helper create_gif_buf_from_sd_images(...) that builds a GIF from an +// array of sd_image_t frames and returns an allocated buffer (caller must free). +// - Added simple heuristics for `quality` -> bitDepth & dithering, and fps -> delay +// +#ifndef GIF_MEMORY_H +#define GIF_MEMORY_H + +#include +#include +#include +#include + +// Memory writer structure (growable buffer) +typedef struct MemFile +{ + uint8_t* data; // allocated buffer + size_t len; // number of bytes written + size_t cap; // buffer capacity +} MemFile; + +static bool memfile_init(MemFile* mf, size_t initial_cap) +{ + mf->data = (uint8_t*)malloc(initial_cap); + if(!mf->data) return false; + mf->len = 0; + mf->cap = initial_cap; + return true; +} + +static void memfile_free(MemFile* mf) +{ + if(!mf) return; + if(mf->data) free(mf->data); + mf->data = NULL; + mf->len = 0; + mf->cap = 0; +} + +static bool memfile_ensure(MemFile* mf, size_t extra) +{ + if(mf->len + extra <= mf->cap) return true; + size_t newcap = mf->cap ? mf->cap : 1024; + while(newcap < mf->len + extra) newcap *= 2; + uint8_t* newdata = (uint8_t*)realloc(mf->data, newcap); + if(!newdata) return false; + mf->data = newdata; + mf->cap = newcap; + return true; +} + +static bool memfile_write(MemFile* mf, const void* src, size_t bytes) +{ + if(!memfile_ensure(mf, bytes)) return false; + memcpy(mf->data + mf->len, src, bytes); + mf->len += bytes; + return true; +} + +static bool memfile_putc(MemFile* mf, int c) +{ + uint8_t b = (uint8_t)c; + return memfile_write(mf, &b, 1); +} + +static uint8_t* memfile_detach(MemFile* mf, size_t* out_len) +{ + // Transfer ownership of the exact-sized buffer to the caller + if(!mf->data) + { + if(out_len) *out_len = 0; + return NULL; + } + uint8_t* exact = (uint8_t*)malloc(mf->len); + if(!exact) return NULL; + memcpy(exact, mf->data, mf->len); + if(out_len) *out_len = mf->len; + // free internal buffer + free(mf->data); + mf->data = NULL; + mf->len = 0; + mf->cap = 0; + return exact; +} + +// allow custom allocators like original file +#ifndef GIF_TEMP_MALLOC +#define GIF_TEMP_MALLOC malloc +#endif +#ifndef GIF_TEMP_FREE +#define GIF_TEMP_FREE free +#endif +#ifndef GIF_MALLOC +#define GIF_MALLOC malloc +#endif +#ifndef GIF_FREE +#define GIF_FREE free +#endif + +const int kGifTransIndex = 0; + +typedef struct +{ + int bitDepth; + + uint8_t r[256]; + uint8_t g[256]; + uint8_t b[256]; + + uint8_t treeSplitElt[256]; + uint8_t treeSplit[256]; +} GifPalette; + +// max/min/abs +static int GifIMax(int l, int r) { return l>r?l:r; } +static int GifIMin(int l, int r) { return l (1<bitDepth)-1) + { + int ind = treeRoot-(1<bitDepth); + if(ind == kGifTransIndex) return; + + int r_err = r - ((int32_t)pPal->r[ind]); + int g_err = g - ((int32_t)pPal->g[ind]); + int b_err = b - ((int32_t)pPal->b[ind]); + int diff = GifIAbs(r_err)+GifIAbs(g_err)+GifIAbs(b_err); + + if(diff < *bestDiff) + { + *bestInd = ind; + *bestDiff = diff; + } + + return; + } + + int comps[3]; comps[0] = r; comps[1] = g; comps[2] = b; + int splitComp = comps[pPal->treeSplitElt[treeRoot]]; + + int splitPos = pPal->treeSplit[treeRoot]; + if(splitPos > splitComp) + { + GifGetClosestPaletteColor(pPal, r, g, b, bestInd, bestDiff, treeRoot*2); + if( *bestDiff > splitPos - splitComp ) + { + GifGetClosestPaletteColor(pPal, r, g, b, bestInd, bestDiff, treeRoot*2+1); + } + } + else + { + GifGetClosestPaletteColor(pPal, r, g, b, bestInd, bestDiff, treeRoot*2+1); + if( *bestDiff > splitComp - splitPos ) + { + GifGetClosestPaletteColor(pPal, r, g, b, bestInd, bestDiff, treeRoot*2); + } + } +} + +void GifSwapPixels(uint8_t* image, int pixA, int pixB) +{ + uint8_t rA = image[pixA*4]; + uint8_t gA = image[pixA*4+1]; + uint8_t bA = image[pixA*4+2]; + uint8_t aA = image[pixA*4+3]; + + uint8_t rB = image[pixB*4]; + uint8_t gB = image[pixB*4+1]; + uint8_t bB = image[pixB*4+2]; + uint8_t aB = image[pixB*4+3]; + + image[pixA*4] = rB; + image[pixA*4+1] = gB; + image[pixA*4+2] = bB; + image[pixA*4+3] = aB; + + image[pixB*4] = rA; + image[pixB*4+1] = gA; + image[pixB*4+2] = bA; + image[pixB*4+3] = aA; +} + +int GifPartition(uint8_t* image, const int left, const int right, const int elt, int pivotValue) +{ + int storeIndex = left; + bool split = 0; + for(int ii=left; ii neededCenter) + GifPartitionByMedian(image, left, pivotIndex, com, neededCenter); + + if(pivotIndex < neededCenter) + GifPartitionByMedian(image, pivotIndex+1, right, com, neededCenter); + } +} + +int GifPartitionByMean(uint8_t* image, int left, int right, int com, int neededMean) +{ + if(left < right-1) + { + return GifPartition(image, left, right-1, com, neededMean); + } + return left; +} + +void GifSplitPalette(uint8_t* image, int numPixels, int treeNode, int treeLevel, bool buildForDither, GifPalette* pal) +{ + if(numPixels == 0) + return; + + int numColors = (1 << pal->bitDepth); + + if(treeNode >= numColors) + { + int entry = treeNode - numColors; + + if(buildForDither) + { + if( entry == 1 ) + { + uint32_t r=255, g=255, b=255; + for(int ii=0; iir[entry] = (uint8_t)r; + pal->g[entry] = (uint8_t)g; + pal->b[entry] = (uint8_t)b; + + return; + } + + if( entry == numColors-1 ) + { + uint32_t r=0, g=0, b=0; + for(int ii=0; iir[entry] = (uint8_t)r; + pal->g[entry] = (uint8_t)g; + pal->b[entry] = (uint8_t)b; + + return; + } + } + + uint64_t r=0, g=0, b=0; + for(int ii=0; iir[entry] = (uint8_t)r; + pal->g[entry] = (uint8_t)g; + pal->b[entry] = (uint8_t)b; + + return; + } + + int minR = 255, maxR = 0; + int minG = 255, maxG = 0; + int minB = 255, maxB = 0; + for(int ii=0; ii maxR) maxR = r; + if(r < minR) minR = r; + + if(g > maxG) maxG = g; + if(g < minG) minG = g; + + if(b > maxB) maxB = b; + if(b < minB) minB = b; + } + + int rRange = maxR - minR; + int gRange = maxG - minG; + int bRange = maxB - minB; + + int splitCom = 1; int rangeMin = minG; int rangeMax = maxG; + if(bRange > gRange) { splitCom = 2; rangeMin = minB; rangeMax = maxB; } + if(rRange > bRange && rRange > gRange) { splitCom = 0; rangeMin = minR; rangeMax = maxR; } + + int subPixelsA = numPixels / 2; + + GifPartitionByMedian(image, 0, numPixels, splitCom, subPixelsA); + int splitValue = image[subPixelsA*4+splitCom]; + + int splitUnbalance = GifIAbs( (splitValue - rangeMin) - (rangeMax - splitValue) ); + if( splitUnbalance > (1536 >> treeLevel) ) + { + splitValue = rangeMin + (rangeMax-rangeMin) / 2; + subPixelsA = GifPartitionByMean(image, 0, numPixels, splitCom, splitValue); + } + + if( treeNode == numColors/2 ) + { + subPixelsA = 0; + splitValue = 0; + } + + int subPixelsB = numPixels-subPixelsA; + pal->treeSplitElt[treeNode] = (uint8_t)splitCom; + pal->treeSplit[treeNode] = (uint8_t)splitValue; + + GifSplitPalette(image, subPixelsA, treeNode*2, treeLevel+1, buildForDither, pal); + GifSplitPalette(image+subPixelsA*4, subPixelsB, treeNode*2+1, treeLevel+1, buildForDither, pal); +} + +int GifPickChangedPixels( const uint8_t* lastFrame, uint8_t* frame, int numPixels ) +{ + int numChanged = 0; + uint8_t* writeIter = frame; + + for (int ii=0; iibitDepth = bitDepth; + + size_t imageSize = (size_t)(width * height * 4 * sizeof(uint8_t)); + uint8_t* destroyableImage = (uint8_t*)GIF_TEMP_MALLOC(imageSize); + memcpy(destroyableImage, nextFrame, imageSize); + + int numPixels = (int)(width * height); + if(lastFrame) + numPixels = GifPickChangedPixels(lastFrame, destroyableImage, numPixels); + + GifSplitPalette(destroyableImage, numPixels, 1, 0, buildForDither, pPal); + + GIF_TEMP_FREE(destroyableImage); + + pPal->treeSplit[1 << (bitDepth-1)] = 0; + pPal->treeSplitElt[1 << (bitDepth-1)] = 0; + + pPal->r[0] = pPal->g[0] = pPal->b[0] = 0; +} + +void GifDitherImage( const uint8_t* lastFrame, const uint8_t* nextFrame, uint8_t* outFrame, uint32_t width, uint32_t height, GifPalette* pPal ) +{ + int numPixels = (int)(width * height); + + int32_t *quantPixels = (int32_t *)GIF_TEMP_MALLOC(sizeof(int32_t) * (size_t)numPixels * 4); + + for( int ii=0; iir[bestInd]) * 256; + int32_t g_err = nextPix[1] - (int32_t)(pPal->g[bestInd]) * 256; + int32_t b_err = nextPix[2] - (int32_t)(pPal->b[bestInd]) * 256; + + nextPix[0] = pPal->r[bestInd]; + nextPix[1] = pPal->g[bestInd]; + nextPix[2] = pPal->b[bestInd]; + nextPix[3] = bestInd; + + int quantloc_7 = (int)(yy * width + xx + 1); + int quantloc_3 = (int)(yy * width + width + xx - 1); + int quantloc_5 = (int)(yy * width + width + xx); + int quantloc_1 = (int)(yy * width + width + xx + 1); + + if(quantloc_7 < numPixels) + { + int32_t* pix7 = quantPixels+4*quantloc_7; + pix7[0] += GifIMax( -pix7[0], r_err * 7 / 16 ); + pix7[1] += GifIMax( -pix7[1], g_err * 7 / 16 ); + pix7[2] += GifIMax( -pix7[2], b_err * 7 / 16 ); + } + + if(quantloc_3 < numPixels) + { + int32_t* pix3 = quantPixels+4*quantloc_3; + pix3[0] += GifIMax( -pix3[0], r_err * 3 / 16 ); + pix3[1] += GifIMax( -pix3[1], g_err * 3 / 16 ); + pix3[2] += GifIMax( -pix3[2], b_err * 3 / 16 ); + } + + if(quantloc_5 < numPixels) + { + int32_t* pix5 = quantPixels+4*quantloc_5; + pix5[0] += GifIMax( -pix5[0], r_err * 5 / 16 ); + pix5[1] += GifIMax( -pix5[1], g_err * 5 / 16 ); + pix5[2] += GifIMax( -pix5[2], b_err * 5 / 16 ); + } + + if(quantloc_1 < numPixels) + { + int32_t* pix1 = quantPixels+4*quantloc_1; + pix1[0] += GifIMax( -pix1[0], r_err / 16 ); + pix1[1] += GifIMax( -pix1[1], g_err / 16 ); + pix1[2] += GifIMax( -pix1[2], b_err / 16 ); + } + } + } + + for( int ii=0; iir[bestInd]; + outFrame[1] = pPal->g[bestInd]; + outFrame[2] = pPal->b[bestInd]; + outFrame[3] = (uint8_t)bestInd; + } + + if(lastFrame) lastFrame += 4; + outFrame += 4; + nextFrame += 4; + } +} + +// --- LZW and writing to MemFile --- + +void GifWriteBit( GifBitStatus* stat, uint32_t bit ) +{ + bit = bit & 1; + bit = bit << stat->bitIndex; + stat->byte |= bit; + + ++stat->bitIndex; + if( stat->bitIndex > 7 ) + { + stat->chunk[stat->chunkIndex++] = stat->byte; + stat->bitIndex = 0; + stat->byte = 0; + } +} + +static bool GifWriteChunkToMem(MemFile* mf, GifBitStatus* stat) +{ + // write chunk length and bytes + if(!memfile_putc(mf, (int)stat->chunkIndex)) return false; + if(!memfile_write(mf, stat->chunk, stat->chunkIndex)) return false; + stat->bitIndex = 0; + stat->byte = 0; + stat->chunkIndex = 0; + return true; +} + +void GifWriteCodeMem( MemFile* mf, GifBitStatus* stat, uint32_t code, uint32_t length ) +{ + for( uint32_t ii=0; ii> 1; + + if( stat->chunkIndex == 255 ) + { + // flush chunk to mem + GifWriteChunkToMem(mf, stat); + } + } +} + +typedef struct +{ + uint16_t m_next[256]; +} GifLzwNode; + +static bool GifWritePaletteMem( const GifPalette* pPal, MemFile* mf ) +{ + // transparency color + if(!memfile_putc(mf, 0)) return false; + if(!memfile_putc(mf, 0)) return false; + if(!memfile_putc(mf, 0)) return false; + + for(int ii=1; ii<(1 << pPal->bitDepth); ++ii) + { + if(!memfile_putc(mf, pPal->r[ii])) return false; + if(!memfile_putc(mf, pPal->g[ii])) return false; + if(!memfile_putc(mf, pPal->b[ii])) return false; + } + + return true; +} + +static bool GifWriteLzwImageMem(MemFile* mf, uint8_t* image, uint32_t left, uint32_t top, uint32_t width, uint32_t height, uint32_t delay, GifPalette* pPal) +{ + // graphics control extension + if(!memfile_putc(mf, 0x21)) return false; + if(!memfile_putc(mf, 0xf9)) return false; + if(!memfile_putc(mf, 0x04)) return false; + if(!memfile_putc(mf, 0x05)) return false; // leave prev frame in place, has transparency + if(!memfile_putc(mf, delay & 0xff)) return false; + if(!memfile_putc(mf, (delay >> 8) & 0xff)) return false; + if(!memfile_putc(mf, kGifTransIndex)) return false; // transparent index + if(!memfile_putc(mf, 0)) return false; + + if(!memfile_putc(mf, 0x2c)) return false; // image descriptor block + + if(!memfile_putc(mf, left & 0xff)) return false; + if(!memfile_putc(mf, (left >> 8) & 0xff)) return false; + if(!memfile_putc(mf, top & 0xff)) return false; + if(!memfile_putc(mf, (top >> 8) & 0xff)) return false; + + if(!memfile_putc(mf, width & 0xff)) return false; + if(!memfile_putc(mf, (width >> 8) & 0xff)) return false; + if(!memfile_putc(mf, height & 0xff)) return false; + if(!memfile_putc(mf, (height >> 8) & 0xff)) return false; + + // local color table present, size = 2^bitDepth + if(!memfile_putc(mf, 0x80 + pPal->bitDepth-1)) return false; + + if(!GifWritePaletteMem(pPal, mf)) return false; + + const int minCodeSize = pPal->bitDepth; + const uint32_t clearCode = 1 << pPal->bitDepth; + + if(!memfile_putc(mf, minCodeSize)) return false; // min code size + + GifLzwNode* codetree = (GifLzwNode*)GIF_TEMP_MALLOC(sizeof(GifLzwNode)*4096); + if(!codetree) return false; + memset(codetree, 0, sizeof(GifLzwNode)*4096); + + int32_t curCode = -1; + uint32_t codeSize = (uint32_t)minCodeSize + 1; + uint32_t maxCode = clearCode+1; + + GifBitStatus stat; + stat.byte = 0; + stat.bitIndex = 0; + stat.chunkIndex = 0; + + // write initial clear code + GifWriteCodeMem(mf, &stat, clearCode, codeSize); + + for(uint32_t yy=0; yy= (1ul << codeSize) ) + { + codeSize++; + } + if( maxCode == 4095 ) + { + GifWriteCodeMem(mf, &stat, clearCode, codeSize); + + memset(codetree, 0, sizeof(GifLzwNode)*4096); + codeSize = (uint32_t)(minCodeSize + 1); + maxCode = clearCode+1; + } + + curCode = nextValue; + } + } + } + + GifWriteCodeMem(mf, &stat, (uint32_t)curCode, codeSize); + GifWriteCodeMem(mf, &stat, clearCode, codeSize); + GifWriteCodeMem(mf, &stat, clearCode + 1, (uint32_t)minCodeSize + 1); + + while( stat.bitIndex ) GifWriteBit(&stat, 0); + if( stat.chunkIndex ) GifWriteChunkToMem(mf, &stat); + + if(!memfile_putc(mf, 0)) { GIF_TEMP_FREE(codetree); return false; } // image block terminator + + GIF_TEMP_FREE(codetree); + return true; +} + +// --- Public API: GifBegin / GifWriteFrame / GifEnd (memory-based) --- + +// Initialize a GifWriter that writes to memory. The memory buffer is owned by the writer +// until you call memfile_detach on writer->mem (or use GifEnd + memfile_detach inside helper) +static bool GifBegin( GifWriter* writer, uint32_t width, uint32_t height, uint32_t delay, int32_t bitDepth, bool dither ) +{ + (void)bitDepth; (void)dither; + if(!writer) return false; + size_t approx = (size_t)width * (size_t)height * 1; // rough initial guess + if(approx < 16384) approx = 16384; + if(!memfile_init(&writer->mem, approx)) return false; + + writer->firstFrame = true; + writer->oldImage = (uint8_t*)GIF_MALLOC(width*height*4); + if(!writer->oldImage) { memfile_free(&writer->mem); return false; } + + // header + if(!memfile_write(&writer->mem, "GIF89a", 6)) return false; + + // screen descriptor (width, height) + if(!memfile_putc(&writer->mem, width & 0xff)) return false; + if(!memfile_putc(&writer->mem, (width >> 8) & 0xff)) return false; + if(!memfile_putc(&writer->mem, height & 0xff)) return false; + if(!memfile_putc(&writer->mem, (height >> 8) & 0xff)) return false; + + if(!memfile_putc(&writer->mem, 0xf0)) return false; // global color table flag (dummy) + if(!memfile_putc(&writer->mem, 0)) return false; // background + if(!memfile_putc(&writer->mem, 0)) return false; // aspect + + // global palette (dummy two black entries) + if(!memfile_putc(&writer->mem, 0)) return false; + if(!memfile_putc(&writer->mem, 0)) return false; + if(!memfile_putc(&writer->mem, 0)) return false; + if(!memfile_putc(&writer->mem, 0)) return false; + if(!memfile_putc(&writer->mem, 0)) return false; + if(!memfile_putc(&writer->mem, 0)) return false; + + if( delay != 0 ) + { + // NETSCAPE2.0 application extension for looping + if(!memfile_putc(&writer->mem, 0x21)) return false; // extension + if(!memfile_putc(&writer->mem, 0xff)) return false; // application + if(!memfile_putc(&writer->mem, 11)) return false; // length + if(!memfile_write(&writer->mem, "NETSCAPE2.0", 11)) return false; + if(!memfile_putc(&writer->mem, 3)) return false; // data length + if(!memfile_putc(&writer->mem, 1)) return false; // sub-block ID + if(!memfile_putc(&writer->mem, 0)) return false; // loop count low (0 = infinite) + if(!memfile_putc(&writer->mem, 0)) return false; // loop count high + if(!memfile_putc(&writer->mem, 0)) return false; // terminator + } + + return true; +} + +static bool GifWriteFrame( GifWriter* writer, const uint8_t* image, uint32_t width, uint32_t height, uint32_t delay, int bitDepth, bool dither ) +{ + if(!writer) return false; + + const uint8_t* oldImage = writer->firstFrame? NULL : writer->oldImage; + writer->firstFrame = false; + + GifPalette pal; + GifMakePalette((dither? NULL : oldImage), image, width, height, bitDepth, dither, &pal); + + if(dither) + GifDitherImage(oldImage, image, writer->oldImage, width, height, &pal); + else + GifThresholdImage(oldImage, image, writer->oldImage, width, height, &pal); + + if(!GifWriteLzwImageMem(&writer->mem, writer->oldImage, 0, 0, width, height, delay, &pal)) return false; + + return true; +} + +static bool GifEnd( GifWriter* writer ) +{ + if(!writer) return false; + if(!memfile_putc(&writer->mem, 0x3b)) return false; // EOF + + if(writer->oldImage) GIF_FREE(writer->oldImage); + writer->oldImage = NULL; + writer->firstFrame = true; + + // keep mem buffer available for detaching by caller + return true; +} + + +#endif // GIF_MEMORY_H diff --git a/otherarch/sdcpp/sdtype_adapter.cpp b/otherarch/sdcpp/sdtype_adapter.cpp index 18ce35ce9..8542591b4 100644 --- a/otherarch/sdcpp/sdtype_adapter.cpp +++ b/otherarch/sdcpp/sdtype_adapter.cpp @@ -266,6 +266,7 @@ bool sdtype_load_model(const sd_load_model_inputs inputs) { params.offload_params_to_cpu = inputs.offload_cpu; params.keep_vae_on_cpu = inputs.vae_cpu; params.keep_clip_on_cpu = inputs.clip_cpu; + // params.flow_shift = 5.0f; if (params.chroma_use_dit_mask && params.diffusion_flash_attn) { // note we don't know yet if it's a Chroma model @@ -676,7 +677,9 @@ sd_generation_outputs sdtype_generate(const sd_generation_inputs inputs) } std::vector reference_imgs; - if(extra_image_data.size()>0 && loadedsdver==SDVersion::VERSION_FLUX && !loaded_model_is_chroma(sd_ctx)) + bool is_wan = (loadedsdver == SDVersion::VERSION_WAN2 || loadedsdver == SDVersion::VERSION_WAN2_2_I2V || loadedsdver == SDVersion::VERSION_WAN2_2_TI2V); + bool is_kontext = (loadedsdver==SDVersion::VERSION_FLUX && !loaded_model_is_chroma(sd_ctx)); + if(extra_image_data.size()>0 && (is_wan || is_kontext)) { for(int i=0;i0) + { + if(reference_imgs.size()>=1) + { + vid_gen_params.init_image = reference_imgs[0]; + } + if(reference_imgs.size()>=2) + { + vid_gen_params.end_image = reference_imgs[1]; + } + } if(!sd_is_quiet && sddebugmode==1) { std::stringstream ss; @@ -758,6 +773,7 @@ sd_generation_outputs sdtype_generate(const sd_generation_inputs inputs) << "\nSTRENGTH:" << vid_gen_params.strength << "\nFRAMES:" << vid_gen_params.video_frames << "\nCTRL_FRM:" << vid_gen_params.control_frames_size + << "\nREF_IMGS:" << reference_imgs.size() << "\n\n"; printf("%s", ss.str().c_str()); } @@ -921,9 +937,26 @@ sd_generation_outputs sdtype_generate(const sd_generation_inputs inputs) //if multiframe, make a video if(vid_req_frames>1 && generated_num_results>1 && is_vid_model) { + if(!sd_is_quiet && sddebugmode==1) + { + printf("\nSaving video buffer, AVI=%d...",vid_req_avi); + } uint8_t * out_data = nullptr; size_t out_len = 0; - int status = create_mjpg_avi_membuf_from_sd_images(results, generated_num_results, 24, 40, &out_data,&out_len); + int status = 0; + if(vid_req_avi==1) + { + status = create_mjpg_avi_membuf_from_sd_images(results, generated_num_results, 16, 40, &out_data,&out_len); + } + else + { + status = create_gif_buf_from_sd_images(results, generated_num_results, 16, 100, &out_data,&out_len); + } + + if(!sd_is_quiet && sddebugmode==1) + { + printf(status==0?"Video Saved!\n":"Save Failed!\n"); + } if(status==0) { recent_data = kcpp_base64_encode(out_data, out_len);