// SPDX-License-Identifier: GPL-3.0-only // Copyright (c) 2022, Sylvain Huet, Ambermind // Minimacy (r) System struct GifFrame=[frameDataGF];; const GRAPHICS_EXT_FUNC_CODE =0xf9;; // graphics control const APPLICATION_EXT_FUNC_CODE =0xff;; // application block fun _mkBlocks(i, data)= if i>=strLength(data) then [strInt8(0), ""]::nil else let min(255, strLength(data)-i) -> blockLen in [ strInt8(blockLen), strSlice(data, i, blockLen) ]::_mkBlocks(i+blockLen, data);; fun _gifMakeFrame(x, y, w, h, data)= [frameDataGF= [ ",", strInt16Lsb(x), strInt16Lsb(y), strInt16Lsb(w), strInt16Lsb(h), strInt8(7), // bpp-1 (1:Lut|1:Interlaced|3:unused|3:bpp-1) strInt8(8), // bpp _mkBlocks(0, lzwFromStr(data, 8)) ]];; fun _gifMakeFinal(w, h, palette, data)= strBuild([ "GIF89a", strInt16Lsb(w), strInt16Lsb(h), strInt8(0xf7), // 1:Lut|3:bpp-1|1:0|3:bpp-1 strInt8(0), // background color index strInt8(0), arrayInit(256, lambda(i) = strInt24Msb(palette[i])), data, ";" ]);; const NETSCAPE2_0="NETSCAPE2.0";; fun netscapeBlock()= [ // this block is mandatory for animated gifs, else the animation doesn't loop "!", strInt8(APPLICATION_EXT_FUNC_CODE), strInt8(strLength(NETSCAPE2_0)), // length NETSCAPE2_0, _mkBlocks(0, strBuild([ strInt8(1), // always 1 strInt16Lsb(0) // repeat number. 0 means never stop ])) ];; fun durationBlock(durationMs, transparency)= [ "!", strInt8(GRAPHICS_EXT_FUNC_CODE), _mkBlocks(0, strBuild([ strInt8(if transparency==nil then 0 else 1), strInt16Lsb(max(2, durationMs/10)), // values 0 or 1 are usually interpreted as default (100ms or 20ms) strInt8(if transparency==nil then 0xff else transparency) //transparency index ])) ];; const _PAL8= arrayInit(8, lambda(i)= i*255/7);; const _PAL4= arrayInit(4, lambda(i)= i*255/3);; fun gifStdPalette()= arrayInit(256, lambda(i) = (_PAL4[i>>6]<<16) | (_PAL8[(i>>3)&7]<<8) | _PAL8[i&7]);; fun gifStdProject(rgb)= ((rgb>>16)&0xc0) | ((rgb>>10)&0x38) | ((rgb>>5)&0x7);; export fun gifStdPalettize(bmp, x, y) = let bitmapW(bmp) -> w in let bitmapH(bmp) -> h in let bytesCreate(w*h, 0) -> data in ( for y=0; y w in let bitmapH(bmp) -> h in let gifStdPalettize(bmp, 0, 0) -> data in _gifMakeFinal(w, h, gifStdPalette(), data.frameDataGF);; export fun gifFromFrames(w, h, lFrames, transparency)= let if transparency<>nil then gifStdProject(transparency) -> transparencyIndex in let listMap(lFrames, lambda([frame, durationMs])=[ durationBlock(durationMs, transparencyIndex), frame.frameDataGF ]) -> data in _gifMakeFinal(w, h, gifStdPalette(), [netscapeBlock(), data]);;