/* * 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 #include #include #include #include #include #include #ifdef WIN32 # include #else # include #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] \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; }