/* * 2ooM: The Master of Orion II Reverse Engineering Project * Library for working with LBX image files. * Copyright (C) 2006-2008 Nick Bowler * * 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 3 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, see . */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include "pack.h" #include "misc.h" #include "lbx.h" #include "image.h" #define FLAG_OVERWRITE 0x0400 /* Draw each frame on a clean slate (unsure). */ #define FLAG_PALETTE 0x1000 /* Image contains embedded palette. */ #define FLAG_LOOPING 0x2000 /* Loop over all frames in the image (unsure). */ #define FLAG_ALL (FLAG_OVERWRITE|FLAG_PALETTE|FLAG_LOOPING) #define HDR_LEN 12 struct lbx_image { unsigned short width, height; unsigned short wtf, flags; unsigned short frames, leadin; unsigned short palstart, palcount; FILE *f; long foff, paloff; int currentframe; int currentx, currenty; unsigned char **framedata; unsigned char **mask; unsigned long offsets[]; }; static struct lbx_image *lbximg_init(unsigned char hdr[static HDR_LEN]) { unsigned short nframes = unpack_16_le(hdr+6); struct lbx_image *img; img = malloc(sizeof *img + sizeof img->offsets[0] * (nframes+1)); if (!img) { lbx_errno = -errno; return NULL; } *img = (struct lbx_image) { .width = unpack_16_le(hdr+0), .height = unpack_16_le(hdr+2), .wtf = unpack_16_le(hdr+4), .frames = unpack_16_le(hdr+6), .leadin = unpack_16_le(hdr+8), .flags = unpack_16_le(hdr+10), .currentframe = -1, }; return img; } struct lbx_image *lbximg_fopen(FILE *f) { unsigned char hdr_buf[HDR_LEN]; struct lbx_image *img; size_t rc; if (fread(hdr_buf, 1, sizeof hdr_buf, f) != sizeof hdr_buf) { lbx_errno = -errno; if (feof(f)) lbx_errno = LBX_EEOF; return NULL; } img = lbximg_init(hdr_buf); if (!img) return NULL; img->f = f; img->foff = sizeof hdr_buf; /* * DEBUG ONLY. These assertions exist to catch otherwise valid image * files which differ from what I believe to be true of all LBX images. * If we never find any exceptions, we can replace the assertions with * assumptions. */ _lbx_assert(img->wtf == 0); /* version? */ _lbx_assert(img->frames > img->leadin); /* cmbtshp.lbx breaks this. */ _lbx_assert(!(img->flags & ~FLAG_ALL)); /* Read all offsets. Should be merged with identical code in lbx.c */ for (unsigned i = 0; i <= img->frames; i++) { unsigned char buf[4]; if (fread(buf, 1, sizeof buf, f) != sizeof buf) { lbx_errno = -errno; if (feof(f)) lbx_errno = LBX_EEOF; free(img); return NULL; } img->offsets[i] = unpack_32_le(buf); img->foff += 4; } if (img->flags & FLAG_PALETTE) { unsigned char buf[4]; if (fread(buf, 1, sizeof buf, f) != sizeof buf) { lbx_errno = -errno; if (feof(f)) lbx_errno = LBX_EEOF; free(img); return NULL; } img->palstart = unpack_16_le(buf+0); img->palcount = unpack_16_le(buf+2); img->foff += sizeof buf; img->paloff = img->foff; if (img->palstart + img->palcount > 256) { lbx_errno = LBX_EFORMAT; free(img); return NULL; } } return img; } static int _lbx_drawrow(int first, struct lbx_image *img) { unsigned short type, count, yval, xval; unsigned char buf[4]; unsigned char *pos; size_t rc; assert(img->framedata); assert(img->mask); if (fread(buf, 1, sizeof buf, img->f) != sizeof buf) goto readerr; img->foff += 4; type = unpack_16_le(buf+0); if (first) { img->currentx = 0; img->currenty = 0; type = 0; } if (type == 0) { yval = unpack_16_le(buf+2); if (yval == 1000) return 1; if (fread(buf, 1, sizeof buf, img->f) != sizeof buf) goto readerr; img->foff += 4; count = unpack_16_le(buf+0); xval = unpack_16_le(buf+2); if (xval == 1000) return 1; /* Ensure that the row fits in the image. */ if (img->height - img->currenty <= yval || xval >= img->width) { lbx_errno = LBX_EFORMAT; return -1; } img->currenty += yval; img->currentx = xval; } else { xval = unpack_16_le(buf+2); if (img->width - img->currentx <= xval) { lbx_errno = LBX_EFORMAT; return -1; } img->currentx += xval; count = type; } if (count > img->width - img->currentx) { lbx_errno = LBX_EFORMAT; return -1; } memset(&img->mask[img->currenty][img->currentx], 1, count); pos = &img->framedata[img->currenty][img->currentx]; rc = fread(pos, 1, count, img->f); img->currentx += rc; img->foff += rc; if (rc < count) goto readerr; if (count % 2) { if (fread(buf, 1, 1, img->f) != 1) goto readerr; img->foff += 1; } return 0; readerr: if (feof(img->f)) { lbx_errno = LBX_EEOF; } else { lbx_errno = -errno; } return -1; } static unsigned char **allocframebuffer(size_t width, size_t height) { unsigned char **new, *tmp; size_t i; tmp = calloc(height, width); if (!tmp) { lbx_errno = -errno; return NULL; } new = malloc(height * sizeof *new); if (!new) { lbx_errno = -errno; free(tmp); return NULL; } for (i = 0; i < height; i++) { new[i] = tmp + i * width; } return new; } unsigned char **lbximg_getframe(struct lbx_image *img, int frame) { if (frame >= img->frames || frame < 0) { lbx_errno = LBX_ERANGE; return NULL; } if (!img->framedata) { img->framedata = allocframebuffer(img->width, img->height); if (!img->framedata) return NULL; } if (!img->mask) { img->mask = allocframebuffer(img->width, img->height); if (!img->mask) return NULL; } /* Start over if we are backtracking. */ if (img->currentframe > frame) img->currentframe == -1; if (img->flags & FLAG_OVERWRITE) { /* Clear the slate. */ memset(img->framedata[0], 0, img->width * img->height); memset(img->mask[0], 0, img->width * img->height); } else { /* We must have previous frame decoded to continue. */ if (frame > img->currentframe + 1) { if (!lbximg_getframe(img, frame-1)) return NULL; } } if (img->currentframe != frame) { int rc, first = 1; if (_lbx_fseek(img->f, &img->foff, img->offsets[frame]) == -1) return NULL; do { rc = _lbx_drawrow(first, img); if (rc == -1) return NULL; first = 0; if (!rc && img->foff > img->offsets[frame+1]) { lbx_errno = LBX_EFORMAT; return NULL; } } while (!rc); } img->currentframe = frame; return img->framedata; } int lbximg_loadpalette(FILE *f, struct lbx_colour palette[static 256]) { unsigned char entry[4]; int i; for (i = 0; i < 256; i++) { if (fread(entry, sizeof entry, 1, f) != 1) { lbx_errno = (feof(f)) ? LBX_EEOF : -errno; return -1; } if (entry[0] != 1) { lbx_errno = LBX_EFORMAT; return -1; } palette[i] = (struct lbx_colour){ .red = entry[1] << 2, .green = entry[2] << 2, .blue = entry[3] << 2, }; } return 0; } int lbximg_getpalette(struct lbx_image *img, struct lbx_colour palette[static 256]) { unsigned char entry[4]; unsigned int i; size_t rc; /* Do nothing if the image doesn't have embedded palette data. */ if (!(img->flags & FLAG_PALETTE)) return 0; if (_lbx_fseek(img->f, &img->foff, img->paloff) == -1) return -1; for (i = 0; i < img->palcount; i++) { rc = fread(entry, 1, sizeof entry, img->f); img->foff += rc; if (rc < sizeof entry) { goto readerr; } if (entry[0] != 0) { lbx_errno = LBX_EFORMAT; return -1; } palette[img->palstart + i] = (struct lbx_colour){ .red = entry[1] << 2, .green = entry[2] << 2, .blue = entry[3] << 2, }; } return 0; readerr: lbx_errno = feof(img->f) ? LBX_EEOF : -errno; return -1; } void lbximg_getinfo(struct lbx_image *img, struct lbx_imginfo *info) { *info = (struct lbx_imginfo) { .width = img->width, .height = img->height, .nframes = img->frames, .palettesz = (img->flags & FLAG_PALETTE) ? img->palcount : 0, }; /* There seems to be two ways of specifying that an image loops. */ if (img->flags & FLAG_LOOPING) { info->loopstart = 0; info->looping = 1; } else if (img->leadin != img->frames - 1) { info->loopstart = img->leadin; info->looping = 1; } } unsigned char **lbximg_getmask(struct lbx_image *img) { return img->mask; } void lbximg_close(struct lbx_image *img) { if (!img) return; if (img->framedata) { free(img->framedata[0]); free(img->framedata); } if (img->mask) { free(img->mask[0]); free(img->mask); } if (img->f) { fclose(img->f); } free(img); }