Skip to content

Commit

Permalink
nesbox#2218: new GIF encoder
Browse files Browse the repository at this point in the history
  • Loading branch information
nesbox committed Aug 16, 2023
1 parent b43d814 commit cff7ee5
Show file tree
Hide file tree
Showing 11 changed files with 70 additions and 326 deletions.
4 changes: 4 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,7 @@
path = vendor/naett
url = https://github.com/erkkah/naett.git
shallow = true
[submodule "vendor/msf_gif"]
path = vendor/msf_gif
url = https://github.com/notnullnotvoid/msf_gif.git
shallow = true
7 changes: 4 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,9 @@ set(GIFLIB_SRC
add_library(giflib STATIC ${GIFLIB_SRC})
target_include_directories(giflib
PRIVATE ${GIFLIB_DIR}
INTERFACE ${THIRDPARTY_DIR}/giflib)
INTERFACE
${THIRDPARTY_DIR}/giflib
${THIRDPARTY_DIR}/msf_gif)

################################
# Blipbuf
Expand Down Expand Up @@ -893,12 +895,11 @@ if(BUILD_DEMO_CARTS)

add_executable(xplode
${TOOLS_DIR}/xplode.c
${CMAKE_SOURCE_DIR}/src/ext/gif.c
${CMAKE_SOURCE_DIR}/src/ext/png.c
${CMAKE_SOURCE_DIR}/src/studio/project.c)

target_include_directories(xplode PRIVATE ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/include)
target_link_libraries(xplode tic80core png giflib)
target_link_libraries(xplode tic80core png)

if(LINUX)
target_link_libraries(xplode m)
Expand Down
2 changes: 1 addition & 1 deletion build/assets/config.tic.dat

Large diffs are not rendered by default.

20 changes: 0 additions & 20 deletions build/tools/xplode.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@

#include "cart.h"
#include "tools.h"
#include "ext/gif.h"
#include "ext/png.h"
#include "studio/project.h"

Expand Down Expand Up @@ -84,25 +83,6 @@ s32 main(s32 argc, char** argv)
tic_cart_load(cart, buffer.data, buffer.size);
free(buffer.data);

// export cover.gif
{
FileBuffer buffer = {TIC80_WIDTH * TIC80_HEIGHT * sizeof(u32), malloc(buffer.size)};

u8* data = malloc(TIC80_WIDTH * TIC80_HEIGHT);
for(s32 i = 0; i < TIC80_WIDTH * TIC80_HEIGHT; i++)
data[i] = tic_tool_peek4(cart->bank0.screen.data, i);

if(gif_write_data(buffer.data, &buffer.size, TIC80_WIDTH, TIC80_HEIGHT, data, (gif_color*)cart->bank0.palette.vbank0.colors, TIC_PALETTE_BPP))
{
writeFile("cover.gif", buffer);
printf("cover.gif successfully exported\n");
}
else printf("cannot extract gif cover\n");

free(data);
free(buffer.data);
}

// export cover.png
{
png_img img = {TIC80_WIDTH, TIC80_HEIGHT, malloc(TIC80_WIDTH * TIC80_HEIGHT * sizeof(png_rgba))};
Expand Down
2 changes: 0 additions & 2 deletions config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@ THEME=
CHECK_NEW_VERSION=true
SOFTWARE_RENDERING=false
UI_SCALE=4
GIF_LENGTH=20 -- in seconds
GIF_SCALE=3

---------------------------
function TIC()
Expand Down
224 changes: 0 additions & 224 deletions src/ext/gif.c
Original file line number Diff line number Diff line change
Expand Up @@ -185,66 +185,6 @@ gif_image* gif_read_data(const void* data, s32 size)
return readGif(gif);
}

static bool writeGif(GifFileType* gif, s32 width, s32 height, const u8* data, const gif_color* palette, u8 bpp)
{
bool result = false;
s32 error = 0;

if(gif)
{
s32 colors = 1 << bpp;
ColorMapObject* colorMap = GifMakeMapObject(colors, NULL);

memcpy(colorMap->Colors, palette, colors * sizeof(GifColorType));

if(EGifPutScreenDesc(gif, width, height, bpp, 0, colorMap) != GIF_ERROR)
{
if(EGifPutImageDesc(gif, 0, 0, width, height, false, NULL) != GIF_ERROR)
{
GifByteType* ptr = (GifByteType*)data;
for (s32 i = 0; i < height; i++, ptr += width)
{
if (EGifPutLine(gif, ptr, width) == GIF_ERROR)
{
error = gif->Error;
break;
}
}

result = error == E_GIF_SUCCEEDED;
}
}

EGifCloseFile(gif, &error);
GifFreeMapObject(colorMap);
}

return result;
}

static s32 writeBuffer(GifFileType* gif, const GifByteType* data, s32 size)
{
GifBuffer* buffer = (GifBuffer*)gif->UserData;

memcpy((u8*)buffer->data + buffer->pos, data, size);
buffer->pos += size;

return size;
}

bool gif_write_data(const void* buffer, s32* size, s32 width, s32 height, const u8* data, const gif_color* palette, u8 bpp)
{
s32 error = 0;
GifBuffer output = {buffer, 0};
GifFileType* gif = EGifOpen(&output, writeBuffer, &error);

bool result = writeGif(gif, width, height, data, palette, bpp);

*size = output.pos;

return result;
}

void gif_close(gif_image* image)
{
if(image)
Expand All @@ -255,167 +195,3 @@ void gif_close(gif_image* image)
free(image);
}
}

