mirror of
https://github.com/LostRuins/koboldcpp.git
synced 2026-09-19 01:05:09 +02:00
562 lines
18 KiB
C++
562 lines
18 KiB
C++
#ifndef __AVI_WRITER_H__
|
||
#define __AVI_WRITER_H__
|
||
|
||
#include <cstdint>
|
||
#include <cstdio>
|
||
#include <cstdlib>
|
||
#include <cstring>
|
||
|
||
#include "stable-diffusion.h"
|
||
|
||
#ifndef MSF_GIF_IMPL
|
||
#define MSF_GIF_IMPL
|
||
#endif
|
||
#include "./examples/cli/msf_gif.h" //notnullnotvoid/msf_gif
|
||
|
||
#ifndef INCLUDE_STB_IMAGE_WRITE_H
|
||
#include "stb_image_write.h"
|
||
#endif
|
||
|
||
typedef struct {
|
||
uint32_t offset;
|
||
uint32_t size;
|
||
} avi_index_entry;
|
||
|
||
// Write 32-bit little-endian integer
|
||
void write_u32_le(FILE* f, uint32_t val) {
|
||
fwrite(&val, 4, 1, f);
|
||
}
|
||
|
||
// Write 16-bit little-endian integer
|
||
void write_u16_le(FILE* f, uint16_t val) {
|
||
fwrite(&val, 2, 1, f);
|
||
}
|
||
|
||
/**
|
||
* Create an MJPG AVI file from an array of sd_image_t images.
|
||
* Images are encoded to JPEG using stb_image_write.
|
||
*
|
||
* @param filename Output AVI file name.
|
||
* @param images Array of input images.
|
||
* @param num_images Number of images in the array.
|
||
* @param fps Frames per second for the video.
|
||
* @param quality JPEG quality (0-100).
|
||
* @return 0 on success, -1 on failure.
|
||
*/
|
||
int create_mjpg_avi_from_sd_images(const char* filename, sd_image_t* images, int num_images, int fps, int quality = 90) {
|
||
if (num_images == 0) {
|
||
fprintf(stderr, "Error: Image array is empty.\n");
|
||
return -1;
|
||
}
|
||
|
||
FILE* f = fopen(filename, "wb");
|
||
if (!f) {
|
||
perror("Error opening file for writing");
|
||
return -1;
|
||
}
|
||
|
||
uint32_t width = images[0].width;
|
||
uint32_t height = images[0].height;
|
||
uint32_t channels = images[0].channel;
|
||
if (channels != 3 && channels != 4) {
|
||
fprintf(stderr, "Error: Unsupported channel count: %u\n", channels);
|
||
fclose(f);
|
||
return -1;
|
||
}
|
||
|
||
// --- RIFF AVI Header ---
|
||
fwrite("RIFF", 4, 1, f);
|
||
long riff_size_pos = ftell(f);
|
||
write_u32_le(f, 0); // Placeholder for file size
|
||
fwrite("AVI ", 4, 1, f);
|
||
|
||
// 'hdrl' LIST (header list)
|
||
fwrite("LIST", 4, 1, f);
|
||
write_u32_le(f, 4 + 8 + 56 + 8 + 4 + 8 + 56 + 8 + 40);
|
||
fwrite("hdrl", 4, 1, f);
|
||
|
||
// 'avih' chunk (AVI main header)
|
||
fwrite("avih", 4, 1, f);
|
||
write_u32_le(f, 56);
|
||
write_u32_le(f, 1000000 / fps); // Microseconds per frame
|
||
write_u32_le(f, 0); // Max bytes per second
|
||
write_u32_le(f, 0); // Padding granularity
|
||
write_u32_le(f, 0x110); // Flags (HASINDEX | ISINTERLEAVED)
|
||
write_u32_le(f, num_images); // Total frames
|
||
write_u32_le(f, 0); // Initial frames
|
||
write_u32_le(f, 1); // Number of streams
|
||
write_u32_le(f, width * height * 3); // Suggested buffer size
|
||
write_u32_le(f, width);
|
||
write_u32_le(f, height);
|
||
write_u32_le(f, 0); // Reserved
|
||
write_u32_le(f, 0); // Reserved
|
||
write_u32_le(f, 0); // Reserved
|
||
write_u32_le(f, 0); // Reserved
|
||
|
||
// 'strl' LIST (stream list)
|
||
fwrite("LIST", 4, 1, f);
|
||
write_u32_le(f, 4 + 8 + 56 + 8 + 40);
|
||
fwrite("strl", 4, 1, f);
|
||
|
||
// 'strh' chunk (stream header)
|
||
fwrite("strh", 4, 1, f);
|
||
write_u32_le(f, 56);
|
||
fwrite("vids", 4, 1, f); // Stream type: video
|
||
fwrite("MJPG", 4, 1, f); // Codec: Motion JPEG
|
||
write_u32_le(f, 0); // Flags
|
||
write_u16_le(f, 0); // Priority
|
||
write_u16_le(f, 0); // Language
|
||
write_u32_le(f, 0); // Initial frames
|
||
write_u32_le(f, 1); // Scale
|
||
write_u32_le(f, fps); // Rate
|
||
write_u32_le(f, 0); // Start
|
||
write_u32_le(f, num_images); // Length
|
||
write_u32_le(f, width * height * 3); // Suggested buffer size
|
||
write_u32_le(f, (uint32_t)-1); // Quality
|
||
write_u32_le(f, 0); // Sample size
|
||
write_u16_le(f, 0); // rcFrame.left
|
||
write_u16_le(f, 0); // rcFrame.top
|
||
write_u16_le(f, 0); // rcFrame.right
|
||
write_u16_le(f, 0); // rcFrame.bottom
|
||
|
||
// 'strf' chunk (stream format: BITMAPINFOHEADER)
|
||
fwrite("strf", 4, 1, f);
|
||
write_u32_le(f, 40);
|
||
write_u32_le(f, 40); // biSize
|
||
write_u32_le(f, width);
|
||
write_u32_le(f, height);
|
||
write_u16_le(f, 1); // biPlanes
|
||
write_u16_le(f, 24); // biBitCount
|
||
fwrite("MJPG", 4, 1, f); // biCompression (FOURCC)
|
||
write_u32_le(f, width * height * 3); // biSizeImage
|
||
write_u32_le(f, 0); // XPelsPerMeter
|
||
write_u32_le(f, 0); // YPelsPerMeter
|
||
write_u32_le(f, 0); // Colors used
|
||
write_u32_le(f, 0); // Colors important
|
||
|
||
// 'movi' LIST (video frames)
|
||
// long movi_list_pos = ftell(f);
|
||
fwrite("LIST", 4, 1, f);
|
||
long movi_size_pos = ftell(f);
|
||
write_u32_le(f, 0); // Placeholder for movi size
|
||
fwrite("movi", 4, 1, f);
|
||
|
||
avi_index_entry* index = (avi_index_entry*)malloc(sizeof(avi_index_entry) * num_images);
|
||
if (!index) {
|
||
fclose(f);
|
||
return -1;
|
||
}
|
||
|
||
// Encode and write each frame as JPEG
|
||
struct {
|
||
uint8_t* buf;
|
||
size_t size;
|
||
} jpeg_data;
|
||
|
||
for (int i = 0; i < num_images; i++) {
|
||
jpeg_data.buf = nullptr;
|
||
jpeg_data.size = 0;
|
||
|
||
// Callback function to collect JPEG data into memory
|
||
auto write_to_buf = [](void* context, void* data, int size) {
|
||
auto jd = (decltype(jpeg_data)*)context;
|
||
jd->buf = (uint8_t*)realloc(jd->buf, jd->size + size);
|
||
memcpy(jd->buf + jd->size, data, size);
|
||
jd->size += size;
|
||
};
|
||
|
||
// Encode to JPEG in memory
|
||
stbi_write_jpg_to_func(
|
||
write_to_buf,
|
||
&jpeg_data,
|
||
images[i].width,
|
||
images[i].height,
|
||
channels,
|
||
images[i].data,
|
||
quality);
|
||
|
||
// Write '00dc' chunk (video frame)
|
||
fwrite("00dc", 4, 1, f);
|
||
write_u32_le(f, (uint32_t)jpeg_data.size);
|
||
index[i].offset = ftell(f) - 8;
|
||
index[i].size = (uint32_t)jpeg_data.size;
|
||
fwrite(jpeg_data.buf, 1, jpeg_data.size, f);
|
||
|
||
// Align to even byte size
|
||
if (jpeg_data.size % 2)
|
||
fputc(0, f);
|
||
|
||
free(jpeg_data.buf);
|
||
}
|
||
|
||
// Finalize 'movi' size
|
||
long cur_pos = ftell(f);
|
||
long movi_size = cur_pos - movi_size_pos - 4;
|
||
fseek(f, movi_size_pos, SEEK_SET);
|
||
write_u32_le(f, movi_size);
|
||
fseek(f, cur_pos, SEEK_SET);
|
||
|
||
// Write 'idx1' index
|
||
fwrite("idx1", 4, 1, f);
|
||
write_u32_le(f, num_images * 16);
|
||
for (int i = 0; i < num_images; i++) {
|
||
fwrite("00dc", 4, 1, f);
|
||
write_u32_le(f, 0x10);
|
||
write_u32_le(f, index[i].offset);
|
||
write_u32_le(f, index[i].size);
|
||
}
|
||
|
||
// Finalize RIFF size
|
||
cur_pos = ftell(f);
|
||
long file_size = cur_pos - riff_size_pos - 4;
|
||
fseek(f, riff_size_pos, SEEK_SET);
|
||
write_u32_le(f, file_size);
|
||
fseek(f, cur_pos, SEEK_SET);
|
||
|
||
fclose(f);
|
||
free(index);
|
||
|
||
return 0;
|
||
}
|
||
|
||
|
||
//// KCPP PART FOR WRITING AVI TO MEMORY
|
||
|
||
typedef struct {
|
||
uint8_t* data;
|
||
size_t size;
|
||
} mem_buffer_t;
|
||
|
||
// Append raw bytes
|
||
static void mem_write(mem_buffer_t* buf, const void* data, size_t size) {
|
||
buf->data = (uint8_t*)realloc(buf->data, buf->size + size);
|
||
memcpy(buf->data + buf->size, data, size);
|
||
buf->size += size;
|
||
}
|
||
|
||
// Write 32-bit LE
|
||
static void mem_write_u32_le(mem_buffer_t* buf, uint32_t val) {
|
||
mem_write(buf, &val, 4);
|
||
}
|
||
|
||
// Write 16-bit LE
|
||
static void mem_write_u16_le(mem_buffer_t* buf, uint16_t val) {
|
||
mem_write(buf, &val, 2);
|
||
}
|
||
|
||
static uint8_t* sd_audio_to_pcm16_bytes(const sd_audio_t* audio, size_t* out_size) {
|
||
*out_size = 0;
|
||
if (audio == NULL || audio->data == NULL || audio->sample_count == 0 || audio->channels == 0 || audio->sample_rate == 0) {
|
||
return NULL;
|
||
}
|
||
|
||
size_t pcm_samples = (size_t)audio->sample_count * (size_t)audio->channels;
|
||
size_t pcm_size = pcm_samples * sizeof(int16_t);
|
||
uint8_t* bytes = (uint8_t*)malloc(pcm_size);
|
||
if (bytes == NULL) {
|
||
return NULL;
|
||
}
|
||
|
||
int16_t* pcm = (int16_t*)bytes;
|
||
for (size_t i = 0; i < pcm_samples; ++i) {
|
||
float sample = audio->data[i];
|
||
if (sample < -1.0f) sample = -1.0f;
|
||
if (sample > 1.0f) sample = 1.0f;
|
||
float scaled = sample * 32767.0f;
|
||
pcm[i] = (int16_t)(scaled >= 0.0f ? scaled + 0.5f : scaled - 0.5f);
|
||
}
|
||
|
||
*out_size = pcm_size;
|
||
return bytes;
|
||
}
|
||
|
||
/**
|
||
* Create MJPG AVI file in memory and return as base64 string.
|
||
* Returns 0 on success, -1 on failure
|
||
* must be freed by caller after use
|
||
*/
|
||
int create_mjpg_avi_membuf_from_sd_images(sd_image_t* images, int num_images, int fps, int quality, uint8_t** out_data, size_t *out_len, const sd_audio_t* audio = NULL)
|
||
{
|
||
if (num_images == 0) {
|
||
fprintf(stderr, "Error: Image array is empty.\n");
|
||
return -1;
|
||
}
|
||
|
||
mem_buffer_t buf = {NULL, 0};
|
||
uint32_t width = images[0].width;
|
||
uint32_t height = images[0].height;
|
||
uint32_t channels = images[0].channel;
|
||
|
||
if (channels != 3 && channels != 4) {
|
||
fprintf(stderr, "Error: Unsupported channel count: %u\n", channels);
|
||
return -1;
|
||
}
|
||
|
||
int mjpg_quality = quality;
|
||
if (mjpg_quality < 1) mjpg_quality = 1;
|
||
if (mjpg_quality > 90) mjpg_quality = 90;
|
||
|
||
size_t audio_pcm_size = 0;
|
||
uint8_t* audio_pcm = sd_audio_to_pcm16_bytes(audio, &audio_pcm_size);
|
||
bool has_audio = audio_pcm != NULL && audio_pcm_size > 0;
|
||
uint16_t audio_bits_per_sample = 16;
|
||
uint16_t audio_block_align = has_audio ? (uint16_t)(audio->channels * (audio_bits_per_sample / 8)) : 0;
|
||
uint32_t audio_byte_rate = has_audio ? (uint32_t)(audio->sample_rate * audio_block_align) : 0;
|
||
uint32_t audio_data_size = has_audio ? (uint32_t)audio_pcm_size : 0;
|
||
|
||
// --- RIFF AVI Header ---
|
||
mem_write(&buf, "RIFF", 4);
|
||
size_t riff_size_pos = buf.size;
|
||
mem_write_u32_le(&buf, 0); // placeholder
|
||
mem_write(&buf, "AVI ", 4);
|
||
|
||
// 'hdrl' LIST
|
||
mem_write(&buf, "LIST", 4);
|
||
uint32_t hdrl_size = 4 + 8 + 56 + 8 + 4 + 8 + 56 + 8 + 40;
|
||
if (has_audio) {
|
||
hdrl_size += 8 + (4 + 8 + 56 + 8 + 16);
|
||
}
|
||
mem_write_u32_le(&buf, hdrl_size);
|
||
mem_write(&buf, "hdrl", 4);
|
||
|
||
// 'avih'
|
||
mem_write(&buf, "avih", 4);
|
||
mem_write_u32_le(&buf, 56);
|
||
mem_write_u32_le(&buf, 1000000 / fps);
|
||
mem_write_u32_le(&buf, 0);
|
||
mem_write_u32_le(&buf, 0);
|
||
mem_write_u32_le(&buf, 0x110);
|
||
mem_write_u32_le(&buf, num_images);
|
||
mem_write_u32_le(&buf, 0);
|
||
mem_write_u32_le(&buf, has_audio ? 2 : 1);
|
||
mem_write_u32_le(&buf, width * height * 3);
|
||
mem_write_u32_le(&buf, width);
|
||
mem_write_u32_le(&buf, height);
|
||
mem_write_u32_le(&buf, 0); mem_write_u32_le(&buf, 0);
|
||
mem_write_u32_le(&buf, 0); mem_write_u32_le(&buf, 0);
|
||
|
||
// 'strl' LIST
|
||
mem_write(&buf, "LIST", 4);
|
||
mem_write_u32_le(&buf, 4 + 8 + 56 + 8 + 40);
|
||
mem_write(&buf, "strl", 4);
|
||
|
||
// 'strh'
|
||
mem_write(&buf, "strh", 4);
|
||
mem_write_u32_le(&buf, 56);
|
||
mem_write(&buf, "vids", 4);
|
||
mem_write(&buf, "MJPG", 4);
|
||
mem_write_u32_le(&buf, 0);
|
||
mem_write_u16_le(&buf, 0);
|
||
mem_write_u16_le(&buf, 0);
|
||
mem_write_u32_le(&buf, 0);
|
||
mem_write_u32_le(&buf, 1);
|
||
mem_write_u32_le(&buf, fps);
|
||
mem_write_u32_le(&buf, 0);
|
||
mem_write_u32_le(&buf, num_images);
|
||
mem_write_u32_le(&buf, width * height * 3);
|
||
mem_write_u32_le(&buf, (uint32_t)-1);
|
||
mem_write_u32_le(&buf, 0);
|
||
mem_write_u16_le(&buf, 0); mem_write_u16_le(&buf, 0);
|
||
mem_write_u16_le(&buf, 0); mem_write_u16_le(&buf, 0);
|
||
|
||
// 'strf'
|
||
mem_write(&buf, "strf", 4);
|
||
mem_write_u32_le(&buf, 40);
|
||
mem_write_u32_le(&buf, 40);
|
||
mem_write_u32_le(&buf, width);
|
||
mem_write_u32_le(&buf, height);
|
||
mem_write_u16_le(&buf, 1);
|
||
mem_write_u16_le(&buf, 24);
|
||
mem_write(&buf, "MJPG", 4);
|
||
mem_write_u32_le(&buf, width * height * 3);
|
||
mem_write_u32_le(&buf, 0);
|
||
mem_write_u32_le(&buf, 0);
|
||
mem_write_u32_le(&buf, 0);
|
||
mem_write_u32_le(&buf, 0);
|
||
|
||
if (has_audio) {
|
||
// audio 'strl' LIST
|
||
mem_write(&buf, "LIST", 4);
|
||
mem_write_u32_le(&buf, 4 + 8 + 56 + 8 + 16);
|
||
mem_write(&buf, "strl", 4);
|
||
|
||
mem_write(&buf, "strh", 4);
|
||
mem_write_u32_le(&buf, 56);
|
||
mem_write(&buf, "auds", 4);
|
||
mem_write_u32_le(&buf, 0);
|
||
mem_write_u32_le(&buf, 0);
|
||
mem_write_u16_le(&buf, 0);
|
||
mem_write_u16_le(&buf, 0);
|
||
mem_write_u32_le(&buf, 0);
|
||
mem_write_u32_le(&buf, audio_block_align);
|
||
mem_write_u32_le(&buf, audio_byte_rate);
|
||
mem_write_u32_le(&buf, 0);
|
||
mem_write_u32_le(&buf, (uint32_t)audio->sample_count);
|
||
mem_write_u32_le(&buf, audio_data_size);
|
||
mem_write_u32_le(&buf, (uint32_t)-1);
|
||
mem_write_u32_le(&buf, audio_block_align);
|
||
mem_write_u16_le(&buf, 0); mem_write_u16_le(&buf, 0);
|
||
mem_write_u16_le(&buf, 0); mem_write_u16_le(&buf, 0);
|
||
|
||
mem_write(&buf, "strf", 4);
|
||
mem_write_u32_le(&buf, 16);
|
||
mem_write_u16_le(&buf, 1);
|
||
mem_write_u16_le(&buf, (uint16_t)audio->channels);
|
||
mem_write_u32_le(&buf, audio->sample_rate);
|
||
mem_write_u32_le(&buf, audio_byte_rate);
|
||
mem_write_u16_le(&buf, audio_block_align);
|
||
mem_write_u16_le(&buf, audio_bits_per_sample);
|
||
}
|
||
|
||
// 'movi' LIST
|
||
mem_write(&buf, "LIST", 4);
|
||
size_t movi_size_pos = buf.size;
|
||
mem_write_u32_le(&buf, 0);
|
||
mem_write(&buf, "movi", 4);
|
||
|
||
avi_index_entry* index = (avi_index_entry*)malloc(sizeof(avi_index_entry) * num_images);
|
||
|
||
// Encode and write each frame
|
||
for (int i = 0; i < num_images; i++) {
|
||
struct {
|
||
uint8_t* buf;
|
||
size_t size;
|
||
} jpeg_data = {NULL, 0};
|
||
|
||
auto write_to_buf = [](void* context, void* data, int size) {
|
||
auto jd = (decltype(jpeg_data)*)context;
|
||
jd->buf = (uint8_t*)realloc(jd->buf, jd->size + size);
|
||
memcpy(jd->buf + jd->size, data, size);
|
||
jd->size += size;
|
||
};
|
||
|
||
stbi_write_jpg_to_func(
|
||
write_to_buf, &jpeg_data,
|
||
images[i].width, images[i].height,
|
||
channels, images[i].data, mjpg_quality
|
||
);
|
||
|
||
mem_write(&buf, "00dc", 4);
|
||
mem_write_u32_le(&buf, jpeg_data.size);
|
||
index[i].offset = buf.size - 8;
|
||
index[i].size = jpeg_data.size;
|
||
mem_write(&buf, jpeg_data.buf, jpeg_data.size);
|
||
if (jpeg_data.size % 2) mem_write(&buf, "\0", 1);
|
||
|
||
free(jpeg_data.buf);
|
||
}
|
||
|
||
uint32_t audio_offset = 0;
|
||
if (has_audio) {
|
||
mem_write(&buf, "01wb", 4);
|
||
mem_write_u32_le(&buf, audio_data_size);
|
||
audio_offset = (uint32_t)(buf.size - 8);
|
||
mem_write(&buf, audio_pcm, audio_pcm_size);
|
||
if (audio_pcm_size % 2) mem_write(&buf, "\0", 1);
|
||
}
|
||
|
||
// finalize movi size
|
||
uint32_t movi_size = buf.size - movi_size_pos - 4;
|
||
memcpy(buf.data + movi_size_pos, &movi_size, 4);
|
||
|
||
// write idx1
|
||
mem_write(&buf, "idx1", 4);
|
||
mem_write_u32_le(&buf, (num_images + (has_audio ? 1 : 0)) * 16);
|
||
for (int i = 0; i < num_images; i++) {
|
||
mem_write(&buf, "00dc", 4);
|
||
mem_write_u32_le(&buf, 0x10);
|
||
mem_write_u32_le(&buf, index[i].offset);
|
||
mem_write_u32_le(&buf, index[i].size);
|
||
}
|
||
if (has_audio) {
|
||
mem_write(&buf, "01wb", 4);
|
||
mem_write_u32_le(&buf, 0);
|
||
mem_write_u32_le(&buf, audio_offset);
|
||
mem_write_u32_le(&buf, audio_data_size);
|
||
}
|
||
|
||
// finalize RIFF size
|
||
uint32_t riff_size = buf.size - riff_size_pos - 4;
|
||
memcpy(buf.data + riff_size_pos, &riff_size, 4);
|
||
|
||
free(index);
|
||
free(audio_pcm);
|
||
|
||
*out_data = buf.data;
|
||
*out_len = buf.size;
|
||
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_msf(sd_image_t* images, int num_images, int fps, 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
|
||
|
||
// assume all images same size; use first
|
||
uint32_t width = images[0].width;
|
||
uint32_t height = images[0].height;
|
||
|
||
int centisecondsPerFrame = delay;
|
||
int quality = 16;
|
||
MsfGifState gifState = {};
|
||
msf_gif_begin(&gifState, width, height);
|
||
|
||
// 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);
|
||
return -1;
|
||
}
|
||
|
||
// gif 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);
|
||
return -1;
|
||
}
|
||
|
||
// gif 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
|
||
}
|
||
|
||
int res = msf_gif_frame(&gifState, frame_rgba, centisecondsPerFrame, quality, width * 4); //frame
|
||
if(!res)
|
||
{
|
||
fprintf(stderr, "GIF Write Failed\n");
|
||
return -1;
|
||
}
|
||
|
||
if (channels == 3) {
|
||
free(frame_rgba);
|
||
}
|
||
}
|
||
|
||
MsfGifResult result = msf_gif_end(&gifState);
|
||
*out_data = (uint8_t*)result.data;
|
||
*out_len = result.dataSize;
|
||
return 0;
|
||
}
|
||
|
||
#endif // __AVI_WRITER_H__
|