mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-07-10 22:24:33 +00:00
1765 lines
34 KiB
C
1765 lines
34 KiB
C
/*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <malloc.h>
|
|
#include <zlib.h>
|
|
#include <libmng.h>
|
|
|
|
#ifdef WIN32
|
|
# include <getopt.h>
|
|
#else
|
|
# include <unistd.h>
|
|
#endif
|
|
|
|
|
|
struct options
|
|
{
|
|
char *inmask;
|
|
char *inpath;
|
|
char *outfile;
|
|
int framerate;
|
|
char verbose;
|
|
char backimage;
|
|
char deltamask;
|
|
int sectorsize;
|
|
int fullrects;
|
|
} opts;
|
|
|
|
// externals
|
|
char** make_file_list (const char* pattern, int* pnum_entries);
|
|
void free_file_list (char** list);
|
|
|
|
// internals
|
|
int error (int errn, const char* fmt, const char* s);
|
|
void verbose (const char* fmt, const char* s);
|
|
void verbose_d (const char* fmt, int val);
|
|
void parse_arguments (int argc, char *argv[], struct options *opts);
|
|
int read_file_list (void);
|
|
void calc_mng_dims (void);
|
|
void select_back_image (void);
|
|
void delta_images (void);
|
|
int write_mng_file (void);
|
|
|
|
typedef union
|
|
{
|
|
struct
|
|
{
|
|
unsigned char r, g, b, a;
|
|
} bchan;
|
|
unsigned char channels[4];
|
|
unsigned int value;
|
|
} RGBA;
|
|
|
|
typedef struct _file_info
|
|
{
|
|
FILE* f;
|
|
char* fname;
|
|
char* fmode;
|
|
int w, h;
|
|
int x, y;
|
|
RGBA* image;
|
|
unsigned char* indimg;
|
|
int delay;
|
|
int identical;
|
|
unsigned short objid;
|
|
unsigned short cloneid;
|
|
int clone;
|
|
unsigned short precloneid;
|
|
int preclone;
|
|
struct _file_info* next;
|
|
} file_info;
|
|
|
|
void file_info_free ();
|
|
|
|
// MNG callbacks
|
|
mng_ptr MNG_DECL mng_alloc (mng_size_t iLen);
|
|
void MNG_DECL mng_free (mng_ptr pPtr, mng_size_t iLen);
|
|
mng_bool MNG_DECL mng_open_stream(mng_handle mng);
|
|
mng_bool MNG_DECL mng_close_stream(mng_handle mng);
|
|
mng_bool MNG_DECL mng_read_stream(mng_handle mng, mng_ptr buffer, mng_uint32 size, mng_uint32p bytes);
|
|
mng_bool MNG_DECL mng_write_stream (mng_handle mng, mng_ptr buffer, mng_uint32 size, mng_uint32p bytes);
|
|
mng_bool MNG_DECL mng_process_header(mng_handle mng, mng_uint32 width, mng_uint32 height);
|
|
mng_ptr MNG_DECL mng_get_canvasline_read(mng_handle mng, mng_uint32 line);
|
|
mng_ptr MNG_DECL mng_get_canvasline_write(mng_handle mng, mng_uint32 line);
|
|
mng_bool MNG_DECL mng_refresh_display(mng_handle mng, mng_uint32 x, mng_uint32 y, mng_uint32 w, mng_uint32 h);
|
|
mng_uint32 MNG_DECL mng_get_tickcount(mng_handle mng);
|
|
mng_bool MNG_DECL mng_set_timer(mng_handle mng, mng_uint32 msecs);
|
|
|
|
#define MAX_COLORS 0x100
|
|
|
|
// global png/mng data
|
|
char** Files = 0;
|
|
int cFiles = 0;
|
|
file_info* Infos = 0;
|
|
mng_palette8 Pal;
|
|
int cPalClr = 0;
|
|
int mngw = 0, mngh = 0;
|
|
int iback = 0;
|
|
int timerate = 100; // default 100 ticks per second
|
|
int framedelay = 20; // default 5 fps
|
|
int _curframe = -1;
|
|
int _curdeltaframe = -1;
|
|
|
|
int
|
|
main(int argc, char* argv[])
|
|
{
|
|
int ret = 0;
|
|
|
|
parse_arguments (argc, argv, &opts);
|
|
|
|
if (opts.framerate) // update delay
|
|
framedelay = timerate / opts.framerate;
|
|
|
|
if (!opts.inpath && (strchr (opts.inmask, '/') || strchr (opts.inmask, '\\')))
|
|
{
|
|
char *pch1, *pch2;
|
|
|
|
opts.inpath = (char*) calloc (strlen (opts.inmask), 1);
|
|
if (!opts.inpath)
|
|
return error (EXIT_FAILURE, "No memory", 0);
|
|
|
|
strcpy (opts.inpath, opts.inmask);
|
|
pch1 = strrchr (opts.inpath, '/');
|
|
pch2 = strrchr (opts.inpath, '\\');
|
|
if (pch2 > pch1)
|
|
pch1 = pch2;
|
|
|
|
pch1[1] = 0; // term the path
|
|
|
|
verbose ("Frame files in dir: %s\n", opts.inpath);
|
|
}
|
|
|
|
if (!opts.outfile)
|
|
{
|
|
char* pch;
|
|
|
|
opts.outfile = (char*) calloc (strlen(opts.inmask) + 6, 1);
|
|
if (!opts.outfile)
|
|
return error (EXIT_FAILURE, "No memory", 0);
|
|
|
|
strcpy (opts.outfile, opts.inmask);
|
|
while ((pch = strchr (opts.outfile, '*')) != 0)
|
|
strcpy (pch, pch + 1);
|
|
|
|
pch = strstr (opts.outfile, ".png");
|
|
if (!pch)
|
|
pch = opts.outfile + strlen (opts.outfile);
|
|
strcpy (pch, ".mng");
|
|
|
|
if (pch == opts.outfile || pch[-1] == '/' || pch[-1] == '\\')
|
|
{ // have to fix blank name
|
|
memmove (pch + 1, pch, strlen(pch) + 1);
|
|
*pch = '1';
|
|
}
|
|
|
|
verbose ("Output file: %s\n", opts.outfile);
|
|
}
|
|
|
|
fprintf (stderr, "using timerate of %d and framedelay of %d\n", timerate, framedelay);
|
|
|
|
ret = read_file_list ();
|
|
if (!ret)
|
|
{
|
|
calc_mng_dims ();
|
|
if (opts.backimage)
|
|
select_back_image ();
|
|
delta_images ();
|
|
|
|
ret = write_mng_file ();
|
|
}
|
|
|
|
file_info_free ();
|
|
free_file_list (Files);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
error(int errn, const char* fmt, const char* s)
|
|
{
|
|
fprintf(stderr, fmt, s);
|
|
return errn;
|
|
}
|
|
|
|
void
|
|
verbose(const char* fmt, const char* s)
|
|
{
|
|
if (!opts.verbose)
|
|
return;
|
|
|
|
fprintf(stderr, fmt, s);
|
|
}
|
|
|
|
void verbose_d (const char* fmt, int val)
|
|
{
|
|
if (!opts.verbose)
|
|
return;
|
|
|
|
fprintf(stderr, fmt, val);
|
|
}
|
|
|
|
void
|
|
usage()
|
|
{
|
|
fprintf(stderr, "usage: makemng [-v] [-f rate] [-r] [-s size] [-o outputfile] <png-in-mask>\n");
|
|
fprintf(stderr, "produces an MNG animation from a bunch of frame images\n");
|
|
fprintf(stderr, "options:\n");
|
|
fprintf(stderr, " -v\t\t : be verbose, explains things no human should know\n");
|
|
fprintf(stderr, " -f rate\t : sets the framerate; rate is 1..100 per second (default 5)\n");
|
|
fprintf(stderr, " -b\t\t : auto-select background frame (instead of frame0)\n");
|
|
fprintf(stderr, " -r\t\t : split delta frames into full rectangles only\n");
|
|
fprintf(stderr, " -s size\t : enable sector cleanup and set sector size (8..64)\n");
|
|
fprintf(stderr, "diagnostical options:\n");
|
|
fprintf(stderr, " -d\t\t : generate delta-mask PNGs (form: mask_FRM1_FRM2.png)\n");
|
|
}
|
|
|
|
void
|
|
parse_arguments(int argc, char *argv[], struct options *opts)
|
|
{
|
|
char ch;
|
|
|
|
memset(opts, '\0', sizeof (struct options));
|
|
while ((ch = getopt(argc, argv, "?hvbdrf:s:o:")) != -1)
|
|
{
|
|
switch(ch)
|
|
{
|
|
case 'o':
|
|
opts->outfile = optarg;
|
|
break;
|
|
case 'f':
|
|
opts->framerate = atoi(optarg);
|
|
if (opts->framerate < 1 || opts->framerate > 100)
|
|
{
|
|
fprintf(stderr, "invalid -f option value\n");
|
|
usage();
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
break;
|
|
case 'd':
|
|
opts->deltamask = 1;
|
|
break;
|
|
case 'b':
|
|
opts->backimage = 1;
|
|
break;
|
|
case 'r':
|
|
opts->fullrects = 1;
|
|
break;
|
|
case 's':
|
|
opts->sectorsize = atoi(optarg);
|
|
if (opts->sectorsize < 8 || opts->sectorsize > 64)
|
|
{
|
|
fprintf(stderr, "invalid -r option value\n");
|
|
usage();
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
break;
|
|
case 'v':
|
|
opts->verbose = 1;
|
|
break;
|
|
case '?':
|
|
case 'h':
|
|
default:
|
|
usage();
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
argc -= optind;
|
|
argv += optind;
|
|
if (argc != 1)
|
|
{
|
|
usage();
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
opts->inmask = argv[0];
|
|
}
|
|
|
|
void
|
|
make_file_name(int index, char* buf)
|
|
{
|
|
if (opts.inpath)
|
|
strcpy(buf, opts.inpath);
|
|
else
|
|
*buf = 0;
|
|
|
|
strcat(buf, Files[index]);
|
|
}
|
|
|
|
void
|
|
file_info_init (file_info* ms)
|
|
{
|
|
memset(ms, 0, sizeof(*ms));
|
|
ms->identical = -1;
|
|
ms->clone = -1;
|
|
ms->preclone = -1;
|
|
}
|
|
|
|
void
|
|
file_info_cleanup (file_info* ms)
|
|
{
|
|
file_info* fi = ms;
|
|
|
|
while (fi)
|
|
{
|
|
file_info* tempi = fi;
|
|
|
|
if (fi->image)
|
|
{
|
|
free(fi->image);
|
|
fi->image = 0;
|
|
}
|
|
|
|
if (fi->indimg)
|
|
{
|
|
free(fi->indimg);
|
|
fi->indimg = 0;
|
|
}
|
|
|
|
if (fi->f)
|
|
{
|
|
fclose(fi->f);
|
|
fi->f = 0;
|
|
}
|
|
|
|
|
|
fi = fi->next;
|
|
if (tempi != ms)
|
|
free (tempi);
|
|
}
|
|
}
|
|
|
|
void
|
|
file_info_free ()
|
|
{
|
|
int i;
|
|
|
|
if (Infos)
|
|
{
|
|
for (i = 0; i < cFiles; i++)
|
|
file_info_cleanup (Infos + i);
|
|
|
|
free (Infos);
|
|
Infos = 0;
|
|
}
|
|
}
|
|
|
|
int
|
|
equal_colors (RGBA rgba, mng_palette8e mng_clr)
|
|
{
|
|
return rgba.bchan.r == mng_clr.iRed &&
|
|
rgba.bchan.g == mng_clr.iGreen &&
|
|
rgba.bchan.b == mng_clr.iBlue;
|
|
}
|
|
|
|
int
|
|
lookup_palette (RGBA rgba)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < cPalClr && !equal_colors(rgba, Pal[i]); i++)
|
|
;
|
|
|
|
return i < cPalClr ? i : -1;
|
|
}
|
|
|
|
int
|
|
update_palette (file_info* ms)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ms->w * ms->h; i++)
|
|
{
|
|
RGBA rgba = ms->image[i];
|
|
int ipal = lookup_palette (rgba);
|
|
if (ipal == -1)
|
|
{ // add color
|
|
if (cPalClr >= MAX_COLORS)
|
|
return 1;
|
|
|
|
Pal[cPalClr].iRed = rgba.bchan.r;
|
|
Pal[cPalClr].iGreen = rgba.bchan.g;
|
|
Pal[cPalClr].iBlue = rgba.bchan.b;
|
|
cPalClr++;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
convert_image_indexed (file_info* ms)
|
|
{
|
|
int i;
|
|
|
|
ms->indimg = (unsigned char*) malloc (ms->w * ms->h);
|
|
if (!ms->indimg)
|
|
return 230;
|
|
|
|
for (i = 0; i < ms->w * ms->h; i++)
|
|
{
|
|
int ipal = lookup_palette (ms->image[i]);
|
|
|
|
if (ipal == -1)
|
|
return 1; // something is screwed
|
|
|
|
ms->indimg[i] = ipal;
|
|
}
|
|
|
|
free (ms->image);
|
|
ms->image = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
read_file_list (void)
|
|
{
|
|
int ret = 0;
|
|
mng_handle mng;
|
|
char namebuf[260];
|
|
int i;
|
|
|
|
cFiles = 0;
|
|
Files = make_file_list(opts.inmask, &cFiles);
|
|
|
|
if (!Files || cFiles == 0)
|
|
{
|
|
fprintf (stderr, "No frame files found\n");
|
|
return 1;
|
|
}
|
|
|
|
Infos = (file_info*) malloc (sizeof(file_info) * cFiles);
|
|
if (!Infos)
|
|
return 251;
|
|
|
|
memset(Infos, 0, sizeof(file_info) * cFiles);
|
|
|
|
mng = mng_initialize (MNG_NULL, mng_alloc, mng_free, MNG_NULL);
|
|
if (mng == MNG_NULL)
|
|
return 250;
|
|
|
|
// set the callbacks
|
|
mng_setcb_openstream(mng, mng_open_stream);
|
|
mng_setcb_closestream(mng, mng_close_stream);
|
|
mng_setcb_readdata(mng, mng_read_stream);
|
|
mng_setcb_processheader(mng, mng_process_header);
|
|
mng_setcb_getcanvasline(mng, mng_get_canvasline_read);
|
|
mng_setcb_gettickcount(mng, mng_get_tickcount);
|
|
mng_setcb_settimer(mng, mng_set_timer);
|
|
mng_setcb_refresh(mng, mng_refresh_display);
|
|
|
|
for (i = 0; i < cFiles && !ret; i++)
|
|
{
|
|
file_info* rf = Infos + i;
|
|
|
|
file_info_init (rf);
|
|
make_file_name (i, namebuf);
|
|
rf->fname = namebuf;
|
|
rf->fmode = "rb";
|
|
|
|
verbose_d ("%03d ", i); verbose ("reading '%s'...", rf->fname);
|
|
|
|
mng_reset (mng);
|
|
mng_set_userdata (mng, rf);
|
|
|
|
for (ret = mng_readdisplay (mng);
|
|
ret == MNG_NEEDMOREDATA || ret == MNG_NEEDTIMERWAIT;
|
|
ret = mng_display_resume (mng))
|
|
{
|
|
if (ret == MNG_NEEDTIMERWAIT)
|
|
rf->delay = 0;
|
|
}
|
|
|
|
if (ret)
|
|
{
|
|
fprintf (stderr, "Could not read '%s'\n", rf->fname);
|
|
ret = 2;
|
|
}
|
|
|
|
ret = update_palette (rf);
|
|
if (ret)
|
|
{
|
|
fprintf (stderr, "Too many unique colors (%d processed), giving up\n", i);
|
|
ret = 3;
|
|
}
|
|
|
|
ret = convert_image_indexed (rf);
|
|
if (ret)
|
|
{
|
|
fprintf (stderr, "Image conversion failed on '%s'\n", rf->fname);
|
|
ret = 4;
|
|
}
|
|
|
|
verbose (" done\n", 0);
|
|
}
|
|
|
|
mng_cleanup (&mng);
|
|
|
|
if (ret == MNG_NOERROR)
|
|
fprintf (stderr, "%d animation frames\n", cFiles);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void
|
|
calc_mng_dims (void)
|
|
{
|
|
int i;
|
|
|
|
mngw = mngh = -1;
|
|
|
|
// get max dims
|
|
for (i = 0; i < cFiles; i++)
|
|
{
|
|
if (Infos[i].w > mngw)
|
|
mngw = Infos[i].w;
|
|
|
|
if (Infos[i].h > mngh)
|
|
mngh = Infos[i].h;
|
|
}
|
|
|
|
// adjust images - center
|
|
for (i = 0; i < cFiles; i++)
|
|
{
|
|
if (Infos[i].w < mngw)
|
|
Infos[i].x = (mngw - Infos[i].w) >> 1;
|
|
|
|
if (Infos[i].h < mngh)
|
|
Infos[i].y = (mngh - Infos[i].h) >> 1;
|
|
}
|
|
}
|
|
|
|
int
|
|
compare_images (file_info* i1, file_info* i2)
|
|
{
|
|
int cnt = 0;
|
|
int w, h, x, y;
|
|
int dx1, dx2, dy1, dy2;
|
|
|
|
if (i1->w > i2->w)
|
|
{
|
|
w = i2->w;
|
|
dx1 = i2->x;
|
|
dx2 = 0;
|
|
}
|
|
else if (i1->w < i2->w)
|
|
{
|
|
w = i1->w;
|
|
dx1 = 0;
|
|
dx2 = i1->x;
|
|
}
|
|
else
|
|
{
|
|
w = i1->w;
|
|
dx1 = dx2 = 0;
|
|
}
|
|
|
|
if (i1->h > i2->h)
|
|
{
|
|
h = i2->h;
|
|
dy1 = i2->y;
|
|
dy2 = 0;
|
|
}
|
|
else if (i1->h < i2->h)
|
|
{
|
|
h = i1->h;
|
|
dy1 = 0;
|
|
dy2 = i1->y;
|
|
}
|
|
else
|
|
{
|
|
h = i1->h;
|
|
dy1 = dy2 = 0;
|
|
}
|
|
|
|
for (y = 0; y < h; y++)
|
|
{
|
|
for (x = 0; x < w; x++)
|
|
{
|
|
if (i1->indimg[(y + dy1) * i1->w + x + dx1] != i2->indimg[(y + dy2) * i2->w + x + dx2])
|
|
cnt++;
|
|
}
|
|
}
|
|
return cnt;
|
|
}
|
|
|
|
void
|
|
select_back_image (void)
|
|
{
|
|
int i;
|
|
int* cdiff;
|
|
int max;
|
|
|
|
cdiff = (int*) calloc (cFiles, sizeof(int));
|
|
if (!cdiff)
|
|
return;
|
|
|
|
verbose ("selecting optimal background image...", 0);
|
|
|
|
for (i = 2; i < cFiles; i++)
|
|
{
|
|
if (Infos[i].w == mngw && Infos[i].h == mngh)
|
|
{
|
|
cdiff[i] = compare_images (Infos + i, Infos + i - 1) -
|
|
compare_images (Infos + i, Infos + 0);
|
|
}
|
|
else
|
|
{
|
|
// image is smaller than animation and cannot be background
|
|
cdiff[i] = 0x7fffffff;
|
|
}
|
|
}
|
|
|
|
// the difference has to be big enough
|
|
// or it will be useless
|
|
iback = 0;
|
|
max = mngw * mngh / 32;
|
|
|
|
for (i = 2; i < cFiles; i++)
|
|
{
|
|
if (cdiff[i] > max)
|
|
{
|
|
iback = i;
|
|
max = cdiff[i];
|
|
}
|
|
}
|
|
|
|
verbose (" done\n", 0);
|
|
fprintf(stderr, "frame %03d selected as background\n", iback);
|
|
}
|
|
|
|
int
|
|
equal_images (int i1, int i2)
|
|
{
|
|
// deference identical chain
|
|
while (Infos[i1].identical != -1)
|
|
i1 = Infos[i1].identical;
|
|
while (Infos[i2].identical != -1)
|
|
i2 = Infos[i2].identical;
|
|
|
|
if (i1 == i2)
|
|
return 1;
|
|
|
|
if (Infos[i1].x != Infos[i2].x || Infos[i1].y != Infos[i2].y
|
|
|| Infos[i1].w != Infos[i2].w || Infos[i1].h != Infos[i2].h)
|
|
return 0;
|
|
|
|
return compare_images (Infos + i1, Infos + i2) == 0;
|
|
}
|
|
|
|
int
|
|
delta_adjust_positions (int* pos1, int* pos2)
|
|
{
|
|
if (*pos1 <= *pos2)
|
|
return 1;
|
|
else
|
|
{
|
|
(*pos1)--;
|
|
(*pos2)--;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
void
|
|
clean_expansion_horz (unsigned char* mask, int w, int x1, int y1, int x2, int y2, int threshold)
|
|
{
|
|
int x, y, dx, dy;
|
|
// assume anything out of bounds is cleared
|
|
int prevclear = threshold + 1;
|
|
|
|
dy = delta_adjust_positions (&y1, &y2);
|
|
dx = delta_adjust_positions (&x1, &x2);
|
|
|
|
for (y = y1; y != y2; y += dy)
|
|
{
|
|
int dcnt, ecnt;
|
|
|
|
dcnt = ecnt = 0;
|
|
|
|
for (x = x1; x != x2; x += dx)
|
|
{
|
|
if (mask[y * w + x] == 1)
|
|
dcnt++;
|
|
else if (mask[y * w + x] == 2 || mask[y * w + x] == 3)
|
|
ecnt++;
|
|
}
|
|
|
|
if (dcnt == 0 && ecnt == 0)
|
|
{ // line is clear
|
|
prevclear++;
|
|
}
|
|
else if (dcnt == 0)
|
|
{
|
|
if (prevclear >= threshold)
|
|
{ // it's not clear yet, but it will be in a moment ;)
|
|
int lx, ly = y;
|
|
|
|
if (prevclear == threshold)
|
|
{ // need to clean everything we just checked
|
|
ly = y - prevclear * dy;
|
|
}
|
|
|
|
for (ly = ly; ly != y + dy; ly += dy)
|
|
for (lx = x1; lx != x2; lx += dx)
|
|
mask[ly * w + lx] = 0;
|
|
}
|
|
prevclear++;
|
|
}
|
|
else
|
|
{ // line is dirty
|
|
prevclear = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
clean_expansion_vert (unsigned char* mask, int w, int x1, int y1, int x2, int y2, int threshold)
|
|
{
|
|
int x, y, dx, dy;
|
|
// assume anything out of bounds is cleared
|
|
int prevclear = threshold + 1;
|
|
|
|
dy = delta_adjust_positions (&y1, &y2);
|
|
dx = delta_adjust_positions (&x1, &x2);
|
|
|
|
for (x = x1; x != x2; x += dx)
|
|
{
|
|
int dcnt, ecnt;
|
|
|
|
dcnt = ecnt = 0;
|
|
|
|
for (y = y1; y != y2; y += dy)
|
|
{
|
|
if (mask[y * w + x] == 1)
|
|
dcnt++;
|
|
else if (mask[y * w + x] == 2 || mask[y * w + x] == 3)
|
|
ecnt++;
|
|
}
|
|
|
|
if (dcnt == 0 && ecnt == 0)
|
|
{ // line is clear
|
|
prevclear++;
|
|
}
|
|
else if (dcnt == 0)
|
|
{
|
|
if (prevclear >= threshold)
|
|
{ // it's not clear yet, but it will be in a moment ;)
|
|
int ly, lx = x;
|
|
|
|
if (prevclear == threshold)
|
|
{ // need to clean everything we just checked
|
|
lx = x - prevclear * dx;
|
|
}
|
|
|
|
for (lx = lx; lx != x + dx; lx += dx)
|
|
for (ly = y1; ly != y2; ly += dy)
|
|
mask[ly * w + lx] = 0;
|
|
}
|
|
prevclear++;
|
|
}
|
|
else
|
|
{ // line is dirty
|
|
prevclear = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
struct _expand_corner
|
|
{
|
|
int x, y;
|
|
int tx1, ty1;
|
|
int tx2, ty2;
|
|
}
|
|
const expand_corner [] =
|
|
{
|
|
{0, 0, 1, 0, 0, 1}, // top-left
|
|
{1, 0, 0, 0, 0, 1}, // top-mid, from left
|
|
{1, 0, 2, 0, 2, 1}, // top-mid, from right
|
|
{2, 0, 1, 0, 2, 1}, // top-right
|
|
{0, 1, 0, 0, 1, 0}, // mid-left, from top
|
|
{0, 1, 0, 2, 1, 2}, // mid-left, from bottom
|
|
{2, 1, 1, 0, 2, 0}, // mid-right, from top
|
|
{2, 1, 1, 2, 2, 2}, // mid-right, from bottom
|
|
{0, 2, 1, 2, 0, 1}, // bot-left
|
|
{1, 2, 0, 1, 0, 2}, // bot-mid, from left
|
|
{1, 2, 2, 1, 2, 2}, // bot-mid, from right
|
|
{2, 2, 1, 2, 2, 1}, // bot-right
|
|
|
|
{-1,-1, -1,-1, -1,-1} // term
|
|
};
|
|
|
|
// this will recursively expand the missing corner pixels
|
|
// recursion is limited so we dont overflow the stack
|
|
int
|
|
expand_rect (char* mask, int x, int y, int w, int h)
|
|
{
|
|
static int level = 0;
|
|
int x1, y1, x2, y2, i, lx, ly;
|
|
const struct _expand_corner* pc;
|
|
signed char matrix[3][3];
|
|
int cnt = 0;
|
|
|
|
if (level > 99)
|
|
return 1; // make sure parent knows it failed
|
|
|
|
level++;
|
|
|
|
if (x > 0)
|
|
x1 = x - 1;
|
|
else
|
|
{
|
|
for (i = 0; i < 3; i++)
|
|
matrix[0][i] = -1;
|
|
|
|
x1 = x;
|
|
}
|
|
|
|
if (y > 0)
|
|
y1 = y - 1;
|
|
else
|
|
{
|
|
for (i = 0; i < 3; i++)
|
|
matrix[i][0] = -1;
|
|
|
|
y1 = y;
|
|
}
|
|
|
|
if (x + 1 < w)
|
|
x2 = x + 2;
|
|
else
|
|
{
|
|
for (i = 0; i < 3; i++)
|
|
matrix[2][i] = -1;
|
|
|
|
x2 = x + 1;
|
|
}
|
|
|
|
if (y + 1 < h)
|
|
y2 = y + 2;
|
|
else
|
|
{
|
|
for (i = 0; i < 3; i++)
|
|
matrix[i][2] = -1;
|
|
|
|
y2 = y + 1;
|
|
}
|
|
|
|
for (ly = y1; ly < y2; ly++)
|
|
for (lx = x1; lx < x2; lx++)
|
|
matrix[lx - x + 1][ly - y + 1] = mask[ly * w + lx];
|
|
|
|
// check corner pixels
|
|
for (pc = expand_corner; pc->x != -1; pc++)
|
|
{
|
|
if (matrix[pc->x][pc->y] == 0 && matrix[pc->tx1][pc->ty1] > 0 && matrix[pc->tx2][pc->ty2] > 0)
|
|
{ // corner pixel missing
|
|
int ofs = (y - 1 + pc->y) * w + (x - 1 + pc->x);
|
|
|
|
matrix[pc->x][pc->y] = 3;
|
|
|
|
// but it may already be present in the mask (recursive)
|
|
if (mask[ofs] == 0)
|
|
{
|
|
mask[ofs] = 3;
|
|
cnt += 1 + expand_rect (mask, x - 1 + pc->x, y - 1 + pc->y, w, h);
|
|
}
|
|
}
|
|
}
|
|
|
|
level--;
|
|
|
|
return cnt;
|
|
}
|
|
|
|
file_info*
|
|
file_info_add_image (file_info* fi)
|
|
{
|
|
file_info* ni;
|
|
|
|
ni = (file_info*) malloc (sizeof(file_info));
|
|
if (!ni)
|
|
return 0;
|
|
|
|
file_info_init (ni);
|
|
|
|
while (fi->next)
|
|
fi = fi->next;
|
|
|
|
return fi->next = ni;
|
|
}
|
|
|
|
int
|
|
is_multi_delta_image (file_info* fi)
|
|
{
|
|
return fi && fi->next;
|
|
}
|
|
|
|
#define MASK_COLORS 4
|
|
mng_palette8e mask_pal[MASK_COLORS] =
|
|
{
|
|
{0x00, 0x00, 0x00},
|
|
{0xff, 0xff, 0xff},
|
|
{0x00, 0xff, 0x00},
|
|
{0x00, 0x00, 0xff}
|
|
};
|
|
|
|
void
|
|
create_mask_png (char* mask, int w, int h)
|
|
{
|
|
int ret = 0;
|
|
mng_handle mng;
|
|
file_info wf;
|
|
char fname[260];
|
|
mng_ptr imgdata;
|
|
unsigned char* tempdata;
|
|
unsigned char* p;
|
|
uLong srcLen;
|
|
uLong dstLen;
|
|
int i;
|
|
|
|
file_info_init (&wf);
|
|
sprintf(fname, "mask_%03d_%03d.png", _curframe, _curdeltaframe);
|
|
|
|
wf.fname = fname;
|
|
wf.fmode = "wb";
|
|
|
|
// extra byte in front of each line for filter type
|
|
srcLen = w * h + h;
|
|
tempdata = (mng_ptr) malloc(srcLen);
|
|
if (!tempdata)
|
|
return;
|
|
|
|
// maximum necessary space
|
|
// deflated data can be 100.1% + 12 bytes in worst case
|
|
dstLen = srcLen + srcLen / 100 + 20; // extra 8 for safety
|
|
imgdata = (mng_ptr) malloc(dstLen);
|
|
if (!imgdata)
|
|
return;
|
|
|
|
for (i = 0, p = tempdata; i < w * h; i++, p++)
|
|
{
|
|
if (i % w == 0)
|
|
{ // write filter byte
|
|
*p++ = 0;
|
|
}
|
|
|
|
*p = mask[i];
|
|
}
|
|
|
|
if (Z_OK != compress2(imgdata, &dstLen, tempdata, srcLen, 9))
|
|
return;
|
|
|
|
free(tempdata);
|
|
|
|
mng = mng_initialize (&wf, mng_alloc, mng_free, MNG_NULL);
|
|
if (mng == MNG_NULL)
|
|
return;
|
|
|
|
// set the callbacks
|
|
mng_setcb_openstream(mng, mng_open_stream);
|
|
mng_setcb_closestream(mng, mng_close_stream);
|
|
mng_setcb_writedata(mng, mng_write_stream);
|
|
|
|
ret = mng_create (mng);
|
|
|
|
ret = mng_putchunk_ihdr (mng, w, h,
|
|
MNG_BITDEPTH_8, MNG_COLORTYPE_INDEXED, MNG_COMPRESSION_DEFLATE,
|
|
MNG_FILTER_ADAPTIVE, MNG_INTERLACE_NONE);
|
|
|
|
if (ret == MNG_NOERROR)
|
|
ret = mng_putchunk_plte (mng, 4, mask_pal);
|
|
if (ret == MNG_NOERROR)
|
|
ret = mng_putchunk_idat (mng, dstLen, imgdata);
|
|
if (ret == MNG_NOERROR)
|
|
ret = mng_putchunk_iend (mng);
|
|
|
|
free (imgdata);
|
|
|
|
if (ret == MNG_NOERROR)
|
|
ret = mng_write (mng);
|
|
|
|
mng_cleanup (&mng);
|
|
|
|
file_info_cleanup (&wf);
|
|
}
|
|
|
|
int
|
|
build_delta (file_info* i1, file_info* i2)
|
|
{
|
|
int w, h, x, y;
|
|
int dx1, dx2, dy1, dy2;
|
|
int cnt;
|
|
char* mask = 0;
|
|
|
|
if (i1->w > i2->w)
|
|
{
|
|
w = i2->w;
|
|
dx1 = i2->x;
|
|
dx2 = 0;
|
|
}
|
|
else if (i1->w < i2->w)
|
|
{
|
|
w = i1->w;
|
|
dx1 = 0;
|
|
dx2 = i1->x;
|
|
}
|
|
else
|
|
{
|
|
w = i1->w;
|
|
dx1 = dx2 = 0;
|
|
}
|
|
|
|
if (i1->h > i2->h)
|
|
{
|
|
h = i2->h;
|
|
dy1 = i2->y;
|
|
dy2 = 0;
|
|
}
|
|
else if (i1->h < i2->h)
|
|
{
|
|
h = i1->h;
|
|
dy1 = 0;
|
|
dy2 = i1->y;
|
|
}
|
|
else
|
|
{
|
|
h = i1->h;
|
|
dy1 = dy2 = 0;
|
|
}
|
|
|
|
mask = (char*) malloc (w * h);
|
|
if (!mask)
|
|
return 220;
|
|
memset(mask, 0, w * h);
|
|
|
|
// build diff mask first
|
|
for (y = 0; y < h; y++)
|
|
{
|
|
for (x = 0; x < w; x++)
|
|
{
|
|
if (i1->indimg[(y + dy1) * i1->w + x + dx1] != i2->indimg[(y + dy2) * i2->w + x + dx2])
|
|
// diff pixel
|
|
mask[y * w + x] = 1;
|
|
}
|
|
}
|
|
|
|
// coarse expand the diff pixels
|
|
for (y = 0; y < h; y++)
|
|
{
|
|
for (x = 0; x < w; x++)
|
|
{
|
|
if (mask[y * w + x] == 1)
|
|
{
|
|
int x1 = x - 2;
|
|
int x2 = x + 3;
|
|
int y1 = y - 2;
|
|
int y2 = y + 3;
|
|
int lx;
|
|
if (x1 < 0)
|
|
x1 = 0;
|
|
if (x2 > w)
|
|
x2 = w;
|
|
if (y1 < 0)
|
|
y1 = 0;
|
|
if (y2 > h)
|
|
y2 = h;
|
|
|
|
for (y1 = y1; y1 < y2; y1++)
|
|
for (lx = x1; lx < x2; lx++)
|
|
if (mask[y1 * w + lx] == 0)
|
|
mask[y1 * w + lx] = 2;
|
|
}
|
|
}
|
|
}
|
|
|
|
// scan and remove extra expansion horizontally and vertically
|
|
clean_expansion_vert (mask, w, 0, 0, w, h, 1);
|
|
clean_expansion_vert (mask, w, w, 0, 0, h, 1);
|
|
clean_expansion_horz (mask, w, 0, 0, w, h, 1);
|
|
clean_expansion_horz (mask, w, 0, h, w, 0, 1);
|
|
|
|
|
|
do // coarse expand the diff pixels
|
|
{ // merge would-be diff rectangles in the process
|
|
cnt = 0;
|
|
|
|
for (y = 0; y < h; y++)
|
|
{
|
|
for (x = 0; x < w; x++)
|
|
{
|
|
if (mask[y * w + x] != 0)
|
|
cnt += expand_rect (mask, x, y, w, h);
|
|
}
|
|
}
|
|
// repeat is something was expanded
|
|
} while (cnt != 0);
|
|
|
|
// at this point we should have guaranteed non-overlapping
|
|
// rectangles that cover all of the delta areas
|
|
|
|
if (opts.sectorsize)
|
|
{ // final expansion cleanup
|
|
for (y = 0; y < h; y += opts.sectorsize)
|
|
{
|
|
for (x = 0; x < w; x += opts.sectorsize)
|
|
{
|
|
int x2, y2;
|
|
|
|
cnt = 0;
|
|
for (y2 = y; y2 < y + opts.sectorsize && y2 < h; y2++)
|
|
for (x2 = x; x2 < x + opts.sectorsize && x2 < w; x2++)
|
|
if (mask[y2 * w + x2] == 1)
|
|
cnt++;
|
|
|
|
if (cnt > 0)
|
|
continue; // dirty sector
|
|
|
|
// clean up sector
|
|
for (y2 = y; y2 < y + opts.sectorsize && y2 < h; y2++)
|
|
for (x2 = x; x2 < x + opts.sectorsize && x2 < w; x2++)
|
|
mask[y2 * w + x2] = 0;
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
// check how muany pixels have to be replaced
|
|
for (x = 0, cnt = 0; x < w * h; x++)
|
|
if (mask[x])
|
|
cnt++;
|
|
|
|
if (opts.deltamask)
|
|
create_mask_png (mask, w, h);
|
|
|
|
// generate delta images
|
|
if (cnt != w * h)
|
|
{
|
|
int ofs;
|
|
|
|
for (y = 0, ofs = 0; y < h; y++)
|
|
{
|
|
for (x = 0; x < w; x++, ofs++)
|
|
{
|
|
if (mask[ofs] != 0)
|
|
{ // copy masked rectangle into a new image
|
|
// and clear the mask
|
|
int i;
|
|
int rw, rh;
|
|
int x2, y2;
|
|
unsigned char* src;
|
|
unsigned char* dst;
|
|
file_info* ni;
|
|
|
|
ni = file_info_add_image (i1);
|
|
if (!ni)
|
|
{
|
|
x = w;
|
|
y = h;
|
|
break;
|
|
}
|
|
|
|
|
|
// lookup delta rectangle
|
|
for (i = x, src = mask + ofs; i < w && *src != 0; i++, src++)
|
|
;
|
|
ni->w = rw = i - x;
|
|
|
|
if (opts.fullrects)
|
|
{ // locate only complete rectangles
|
|
y2 = y + 1;
|
|
for (i = y + 1, src = mask + ofs; i < h && *src != 0; i++, src += w)
|
|
{
|
|
unsigned char* src2;
|
|
|
|
y2 = i;
|
|
for (x2 = x, src2 = src; x2 < x + rw && *src2 != 0; x2++, src2++)
|
|
;
|
|
|
|
if (x2 < x + rw)
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{ // any rectangles
|
|
for (y2 = y + 1, src = mask + ofs; y2 < h && *src != 0; y2++, src += w)
|
|
;
|
|
}
|
|
|
|
ni->h = rh = y2 - y;
|
|
|
|
ni->indimg = (unsigned char*) malloc (rw * rh);
|
|
if (!ni->indimg)
|
|
{
|
|
x = w;
|
|
y = h;
|
|
break;
|
|
}
|
|
|
|
// copy the pixels
|
|
for (i = 0, src = i1->indimg + (dy1 + y) * i1->w + dx1 + x, dst = ni->indimg;
|
|
i < rh;
|
|
i++, src += i1->w, dst += rw
|
|
)
|
|
{
|
|
memcpy (dst, src, rw);
|
|
memset (mask + ofs + i * w, 0, rw);
|
|
}
|
|
|
|
ni->x = i1->x + dx1 + x;
|
|
ni->y = i1->y + dy1 + y;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (i1->next)
|
|
{ // dispose of the original
|
|
file_info* ni = i1->next;
|
|
free (i1->indimg);
|
|
i1->indimg = ni->indimg;
|
|
i1->x = ni->x;
|
|
i1->y = ni->y;
|
|
i1->w = ni->w;
|
|
i1->h = ni->h;
|
|
i1->next = ni->next;
|
|
|
|
free (ni);
|
|
}
|
|
}
|
|
else
|
|
{ // break here
|
|
cnt = 1;
|
|
}
|
|
|
|
if (mask)
|
|
free (mask);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
delta_images (void)
|
|
{
|
|
int i;
|
|
unsigned short nextid = 0x101;
|
|
|
|
verbose ("calculating frame image deltas", 0);
|
|
|
|
Infos[iback].objid = nextid++;
|
|
if (iback != 0)
|
|
{ // set the first frame objid different
|
|
// from back id
|
|
Infos[0].objid = nextid++;
|
|
}
|
|
|
|
// remove dupes
|
|
for (i = 1; i < cFiles; i++)
|
|
{
|
|
int i2;
|
|
|
|
if (i == iback)
|
|
continue;
|
|
|
|
Infos[i].objid = Infos[i - 1].objid;
|
|
|
|
for (i2 = i - 1; i2 >= 0 && Infos[i].identical == -1; i2--)
|
|
{
|
|
int orgi2 = i2;
|
|
|
|
// deference identical chain
|
|
while (Infos[i2].identical != -1)
|
|
i2 = Infos[i2].identical;
|
|
|
|
if (equal_images (i, i2))
|
|
{
|
|
Infos[i].identical = i2;
|
|
// dont need image data anymore
|
|
if (Infos[i].indimg)
|
|
{
|
|
free (Infos[i].indimg);
|
|
Infos[i].indimg = 0;
|
|
}
|
|
|
|
if (orgi2 != i - 1)
|
|
{ // detached descendant
|
|
// clone the object for it
|
|
if (Infos[i2].clone == -1)
|
|
{ // no clones yet
|
|
Infos[i2].cloneid = nextid++;
|
|
Infos[i2].clone = i;
|
|
Infos[i].objid = Infos[i2].cloneid;
|
|
}
|
|
else
|
|
{ // already cloned for another frame
|
|
// tell the frame to preclone it for
|
|
// this frame too
|
|
// dereference preclone chain first
|
|
for (i2 = Infos[i2].clone; Infos[i2].preclone != -1; i2 = Infos[i2].preclone)
|
|
;
|
|
Infos[i2].preclone = i;
|
|
Infos[i2].precloneid = nextid++;
|
|
Infos[i].objid = Infos[i2].precloneid;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
verbose (".", 0);
|
|
}
|
|
verbose ("|", 0);
|
|
|
|
// compute deltas
|
|
for (i = cFiles - 1; i >= 0; i--)
|
|
{
|
|
int i2;
|
|
|
|
if (i == iback || Infos[i].identical != -1)
|
|
// no delta needed
|
|
continue;
|
|
else
|
|
{
|
|
if (i == 0 && i != iback)
|
|
{ // delta against original background
|
|
i2 = iback;
|
|
}
|
|
else
|
|
{ // deref indentical chain
|
|
for (i2 = i - 1; i2 >= 0 && Infos[i2].identical != -1; i2 = Infos[i2].identical)
|
|
;
|
|
|
|
// sanity check
|
|
if (Infos[i2].objid != Infos[i].objid)
|
|
{
|
|
fprintf (stderr, "delta_images: logical error 1\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
// debug info
|
|
_curframe = i;
|
|
_curdeltaframe = i2;
|
|
|
|
build_delta (Infos + i, Infos + i2);
|
|
}
|
|
verbose (".", 0);
|
|
}
|
|
|
|
verbose ("\n", 0);
|
|
}
|
|
|
|
int
|
|
get_png_image_data (file_info* ms, unsigned char* imgdata)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ms->w * ms->h; i++, imgdata++)
|
|
{
|
|
if (i % ms->w == 0)
|
|
{ // write filter byte
|
|
*imgdata++ = 0;
|
|
}
|
|
|
|
*imgdata = ms->indimg[i];
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
compress_png (file_info* ms, mng_ptr imgdata, mng_uint32 imglen, mng_uint32p bytes)
|
|
{
|
|
int ret = 0;
|
|
unsigned char* tempdata;
|
|
uLong srcLen;
|
|
|
|
*bytes = 0;
|
|
|
|
// extra byte in front of each line for filter type
|
|
srcLen = ms->w * ms->h + ms->h;
|
|
tempdata = (mng_ptr) malloc(srcLen);
|
|
if (!tempdata)
|
|
return 241;
|
|
|
|
ret = get_png_image_data (ms, tempdata);
|
|
if (!ret)
|
|
{
|
|
uLong dstLen = imglen;
|
|
|
|
if (Z_OK == compress2(imgdata, &dstLen, tempdata, srcLen, 9))
|
|
*bytes = dstLen;
|
|
else
|
|
ret = 253;
|
|
}
|
|
|
|
free(tempdata);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
output_png (mng_handle mng, file_info* rf, int delta)
|
|
{
|
|
int ret = 0;
|
|
mng_ptr imgdata;
|
|
mng_uint32 imglen;
|
|
mng_uint32 cbcomp;
|
|
unsigned short objid = rf->objid;
|
|
|
|
// maximum necessary space
|
|
// deflated data can be 100.1% + 12 bytes in worst case
|
|
imglen = mngw * mngh + mngh;
|
|
imglen += imglen / 100 + 20; // extra 8 for safety
|
|
imgdata = (mng_ptr) malloc(imglen);
|
|
if (!imgdata)
|
|
return 252;
|
|
|
|
do
|
|
{
|
|
if (delta)
|
|
{ // output delta
|
|
ret = mng_putchunk_dhdr (mng, objid,
|
|
MNG_IMAGETYPE_PNG, MNG_DELTATYPE_BLOCKPIXELREPLACE,
|
|
rf->w, rf->h, rf->x, rf->y);
|
|
}
|
|
else
|
|
{ // output image verbatim
|
|
ret = mng_putchunk_ihdr (mng, rf->w, rf->h,
|
|
MNG_BITDEPTH_8, MNG_COLORTYPE_INDEXED, MNG_COMPRESSION_DEFLATE,
|
|
MNG_FILTER_ADAPTIVE, MNG_INTERLACE_NONE);
|
|
|
|
if (ret == MNG_NOERROR)
|
|
{ // write empty PLTE to use the global PLTE
|
|
ret = mng_putchunk_plte (mng, 0, Pal);
|
|
//ret = mng_putchunk_plte (mng, cPalClr, Pal); // enable to write plain PNG
|
|
}
|
|
}
|
|
|
|
if (ret == MNG_NOERROR)
|
|
ret = compress_png (rf, imgdata, imglen, &cbcomp);
|
|
|
|
if (ret == MNG_NOERROR)
|
|
ret = mng_putchunk_idat (mng, cbcomp, imgdata);
|
|
|
|
if (ret == MNG_NOERROR)
|
|
ret = mng_putchunk_iend (mng);
|
|
|
|
} while ((rf = rf->next) != 0 && ret == MNG_NOERROR);
|
|
|
|
free (imgdata);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
write_mng_file (void)
|
|
{
|
|
int ret = 0;
|
|
mng_handle mng;
|
|
file_info wf;
|
|
file_info rf;
|
|
file_info backf;
|
|
int i;
|
|
unsigned short lastobjid;
|
|
char curframemode, newframemode;
|
|
|
|
mng = mng_initialize (MNG_NULL, mng_alloc, mng_free, MNG_NULL);
|
|
if (mng == MNG_NULL)
|
|
{
|
|
fprintf (stderr, "libmng did not init properly\n");
|
|
return 250;
|
|
}
|
|
|
|
// set the callbacks
|
|
mng_setcb_openstream(mng, mng_open_stream);
|
|
mng_setcb_closestream(mng, mng_close_stream);
|
|
mng_setcb_writedata(mng, mng_write_stream);
|
|
|
|
file_info_init (&wf);
|
|
wf.fname = opts.outfile;
|
|
wf.fmode = "wb";
|
|
mng_set_userdata (mng, &wf);
|
|
|
|
ret = mng_create (mng);
|
|
if (ret != MNG_NOERROR)
|
|
fprintf (stderr, "Could not create '%s'\n", wf.fname);
|
|
else
|
|
verbose ("writing MNG file '%s'", wf.fname);
|
|
|
|
ret = mng_putchunk_mhdr (mng, mngw, mngh, timerate, 0, 0, 0,
|
|
MNG_SIMPLICITY_VALID | MNG_SIMPLICITY_SIMPLEFEATURES |
|
|
MNG_SIMPLICITY_COMPLEXFEATURES | MNG_SIMPLICITY_DELTAPNG | 0x240);
|
|
|
|
//ret = mng_putchunk_term (mng, MNG_TERMACTION_LASTFRAME, MNG_ITERACTION_LASTFRAME, 0, 0);
|
|
|
|
ret = mng_putchunk_plte (mng, cPalClr, Pal);
|
|
|
|
ret = mng_putchunk_back (mng, 0,0,0, 0, 0, MNG_BACKGROUNDIMAGE_NOTILE);
|
|
|
|
curframemode = MNG_FRAMINGMODE_1;
|
|
ret = mng_putchunk_fram (mng, MNG_FALSE, curframemode, 0,MNG_NULL,
|
|
MNG_CHANGEDELAY_DEFAULT, MNG_CHANGETIMOUT_NO, MNG_CHANGECLIPPING_NO, MNG_CHANGESYNCID_NO,
|
|
framedelay, 0,0,0,0,0,0, MNG_NULL,0);
|
|
|
|
// define the staring image/object
|
|
backf = Infos[iback];
|
|
ret = mng_putchunk_defi (mng, backf.objid, MNG_DONOTSHOW_NOTVISIBLE, MNG_CONCRETE, MNG_FALSE, 0,0, MNG_FALSE, 0,0,0,0);
|
|
ret = output_png (mng, &backf, 0);
|
|
|
|
//ret = mng_putchunk_save (mng, MNG_TRUE, 0,0);
|
|
//ret = mng_putchunk_seek (mng, 5, "start");
|
|
|
|
if (iback != 0)
|
|
{ // clone the starting object for the first frame
|
|
ret = mng_putchunk_clon (mng, backf.objid, Infos[0].objid,
|
|
MNG_FULL_CLONE, MNG_DONOTSHOW_NOTVISIBLE, MNG_CONCRETE_ASPARENT,
|
|
MNG_FALSE, 0,0,0);
|
|
}
|
|
|
|
lastobjid = 0;
|
|
|
|
for (i = 0; i < cFiles && ret == MNG_NOERROR; i++)
|
|
{
|
|
rf = Infos[i];
|
|
|
|
if (rf.precloneid != 0)
|
|
{ // pre-clone the object for another frame
|
|
ret = mng_putchunk_clon (mng, rf.objid, rf.precloneid,
|
|
MNG_FULL_CLONE, MNG_DONOTSHOW_NOTVISIBLE, MNG_CONCRETE_ASPARENT,
|
|
MNG_FALSE, 0,0,0);
|
|
}
|
|
|
|
if (is_multi_delta_image (&rf))
|
|
// multi-delta png; frame mode: 0-delay for subframe
|
|
newframemode = MNG_FRAMINGMODE_2;
|
|
else
|
|
// frame mode: 1 image per frame
|
|
newframemode = MNG_FRAMINGMODE_1;
|
|
|
|
|
|
if (newframemode != curframemode)
|
|
{ // change framing mode only
|
|
ret = mng_putchunk_fram (mng, MNG_FALSE, newframemode, 0,MNG_NULL,
|
|
MNG_CHANGEDELAY_NO, MNG_CHANGETIMOUT_NO, MNG_CHANGECLIPPING_NO, MNG_CHANGESYNCID_NO,
|
|
0,0,0,0,0,0,0, MNG_NULL,0);
|
|
curframemode = newframemode;
|
|
}
|
|
else if (curframemode == MNG_FRAMINGMODE_2)
|
|
{ // start new subframe
|
|
ret = mng_putchunk_fram (mng, MNG_TRUE, 0,0,MNG_NULL,0,0,0,0,0,0,0,0,0,0,0,0,0);
|
|
}
|
|
|
|
if (rf.indimg != 0 && i != iback)
|
|
{ // display a delta png
|
|
ret = output_png (mng, &rf, 1);
|
|
}
|
|
|
|
if (rf.cloneid != 0)
|
|
{ // post-clone the object for another frame
|
|
ret = mng_putchunk_clon (mng, rf.objid, rf.cloneid,
|
|
MNG_FULL_CLONE, MNG_DONOTSHOW_NOTVISIBLE, MNG_CONCRETE_ASPARENT,
|
|
MNG_FALSE, 0,0,0);
|
|
}
|
|
|
|
if (rf.objid != lastobjid || rf.identical != -1)
|
|
{ // show the object for this frame
|
|
ret = mng_putchunk_show (mng, MNG_FALSE, rf.objid, rf.objid, MNG_SHOWMODE_0);
|
|
lastobjid = rf.objid;
|
|
}
|
|
|
|
verbose (".", 0);
|
|
}
|
|
|
|
//ret = mng_putchunk_seek (mng, 3, "end");
|
|
ret = mng_putchunk_mend (mng);
|
|
|
|
ret = mng_write (mng);
|
|
|
|
file_info_cleanup (&wf);
|
|
|
|
mng_cleanup (&mng);
|
|
|
|
if (ret == MNG_NOERROR)
|
|
verbose ("finished.\n", 0);
|
|
else
|
|
fprintf (stderr, "Could not create MNG file\n");
|
|
|
|
return ret;
|
|
}
|
|
|
|
mng_ptr MNG_DECL
|
|
mng_alloc (mng_size_t iLen)
|
|
{
|
|
mng_ptr ptr;
|
|
|
|
if (iLen & 0x80000000)
|
|
return 0; // MNG error!
|
|
|
|
ptr = malloc (iLen);
|
|
if (ptr)
|
|
memset(ptr, 0, iLen);
|
|
|
|
return ptr;
|
|
}
|
|
|
|
void MNG_DECL
|
|
mng_free (mng_ptr pPtr, mng_size_t iLen)
|
|
{
|
|
if (iLen)
|
|
free (pPtr);
|
|
}
|
|
|
|
mng_bool MNG_DECL
|
|
mng_open_stream (mng_handle mng)
|
|
{
|
|
file_info* ms;
|
|
|
|
ms = (file_info*) mng_get_userdata (mng);
|
|
|
|
ms->f = fopen (ms->fname, ms->fmode);
|
|
if (!ms->f)
|
|
{
|
|
fprintf(stderr, "unable to open '%s'\n", ms->fname);
|
|
return MNG_FALSE;
|
|
}
|
|
|
|
return MNG_TRUE;
|
|
}
|
|
|
|
mng_bool MNG_DECL
|
|
mng_close_stream (mng_handle mng)
|
|
{
|
|
file_info* ms;
|
|
|
|
ms = (file_info*) mng_get_userdata (mng);
|
|
|
|
fclose(ms->f);
|
|
ms->f = NULL;
|
|
|
|
return MNG_TRUE;
|
|
}
|
|
|
|
mng_bool MNG_DECL
|
|
mng_read_stream (mng_handle mng, mng_ptr buffer, mng_uint32 size, mng_uint32p bytes)
|
|
{
|
|
file_info* ms;
|
|
|
|
ms = (file_info*) mng_get_userdata (mng);
|
|
|
|
*bytes = fread(buffer, 1, size, ms->f);
|
|
|
|
return MNG_TRUE;
|
|
}
|
|
|
|
mng_bool MNG_DECL
|
|
mng_write_stream (mng_handle mng, mng_ptr buffer, mng_uint32 size, mng_uint32p bytes)
|
|
{
|
|
file_info* ms;
|
|
|
|
ms = (file_info*) mng_get_userdata (mng);
|
|
|
|
*bytes = fwrite(buffer, 1, size, ms->f);
|
|
|
|
return MNG_TRUE;
|
|
}
|
|
|
|
mng_bool MNG_DECL
|
|
mng_process_header (mng_handle mng, mng_uint32 width, mng_uint32 height)
|
|
{
|
|
file_info* ms;
|
|
|
|
ms = (file_info*) mng_get_userdata (mng);
|
|
ms->w = width;
|
|
ms->h = height;
|
|
ms->image = (RGBA*) malloc(sizeof(RGBA) * width * height);
|
|
if (!ms->image)
|
|
return MNG_FALSE;
|
|
|
|
mng_set_canvasstyle(mng, MNG_CANVAS_RGBA8);
|
|
|
|
return MNG_TRUE;
|
|
}
|
|
|
|
mng_ptr MNG_DECL
|
|
mng_get_canvasline_read (mng_handle mng, mng_uint32 line)
|
|
{
|
|
file_info* ms;
|
|
mng_ptr row;
|
|
|
|
ms = (file_info*) mng_get_userdata (mng);
|
|
|
|
row = ms->image + ms->w * line;
|
|
|
|
return row;
|
|
}
|
|
|
|
mng_ptr MNG_DECL
|
|
mng_get_canvasline_write (mng_handle mng, mng_uint32 line)
|
|
{
|
|
file_info* ms;
|
|
|
|
ms = (file_info*) mng_get_userdata (mng);
|
|
|
|
//if (!ms->rowdata)
|
|
// ms->rowdata = (unsigned char*) malloc (ms->w);
|
|
//if (!ms->rowdata)
|
|
// return MNG_NULL;
|
|
|
|
//make_pal_row (ms, line, ms->rowdata);
|
|
|
|
// satisfying compiler
|
|
line = 0;
|
|
|
|
return MNG_NULL;
|
|
}
|
|
|
|
mng_bool MNG_DECL
|
|
mng_refresh_display (mng_handle mng, mng_uint32 x, mng_uint32 y, mng_uint32 w, mng_uint32 h)
|
|
{
|
|
// not implemented
|
|
file_info* ms;
|
|
|
|
ms = (file_info*) mng_get_userdata (mng);
|
|
|
|
// satisfying compiler
|
|
x = y = w = h = 0;
|
|
|
|
return MNG_TRUE;
|
|
}
|
|
|
|
mng_uint32 MNG_DECL
|
|
mng_get_tickcount (mng_handle mng)
|
|
{
|
|
// not implemented
|
|
file_info* ms;
|
|
static int tick = 0;
|
|
|
|
ms = (file_info*) mng_get_userdata (mng);
|
|
|
|
return tick += 50;
|
|
}
|
|
|
|
mng_bool MNG_DECL
|
|
mng_set_timer (mng_handle mng, mng_uint32 msecs)
|
|
{
|
|
// not implemented
|
|
file_info* ms;
|
|
|
|
ms = (file_info*) mng_get_userdata (mng);
|
|
ms->delay = msecs;
|
|
|
|
return MNG_TRUE;
|
|
}
|