static bool AddLoop(GifFileType *gif)
{
{
const char *nsle = "NETSCAPE2.0";
const char subblock[] = {
1, // always 1
0, // little-endian loop counter:
0 // 0 for infinite loop.
};

EGifPutExtensionLeader(gif, APPLICATION_EXT_FUNC_CODE);
EGifPutExtensionBlock(gif, 11, nsle);
EGifPutExtensionBlock(gif, 3, subblock);
EGifPutExtensionTrailer(gif);
}

return true;
}

static const u8* toColor(const u8* ptr, gif_color* color)
{
color->r = *ptr++;
color->g = *ptr++;
color->b = *ptr++;
ptr++;

return ptr;
}

bool gif_write_animation(const void* buffer, s32* size, s32 width, s32 height, const u8* data, s32 frames, s32 fps, s32 scale)
{
bool result = false;

s32 swidth = width*scale, sheight = height*scale;
s32 frameSize = width * height;

enum{Bpp = 8, PalSize = 1 << Bpp, PalStructSize = PalSize * sizeof(gif_color)};

s32 error = 0;
GifBuffer output = {buffer, 0};
GifFileType* gif = EGifOpen(&output, writeBuffer, &error);

if(gif)
{
EGifSetGifVersion(gif, true);

if(EGifPutScreenDesc(gif, swidth, sheight, Bpp, 0, NULL) != GIF_ERROR)
{
if(AddLoop(gif))
{
gif_color* palette = (gif_color*)malloc(PalStructSize);
u8* screen = malloc(frameSize);
u8* line = malloc(swidth);

for(s32 f = 0; f < frames; f++)
{
enum {DelayUnits = 100, MinDelay = 2};

s32 frame = (f * fps * MinDelay * 2 + 1) / (2 * DelayUnits);

if(frame >= frames)
break;

s32 colors = 0;
const u8* ptr = data + frameSize*frame*sizeof(u32);

{
memset(palette, 0, PalStructSize);
memset(screen, 0, frameSize);

for(s32 i = 0; i < frameSize; i++)
{
if(colors >= PalSize) break;

gif_color color;
toColor(ptr + i*sizeof(u32), &color);

bool found = false;
for(s32 c = 0; c < colors; c++)
{
if(memcmp(&palette[c], &color, sizeof(gif_color)) == 0)
{
found = true;
screen[i] = c;
break;
}
}

if(!found)
{
// TODO: check for last color in palette and try to find closest color
screen[i] = colors;
memcpy(&palette[colors], &color, sizeof(gif_color));
colors++;
}
}
}

{
GraphicsControlBlock gcb =
{
.DisposalMode = DISPOSE_DO_NOT,
.UserInputFlag = false,
.DelayTime = MinDelay,
.TransparentColor = -1,
};

u8 ext[4];
EGifGCBToExtension(&gcb, ext);
EGifPutExtension(gif, GRAPHICS_EXT_FUNC_CODE, sizeof ext, ext);
}

ColorMapObject* colorMap = GifMakeMapObject(PalSize, NULL);
memset(colorMap->Colors, 0, PalStructSize);
memcpy(colorMap->Colors, palette, colors * sizeof(GifColorType));

if(EGifPutImageDesc(gif, 0, 0, swidth, sheight, false, colorMap) != GIF_ERROR)
{
for(s32 y = 0; y < height; y++)
{
for(s32 x = 0, pos = y*width; x < width; x++, pos++)
{
u8 color = screen[pos];
for(s32 s = 0, pos = x*scale; s < scale; s++, pos++)
line[pos] = color;
}

for(s32 s = 0; s < scale; s++)
{
if (EGifPutLine(gif, line, swidth) == GIF_ERROR)
{
error = gif->Error;
break;
}
}

if(error != E_GIF_SUCCEEDED) break;
}

*size = output.pos;

result = error == E_GIF_SUCCEEDED;
}

GifFreeMapObject(colorMap);

if(!result)
break;
}

free(line);
free(screen);
free(palette);
}
}

EGifCloseFile(gif, &error);

*size = output.pos;
}

return result;
}
2 changes: 0 additions & 2 deletions src/ext/gif.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,4 @@ typedef struct
} gif_image;

gif_image* gif_read_data(const void* buffer, s32 size);
bool gif_write_data(const void* buffer, s32* size, s32 width, s32 height, const u8* data, const gif_color* palette, u8 bpp);
bool gif_write_animation(const void* buffer, s32* size, s32 width, s32 height, const u8* data, s32 frames, s32 fps, s32 scale);
void gif_close(gif_image* image);
5 changes: 3 additions & 2 deletions src/studio/config.c
Original file line number Diff line number Diff line change
Expand Up @@ -201,12 +201,13 @@ static void readConfig(Config* config)
{
if(luaL_loadstring(lua, config->cart->code.data) == LUA_OK && lua_pcall(lua, 0, LUA_MULTRET, 0) == LUA_OK)
{
readGlobalInteger(lua, "GIF_LENGTH", &config->data.gifLength);
readGlobalInteger(lua, "GIF_SCALE", &config->data.gifScale);
readGlobalBool(lua, "CHECK_NEW_VERSION", &config->data.checkNewVersion);
readGlobalInteger(lua, "UI_SCALE", &config->data.uiScale);
readGlobalBool(lua, "SOFTWARE_RENDERING", &config->data.soft);

if(config->data.uiScale <= 0)
config->data.uiScale = 1;

#if defined(CRT_SHADER_SUPPORT)
readConfigCrtShader(config, lua);
#endif
Expand Down
Loading

0 comments on commit cff7ee5

Please sign in to comment.