From c92033fe3746f2b80f3a44913a73a09a886dbe9d Mon Sep 17 00:00:00 2001 From: avolkov Date: Fri, 21 Jan 2005 18:42:28 +0000 Subject: [PATCH] Tool used to convert DOS LPF files to UQM .ani + presentations git-svn-id: svn://svn.code.sf.net/p/sc2/code/trunk@1477 8092fc87-c524-0410-9efc-e669fe64eaf9 --- tools/lpf2ani/Makefile | 34 + tools/lpf2ani/config.h | 15 + tools/lpf2ani/lpf.h | 161 +++++ tools/lpf2ani/lpf2ani.c | 1325 +++++++++++++++++++++++++++++++++++++ tools/lpf2ani/lpf2ani.dsp | 110 +++ tools/lpf2ani/lpf2ani.dsw | 29 + 6 files changed, 1674 insertions(+) create mode 100644 tools/lpf2ani/Makefile create mode 100644 tools/lpf2ani/config.h create mode 100644 tools/lpf2ani/lpf.h create mode 100644 tools/lpf2ani/lpf2ani.c create mode 100644 tools/lpf2ani/lpf2ani.dsp create mode 100644 tools/lpf2ani/lpf2ani.dsw diff --git a/tools/lpf2ani/Makefile b/tools/lpf2ani/Makefile new file mode 100644 index 000000000..11990d68a --- /dev/null +++ b/tools/lpf2ani/Makefile @@ -0,0 +1,34 @@ +######################################################################## +# This is a GNU makefile - tested on CYGWIN and Linux +# +# You may need to compile or install libpng and/or zlib before +# compiling this. +# + +TARGET = lpf2ani +# These flags are needed to get it to compile with libpngX.dll on CYGWIN +# CYGWINFLAGS = -DPNG_USE_DLL +CYGWINFLAGS = +PNGFLAGS = -I. + +CC = gcc +CFLAGS = -W -Wall -O0 # -g +LIBS = -L/usr/local/lib -lpng -lm -lz +LDFLAGS= + +BINS = $(TARGET) $(TARGET).exe +SRCS = lpf2ani.c +OBJS = lpf2ani.o + +.SUFFIXES: .c .o + +.c.o: + $(CC) -c $(CFLAGS) -o $@ $*.c $(PNGFLAGS) $(CYGWINFLAGS) + +all: $(TARGET) + +$(TARGET): $(OBJS) + $(CC) -o $(TARGET) $(OBJS) $(LIBS) $(LDFLAGS) + +clean: + rm -f $(BINS) *.o diff --git a/tools/lpf2ani/config.h b/tools/lpf2ani/config.h new file mode 100644 index 000000000..0a70fe8f0 --- /dev/null +++ b/tools/lpf2ani/config.h @@ -0,0 +1,15 @@ +/* Dummy compile-time configuration options + * Only used when compiling in MS VC++ using UQM's getopt.c + */ + +#ifndef _CONFIG_H +#define _CONFIG_H + +/* Define if words are stored with the most significant byte first */ +#undef WORDS_BIGENDIAN + +/* Defined if your system has getopt.h */ +#undef HAVE_GETOPT_H + +#endif /* _CONFIG_H */ + diff --git a/tools/lpf2ani/lpf.h b/tools/lpf2ani/lpf.h new file mode 100644 index 000000000..5d9cf6fdf --- /dev/null +++ b/tools/lpf2ani/lpf.h @@ -0,0 +1,161 @@ +#include + +#if defined(_MSC_VER) +# define PACKED +# pragma pack(push, 1) +#elif defined(__GNUC__) +# define PACKED __attribute__ ((packed)) +#endif + +// LPF file layout +// +// header (0x40) +// padding (?) +// palette (0x400) - get offset from header +// 4 bytes/color) +// usually 0x100 colors +// framepackinfos (?) - get offset from header +// get number if packs from header +// 1st pack +// immediatelly follows framepackinfos +// 2nd pack +// offset = offset(1st pack) + 0x10000 +// ... +// last pack +// end + +typedef struct +{ + uint32_t PACKED magic; // "LPF " + uint16_t PACKED cpackslots; // frame-pack slots + uint16_t PACKED cpacks; // frame-pack count + uint32_t PACKED cframes; // frame count + uint16_t PACKED opal; // offset of palette + uint16_t PACKED opacks; // offset of framepack infos + uint32_t PACKED type; // "ANIM" + uint16_t PACKED w; // animation width + uint16_t PACKED h; // animation height + uint16_t PACKED stuff1[0x14]; // who knows? + uint32_t PACKED cframes2; // again? + uint32_t PACKED fps; // frames/second +} header; + +#define PAL_COLORS 0x100 + +typedef union +{ + struct { + uint8_t b, g, r, a; + } bchan; + uint8_t channels[4]; + uint32_t rgba; +} lpfpalcolor; + +typedef struct +{ + uint16_t PACKED frame; // first frame in this pack + uint16_t PACKED cframes; // count of frames in this pack + uint16_t PACKED datasize; // size of data in this pack +} framepackinfo; + +typedef struct +{ + framepackinfo PACKED info; // pack info + uint16_t PACKED za; // 0 word + uint16_t PACKED sizes[1]; // frame-size array +} framepack; + +#define FRAMEPACK_SIZE(fpi) \ + (sizeof(framepack) + sizeof(uint16_t) * (fpi.cframes - 1)) + +typedef struct +{ + uint16_t PACKED flags; // 0x0042 - frame flags? + uint16_t PACKED csubs; // 0x0001 - count of subframes? + // packed frame data follows +} frameheader; + + +// Frame Data +// Everything is stored LSB first unless otherwise specified. +// +// The data is processed and dumped into the frame buffer linearly. If not +// all of the frame buffer is covered by the data packets, the remaining +// pixels are copied from the previous frame. +// +// Data Packet (typed packets follow and include the data described here): +// length meaning +// 1 type and count +// - bit 7: type flag (FD_EXT_FLAG) +// - bits 0-6: count +// flag=0: +// count=0: pixel repeat; Repeat Packet +// count=N: quote next N; Quote Packet +// flag=1: +// count=0: 16-bit counts; Extended Packet +// count=N: skip next N; Skip Packet +// +// Repeat Packet: +// 1 type and count, always 0x00 for this packet type +// 1 Repeat count +// 1 Repeated pixel +// +// Quote Packet: +// 1 type and count +// - bit 7: type flag; always 0 for this packet type +// - bits 0-6: Quoted pixel count +// ? Quoted pixels; count specified by previous byte +// +// Skip Packet: +// 1 type and count +// - bit 7: type flag; always 1 for this packet type +// - bits 0-6: Skipped pixel count; the pixels are copied +// from the same location in the previous frame +// +// Extended Packet (typed packets follow and include the data described here): +// 1 type and count, always 0x80 for this packet type +// 2 extended type and count +// - bit 15: type flag1 (FD_EXT_FLAG1) +// when 0: Extended Skip Packet +// - bits 0-14: Skipped pixel count +// when 1: +// - bit 14: type flag2 (FD_EXT_FLAG2) +// when 0: Extended Quote Packet +// when 1: Extended Repeat Packet +// - bits 0-13: Repeat/Quote pixel count +// +// Extended Skip Packet: +// 1 type and count, always 0x80 for this packet type +// 2 extended type and count +// - bit 15: type flag1 (FD_EXT_FLAG1), always 0 +// - bits 0-14: Skipped pixel count; the pixels are copied +// from the same location in the previous frame +// +// Extended Quote Packet: +// 1 type and count, always 0x80 for this packet type +// 2 extended type and count +// - bit 15: type flag1 (FD_EXT_FLAG1), always 1 +// - bit 14: type flag2 (FD_EXT_FLAG2), always 0 +// - bits 0-13: Quoted pixel count +// ? Quoted pixels; count specified by previous word +// +// Extended Repeat Packet: +// 1 type and count, always 0x80 for this packet type +// 2 extended type and count +// - bit 15: type flag1 (FD_EXT_FLAG1), always 1 +// - bit 14: type flag2 (FD_EXT_FLAG2), always 1 +// - bits 0-13: Repeat count +// 1 Repeated pixel +// +#define FD_EXT_FLAG 0x0080 +#define FD_COUNT_MASK 0x007f +#define FD_EXT_FLAG1 0x8000 +#define FD_EXT_FLAG2 0x4000 +#define FD_EXT_SCOUNT_MASK 0x7fff +#define FD_EXT_RCOUNT_MASK 0x3fff +#define FD_EXT_QCOUNT_MASK FD_EXT_RCOUNT_MASK + +#if defined(_MSC_VER) +# pragma pack(pop) +#endif +#undef PACKED diff --git a/tools/lpf2ani/lpf2ani.c b/tools/lpf2ani/lpf2ani.c new file mode 100644 index 000000000..c7d0ad2b6 --- /dev/null +++ b/tools/lpf2ani/lpf2ani.c @@ -0,0 +1,1325 @@ +/** + * DOS SC2 LPF animation files converter (.anm?) + * By Alex Volkov (codepro@usa.net), 20050121 + * GPL applies + * + * The frames are dumped into separate paletted 8bpp PNGs; + * .ani and presentation .txt files are created + * +**/ + +#include +#include +#include +#include +#include +#ifdef WIN32 +# include +#else +# include +#endif +#include +#if defined(__GNUC__) +# include +#endif +#include "lpf.h" + +int opt_verbose = 0; +int opt_framerate = 0; +char* opt_inname = 0; +char* opt_outname = 0; +int opt_deltamask = 0; +int opt_fullrects = 0; +int opt_sectorsize = 8; +char* opt_actfile = 0; + +header hdr; +framepackinfo* packs = 0; +const char* outmask = 0; +png_color* palette = 0; +uint8_t buf[0x10000]; +uint8_t* fimg = 0; +uint32_t fimgsize = 0; + +// internals +int error(int errn, const char* fmt, const char* s); +void verbose(const char* fmt, ...); + +typedef struct _file_info +{ + int w, h; + int x, y; + uint8_t* image; + int timestamp; + int index; + int identical; + struct _file_info* next; +} file_info; + +void file_infos_free(); +void file_info_init(file_info* ms); +void file_info_cleanup(file_info* ms); + +int cInfos = 0; +file_info* Infos = 0; +int framedelay = 100; // default 10 fps +int _curframe = -1; +int _curdeltaframe = -1; + +int error(int errn, const char* fmt, const char* s) +{ + fprintf(stderr, fmt, s); + return errn; +} + +void verbose(const char* fmt, ...) +{ + va_list args; + + if (!opt_verbose) + return; + + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); +} + +int readhead(FILE* f) +{ + if (fread(&hdr, sizeof(hdr), 1, f) != 1) + return 250; + + verbose("Total frames %u\n", hdr.cframes); + + return 0; +} + +int readpalette(FILE* f) +{ + int i; + lpfpalcolor* lpfpal; + + verbose("Converting palette, %d colors...\n", PAL_COLORS); + + lpfpal = (lpfpalcolor*) alloca(sizeof(lpfpalcolor) * PAL_COLORS); + palette = (png_color*) malloc(sizeof(png_color) * PAL_COLORS); + if (!lpfpal || !palette) + return 240; + + fseek(f, hdr.opal, SEEK_SET); + + if (fread(lpfpal, sizeof(lpfpalcolor), PAL_COLORS, f) != PAL_COLORS) + return 241; + + for (i = 0; i < PAL_COLORS; i++) + { + palette[i].red = lpfpal[i].bchan.r; + palette[i].green = lpfpal[i].bchan.g; + palette[i].blue = lpfpal[i].bchan.b; + } + + return 0; +} + +int writepalette(const char* actfile) +{ + FILE* f; + int c; + + f = fopen(actfile, "wb"); + if (!f) + return 231; + + c = fwrite(palette, sizeof(palette[0]), PAL_COLORS, f); + fclose(f); + + return c == PAL_COLORS ? 0 : 232; +} + +int processpacks(FILE* f) +{ + int i, j, k; + uint32_t packofs; + framepack* pack; + + packs = (framepackinfo*) calloc(sizeof(framepackinfo), hdr.cpacks); + pack = (framepack*) alloca(sizeof(framepack) + sizeof(uint16_t) * 128); + if (!packs || !pack) + return 230; + + fseek(f, hdr.opacks, SEEK_SET); + + if (fread(packs, sizeof(framepackinfo), hdr.cpacks, f) != hdr.cpacks) + return 231; + + packofs = hdr.opacks + hdr.cpackslots * sizeof(framepackinfo); + + // dump packs + for (i = 0; i < hdr.cpacks; ++i) + { + verbose("pack %02d: start frame %03d, frames %03d\n", i, packs[i].frame, packs[i].cframes); + + fseek(f, packofs + i * 0x10000, SEEK_SET); + fread(pack, FRAMEPACK_SIZE(packs[i]), 1, f); + + for (j = 0; j < pack->info.cframes; ++j) + { + verbose("\tframe %03d: size 0x%04x", pack->info.frame + j, pack->sizes[j]); + + if (pack->sizes[j]) + { + fread(buf, 1, pack->sizes[j], f); + + verbose(" dump "); + + for (k = 0; k < 16 && k < pack->sizes[j]; ++k) + verbose("%02x ", buf[k]); + } + verbose("\n"); + } + } + + return 0; +} + +int processframe(uint8_t* frame, int size, uint8_t* image) +{ + uint8_t* fd; + uint8_t* fe; + uint8_t* fo; + int total = 0; + int i; + + if (size == 0) + return total; + + size -= sizeof(frameheader); + frame += sizeof(frameheader); + + verbose("\tpacked data: "); + for (i = 0; i < 16 && i < size; i++) + verbose("%02x ", frame[i]); + verbose("\n"); + + for (fd = frame, fe = frame + size, fo = image; fd < fe; ) + { + uint8_t flags = *fd++; + int count = flags & FD_COUNT_MASK; + + if (flags & FD_EXT_FLAG) + { + if (count == 0) + { // ext + uint16_t flags = *((uint16_t*)fd)++; + + if (flags & FD_EXT_FLAG1) + { + count = flags & FD_EXT_RCOUNT_MASK; + + if (flags & FD_EXT_FLAG2) + { // repeat + uint8_t pixel = *fd++; + + //verbose("\t\tlong repeat: %04x, pixel %02x, ofs %04x\n", flags, pixel, fd - frame + 4); + memset(fo, pixel, count); + fo += count; + total += count; + } + else + { // quote + //verbose("\t\tlong quote: %04x, ofs %04x\n", flags, fd - frame + 4); + memcpy(fo, fd, count); + fd += count; + fo += count; + total += count; + } + } + else + { // skip + count = flags & FD_EXT_SCOUNT_MASK; + fo += count; + total += count; + } + } + else + { // skip + //verbose("\t\tshort skip: %02x, ofs %04x\n", fd->skip.count, (uint8_t*)fd - frame + 4); + fo += count; + total += count; + } + } + else + { + if (count == 0) + { // repeat + uint8_t pixel; + + count = *fd++; + pixel = *fd++; + memset(fo, pixel, count); + fo += count; + total += count; + } + else + { // quote + memcpy(fo, fd, count); + fd += count; + fo += count; + total += count; + } + } + } + + verbose("\t\toutput bytes: %04x\n", total); + + return total; +} + +int packfromframe(uint16_t iframe) +{ + uint16_t i; + + for (i = 0; i < hdr.cpacks && (iframe < packs[i].frame || iframe >= packs[i].frame + packs[i].cframes); ++i) + ; + + return i; +} + +int write_png(const char* filename, uint8_t* fimg, uint32_t w, uint32_t h, png_colorp pal, uint32_t cpal) +{ + FILE* out; + png_structp png; + png_infop pngi; + png_bytepp rows; + uint32_t i; + + out = fopen(filename, "wb"); + if (!out) + return -1; + + png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png) + { + fclose(out); + return -1; + } + pngi = png_create_info_struct(png); + if (!pngi) + { + png_destroy_write_struct(&png, (png_infopp)NULL); + fclose(out); + return -1; + } + + rows = malloc(sizeof(png_bytep) * h); + if (!rows) + { + png_destroy_write_struct(&png, (png_infopp)NULL); + fclose(out); + return -1; + } + for (i = 0; i < h; ++i) + rows[i] = fimg + i * w; + + if (setjmp(png_jmpbuf(png))) + { + png_destroy_write_struct(&png, &pngi); + free(rows); + fclose(out); + return -1; + } + + png_init_io(png, out); + png_set_filter(png, 0, PNG_FILTER_NONE); + png_set_compression_level(png, Z_BEST_COMPRESSION); + png_set_IHDR(png, pngi, w, h, 8, PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + png_set_PLTE(png, pngi, pal, cpal); + png_set_rows(png, pngi, rows); + //png_write_png(png, pngi, PNG_TRANSFORM_IDENTITY, NULL); + png_write_info(png, pngi); + png_write_image(png, rows); + png_write_end(png, pngi); + + png_destroy_write_struct(&png, &pngi); + fclose(out); + free(rows); + + return 0; +} + +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->image[(y + dy1) * i1->w + x + dx1] != i2->image[(y + dy2) * i2->w + x + dx2]) + cnt++; + } + } + return cnt; +} + +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 +png_color mask_pal[MASK_COLORS] = +{ + {0x00, 0x00, 0x00}, + {0xff, 0xff, 0xff}, + {0x00, 0xff, 0x00}, + {0x00, 0x00, 0xff} +}; + +void create_mask_png (uint8_t* mask, int w, int h) +{ + char fname[260]; + + sprintf(fname, "mask_%03d_%03d.png", _curframe, _curdeltaframe); + write_png(fname, mask, w, h, mask_pal, MASK_COLORS); +} + +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->image[(y + dy1) * i1->w + x + dx1] != i2->image[(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 (opt_sectorsize) + { // final expansion cleanup + for (y = 0; y < h; y += opt_sectorsize) + { + for (x = 0; x < w; x += opt_sectorsize) + { + int x2, y2; + + cnt = 0; + for (y2 = y; y2 < y + opt_sectorsize && y2 < h; y2++) + for (x2 = x; x2 < x + opt_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 + opt_sectorsize && y2 < h; y2++) + for (x2 = x; x2 < x + opt_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 (opt_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 (opt_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->image = (unsigned char*) malloc (rw * rh); + if (!ni->image) + { + x = w; + y = h; + break; + } + + // copy the pixels + for (i = 0, src = i1->image + (dy1 + y) * i1->w + dx1 + x, dst = ni->image; + 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->image); + i1->image = ni->image; + 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; + + verbose("calculating frame image deltas"); + + // remove dupes + for (i = 1; i < cInfos; i++) + { + int i2; + + // dereference identical chain + for (i2 = i - 1; 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].image) + { + free(Infos[i].image); + Infos[i].image = 0; + } + } + verbose (".", 0); + } + verbose ("|", 0); + + // compute deltas + for (i = cInfos - 1; i > 0; i--) + { + int i2; + + if (Infos[i].identical != -1) + // no delta needed + continue; + else + { + // deref indentical chain + for (i2 = i - 1; i2 >= 0 && Infos[i2].identical != -1; i2 = Infos[i2].identical) + ; + + // debug info + _curframe = i; + _curdeltaframe = i2; + + build_delta (Infos + i, Infos + i2); + } + verbose (".", 0); + } + + verbose ("\n", 0); +} + +void file_info_init(file_info* ms) +{ + memset(ms, 0, sizeof(*ms)); + ms->identical = -1; + ms->index = -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; + } + + fi = fi->next; + if (tempi != ms) + free (tempi); + } +} + +void file_infos_free() +{ + int i; + + if (Infos) + { + for (i = 0; i < cInfos; i++) + file_info_cleanup (Infos + i); + + free (Infos); + Infos = 0; + } +} + +int processframes(FILE* f) +{ + uint32_t i; + uint32_t packofs; + framepack* pack; + //char filename[250]; + + pack = (framepack*) alloca(sizeof(framepack) + sizeof(uint16_t) * 128); + packofs = hdr.opacks + hdr.cpackslots * sizeof(framepackinfo); + + cInfos = hdr.cframes; + Infos = malloc(sizeof(file_info) * cInfos); + if (!Infos) + return 251; + memset(Infos, 0, sizeof(file_info) * cInfos); + + // process frames + for (i = 0; i < hdr.cframes; ++i) + { + int iframe; + int ofs; + int j; + int size; + int ipack = packfromframe(i); + file_info* rf = Infos + i; + + fseek(f, packofs + ipack * 0x10000, SEEK_SET); + fread(pack, FRAMEPACK_SIZE(packs[ipack]), 1, f); + iframe = i - pack->info.frame; + size = pack->sizes[iframe]; + + for (j = 0, ofs = 0; j < iframe; ofs += pack->sizes[j], ++j) + ; + + verbose("frame %03d: pack %03d, index %03d, ofs 0x%04x, size 0x%04x\n", i, ipack, iframe, ofs, size); + + fseek(f, ofs, SEEK_CUR); + fread(buf, 1, size, f); + + processframe(buf, size, fimg); + + file_info_init(rf); + rf->image = malloc(fimgsize); + if (!rf->image) + return 221; + memcpy(rf->image, fimg, fimgsize); + rf->w = hdr.w; + rf->h = hdr.h; + rf->x = 0; + rf->y = 0; + } + + return 0; +} + +int write_ani_file(const char* outname) +{ + char filename[250]; + FILE* fani; + FILE* fscr; + int i; + int sync; + int imgind; + + verbose("Writing ANI, Script and PNG files\n"); + + sprintf(filename, "%s.ani", outname); + fani = fopen(filename, "wt"); + if (!fani) + return 211; + + sprintf(filename, "%s.txt", outname); + fscr = fopen(filename, "wt"); + if (!fscr) + { + fclose(fani); + return 211; + } + + for (i = 0, imgind = 0, sync = 0; i < cInfos; ++i, sync += framedelay) + { + file_info* rf = Infos + i; + int new_png; + int draw_cmd; + int batched; + + if (rf->identical == -1) + { // unique frame + new_png = 1; + draw_cmd = 1; + } + else + { // frame identical to another + new_png = 0; + + if (i > 0 && (rf->identical == i - 1 || rf->identical == Infos[i - 1].identical)) + draw_cmd = 0; // frame has not changed since last + else + draw_cmd = 1; // frame changed + + rf = Infos + rf->identical; + } + + if (draw_cmd) + fprintf(fscr, "#(Sync frame)\n" + "SYNC %d\n", sync); + + batched = 0; + for ( ; rf && rf->image; rf = rf->next) + { + if (new_png) + { // produce new png + rf->index = imgind++; + + sprintf(filename, "%s.%03d.png", outname, rf->index); + // produce PNG + write_png(filename, rf->image, rf->w, rf->h, palette, PAL_COLORS); + // add it to .ani + fprintf(fani, "%s -1 -1 %d %d\n", filename, -rf->x, -rf->y); + } + + if (draw_cmd) + { // add it to script + if (!batched && rf->next) + { // drawing a batch + batched = 1; + fprintf(fscr, "#(Batch several draws)\n" + "BATCH\n"); + } + fprintf(fscr, "#(Draw frame image)\n" + "DRAW %d\n", rf->index); + } + } + if (batched && draw_cmd) + { // unbatch + fprintf(fscr, "#(End of batch)\n" + "UNBATCH\n"); + } + + verbose("."); + } + // sync last frame + fprintf(fscr, "#(Sync frame)\n" + "SYNC %d\n", sync); + verbose("\n"); + + fclose(fscr); + fclose(fani); + + return 0; +} + +void usage() +{ + fprintf(stderr, "usage: lpf2ani [-v] [-f rate] [-r] [-s size] [-o out-name] \n"); + fprintf(stderr, "\tconverts DOS LPF animation file to .ani format and scripts the animation\n"); + fprintf(stderr, "\twill create .ani, .txt, and\n"); + fprintf(stderr, "\t8bpp paletted PNGs of mask ..png\n"); + fprintf(stderr, "options:\n"); + fprintf(stderr, " -v\t\t : be verbose, explains things no human should know\n"); + fprintf(stderr, " -f rate\t : override framerate; rate is 1..100 per second\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, " -p actname\t : produce PhotoShop palette file (.act)\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[]) +{ + char ch; + + while ((ch = getopt(argc, argv, "?hvdrf:s:o:p:")) != -1) + { + switch(ch) + { + case 'o': + opt_outname = optarg; + break; + case 'f': + opt_framerate = atoi(optarg); + if (opt_framerate < 1 || opt_framerate > 100) + { + fprintf(stderr, "invalid -f option value\n"); + usage(); + exit(EXIT_FAILURE); + } + break; + case 'd': + opt_deltamask = 1; + break; + case 'r': + opt_fullrects = 1; + break; + case 's': + opt_sectorsize = atoi(optarg); + if (opt_sectorsize < 8 || opt_sectorsize > 64) + { + fprintf(stderr, "invalid -r option value\n"); + usage(); + exit(EXIT_FAILURE); + } + break; + case 'v': + opt_verbose = 1; + break; + case 'p': + opt_actfile = optarg; + break; + case '?': + case 'h': + default: + usage(); + exit(EXIT_FAILURE); + } + } + + argc -= optind; + argv += optind; + if (argc != 1) + { + usage(); + exit(EXIT_FAILURE); + } + + opt_inname = argv[0]; +} + +int main(int argc, char* argv[]) +{ + FILE *f; + int ret = 0; + + parse_arguments(argc, argv); + + if (!opt_outname) + { + char* pch; + + opt_outname = (char*) calloc (strlen(opt_inname) + 6, 1); + if (!opt_outname) + return error (EXIT_FAILURE, "No memory", 0); + + pch = strrchr(opt_inname, '.'); + if (!pch) + pch = opt_inname + strlen(opt_inname); + + strncpy(opt_outname, opt_inname, pch - opt_inname + 1); + opt_outname[pch - opt_inname] = '\0'; + + verbose("Output name: %s\n", opt_outname); + } + + f = fopen(opt_inname, "rb"); + if (!f) + { + fprintf(stderr, "Cannot open %s\n", opt_inname); + return 1; + } + + verbose("Reading file %s\n", opt_inname); + + ret = readhead(f); + if (!ret) + ret = readpalette(f); + if (!ret) + { + framedelay = 1000 / hdr.fps; + if (opt_framerate) + { // override delay + framedelay = 1000 / opt_framerate; + } + verbose("using framedelay of %d, effective fps %d\n", framedelay, 1000 / framedelay); + } + + if (!ret && opt_actfile) + ret = writepalette(opt_actfile); + if (!ret) + { + fimgsize = hdr.w * hdr.h; + fimg = malloc(fimgsize); + if (!fimg) + ret = 101; + } + if (!ret) + ret = processpacks(f); + if (!ret) + ret = processframes(f); + + if (!ret) + { + delta_images(); + ret = write_ani_file(opt_outname); + } + + file_infos_free(); + fclose(f); + + return ret; +} diff --git a/tools/lpf2ani/lpf2ani.dsp b/tools/lpf2ani/lpf2ani.dsp new file mode 100644 index 000000000..562f25bc2 --- /dev/null +++ b/tools/lpf2ani/lpf2ani.dsp @@ -0,0 +1,110 @@ +# Microsoft Developer Studio Project File - Name="lpf2ani" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=lpf2ani - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "lpf2ani.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "lpf2ani.mak" CFG="lpf2ani - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "lpf2ani - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "lpf2ani - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=xicl6.exe +RSC=rc.exe + +!IF "$(CFG)" == "lpf2ani - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /MD /W3 /GX /O2 /I "..\..\src\getopt" /I "." /D "NDEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "PNG_USE_DLL" /YX /FD /c +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=xilink6.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 kernel32.lib user32.lib advapi32.lib shell32.lib libpng10.lib libz.lib /nologo /subsystem:console /machine:I386 + +!ELSEIF "$(CFG)" == "lpf2ani - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /MD /W3 /Gm /GX /ZI /Od /I "..\..\src\getopt" /I "." /D "_DEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "PNG_USE_DLL" /YX /FD /GZ /c +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=xilink6.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib advapi32.lib shell32.lib libpng10.lib libz.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "lpf2ani - Win32 Release" +# Name "lpf2ani - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\config.h +# End Source File +# Begin Source File + +SOURCE=..\..\src\getopt\getopt.c +# End Source File +# Begin Source File + +SOURCE=..\..\src\getopt\getopt.h +# End Source File +# Begin Source File + +SOURCE=.\lpf.h +# End Source File +# Begin Source File + +SOURCE=.\lpf2ani.c +# End Source File +# End Group +# End Target +# End Project diff --git a/tools/lpf2ani/lpf2ani.dsw b/tools/lpf2ani/lpf2ani.dsw new file mode 100644 index 000000000..2babe5af8 --- /dev/null +++ b/tools/lpf2ani/lpf2ani.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "lpf2ani"=.\lpf2ani.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### +