/* * ============================================================================ * DATA_FMT: Data format conversion functions * * Author: J. Zbiciak * Last Revision: 8/29/98 * ============================================================================ * * Exported functionality implemented in this file: * * FMT_RLE_ENCODE -- RLE-encodes an image or font. * FMT_RLE_DECODE -- RLE-decodes an image or font. * FMT_DECLE_TO_8P2 -- Converts decles stored in shorts to 8+2 format * FMT_8P2_TO_DECLE -- Converts decles stored in 8+2 format to shorts * * Internal helper functionality. * * ENCODE_IMG_HDR -- encodes an image header * DECODE_IMG_HDR -- decodes an image header * HDR_LEN_IMG -- length of image record's header * * ============================================================================ */ #include "config.h" #include /* for sprintf() */ #include /* for NULL */ #include /* for size_t */ #include "res_file.h" /* for resource file structure types. */ #include "macros.h" /* for pack/unpack macros. */ /* * ============================================================================ * ============================================================================ * NON-EXPORTED FUNCTION PROTOTYPES AND FILE-LEVEL MACROS * ============================================================================ * ============================================================================ */ static void encode_img_hdr (void *raw, res_img_t *header); static void decode_img_hdr (void *raw, res_img_t *header); #define HDR_LEN_IMG (8) /* * ============================================================================ * ============================================================================ * EXPORTED FUNCTION DEFINITIONS * ============================================================================ * ============================================================================ */ /* * ============================================================================ * FMT_RLE_ENCODE -- RLE-encodes an image or font. * * Accepts a pointer to an image or font record, and RLE-encodes it. Also * packs the header, etc. into the memory image. Relies on encode_img_hdr. * ============================================================================ */ int fmt_rle_encode ( res_rec_t *record ) { res_img_t *img; unsigned char * encoded, u, v = 0, run = 0, esc = 0, run_val; unsigned char * esc_q, *pi, *po; int i, j; size_t l, mem_size; /* --------------------------------------------------------------------- */ /* Encoded image must be no larger than decoded image, so start with a */ /* work buffer that is the same size as the decoded image plus room */ /* for the image file header. */ /* --------------------------------------------------------------------- */ img = record->res.img; i = j = l = (int)img->x_dim * (int)img->y_dim; mem_size = l + HDR_LEN_IMG; record->len = mem_size; encoded = record->lib_state->malloc(mem_size); if (!encoded) { res_report_err(record, RES_e_no_mem); return -1; } esc_q = pi = img->img; po = encoded + HDR_LEN_IMG; /* --------------------------------------------------------------------- */ /* The encoder main loop works like so: */ /* */ /* -- Identify a run. */ /* */ /* -- If a run or escape queue gets to be longer than MAX_RUN, then */ /* flush it immediately, since the maximum run we can represent */ /* is MAX_RUN. Larger runs are composed of many smaller runs. */ /* */ /* -- If run is too short, add it to the queue of runs to escape. */ /* */ /* -- If run is long enough, flush the escape queue first, and */ /* then output the run. If the escape queue is empty, then */ /* 2 is long enough, else 3 is long enough. */ /* */ /* -- Repeat until one of the following */ /* */ /* -- If we run out of output buffer, abort compression process and */ /* just store the decompressed image (since it's smaller than */ /* the compressed copy). */ /* */ /* -- If we run out of input buffer, we're done. */ /* --------------------------------------------------------------------- */ u = *pi++; i--; run = 1; #define MAX_RUN (127) #define MIN_RUN (3) while (i-->0 && j > 3 + esc) { /* ----------------------------------------------------------------- */ /* Read a pixel and determine if it is part of the current run. */ /* If so, increment the run length. */ /* ----------------------------------------------------------------- */ v = *pi++; if (v == u) run++; /* ----------------------------------------------------------------- */ /* Process the current run in one of the following situations: */ /* -- We're at the end of a run, */ /* -- The current run is longer than MAX_RUN pixels */ /* ----------------------------------------------------------------- */ if (v != u || run > MAX_RUN) { /* ------------------------------------------------------------- */ /* If the current run is long enough (or we're at EOF), output */ /* this run as a "repeat run". Be sure to flush the escape */ /* queue first, if anything is in it. */ /* ------------------------------------------------------------- */ if (run >= MIN_RUN || (run == MIN_RUN-1 && esc == 0)) { while (j > 1+esc && esc > 0) { run_val = esc > MAX_RUN ? MAX_RUN : esc; esc -= run_val; j -= run_val + 1; *po++ = run_val << 1; while (run_val-->0) *po++ = *esc_q++; } j -= 2; run_val = run > MAX_RUN ? MAX_RUN : run; *po++ = (run_val << 1) + 1; *po++ = u; esc_q = pi - 1; } else /* ------------------------------------------------------------- */ /* Add the current run to the "escape queue". The contents of */ /* this queue will be output as a "copy run" eventually. If */ /* the "escape queue" gets too full, flush it right now. */ /* ------------------------------------------------------------- */ { esc += run; while (j > MAX_RUN+1 && esc > MAX_RUN) { run_val = MAX_RUN; esc -= run_val; j -= run_val + 1; *po++ = run_val << 1; while (run_val-->0) *po++ = *esc_q++; } } /* ------------------------------------------------------------- */ /* Reset our run counter and start tallying the new run. */ /* ------------------------------------------------------------- */ u = v; run = 1; } } /* --------------------------------------------------------------------- */ /* End of file cleanup. If we had a short run at the end, append it */ /* to the escape queue. In any case, output the escape queue, final */ /* run (if any) and final byte (if not part of the run). */ /* --------------------------------------------------------------------- */ if (run < MIN_RUN && esc) { esc += run; if (u != v) esc++; while (j > MAX_RUN+1 && esc > MAX_RUN) { esc -= MAX_RUN; run = MAX_RUN; j -= MAX_RUN; *po++ = MAX_RUN << 1; while (run-->0) *po++ = *esc_q++; } run = 0; } if (j >= (1 + esc) && esc > 0) { j -= esc + 1; *po++ = esc << 1; while (esc-->0) *po++ = *esc_q++; esc = 0; } if (j >= 2 && run > 0) { j -= 2; run_val = run > MAX_RUN ? MAX_RUN : run; *po++ = (run_val << 1) + 1; *po++ = u; esc_q = pi - 1; run = u != v; } if (j >= 2 && run > 0) { j -= 2; *po++ = 2; *po++ = v; run = 0; } #undef MAX_RUN #undef MIN_RUN /* --------------------------------------------------------------------- */ /* Check to see if we managed to actually compress the data. If we */ /* didn't, then just copy over the uncompressed data into the our */ /* work area and set our compressed length to equal to 0. Otherwise */ /* resize the allocated memory to match our actual encoded size. */ /* --------------------------------------------------------------------- */ if (i != -1 || esc != 0 || run != 0 || j == 0) { img->len = 0; memcpy(encoded + HDR_LEN_IMG, img->img, l); } else { record->len = mem_size - j; img->len = l - j; encoded = record->lib_state->realloc(encoded, mem_size - j); if (!encoded) { res_report_err(record, RES_e_no_mem); return -1; } } /* --------------------------------------------------------------------- */ /* Pack the image information into the header portion of the mem image */ /* --------------------------------------------------------------------- */ encode_img_hdr(encoded, img); /* --------------------------------------------------------------------- */ /* Now, free the decoded image and its info structure. Put the */ /* encoded memory image in its place, and return SUCCESS! */ /* --------------------------------------------------------------------- */ record->lib_state->free(img->img); record->lib_state->free(img); record->res.data = encoded; return 0; } /* * ============================================================================ * FMT_RLE_DECODE -- RLE-decodes an image or font. * * Accepts a pointer to an image or font record, and decodes it. Also * allocates a res_img_t structure and unpacks the header into it. Relies * on decode_img_hdr for this. * ============================================================================ */ int fmt_rle_decode ( res_rec_t *record ) { res_img_t *img; unsigned char * decoded, *p0, *p1, *po, u, v = 0, run = 0, type = 0, r; int i, j; /* --------------------------------------------------------------------- */ /* First order of business: Allocate a res_img_t so we can unpack the */ /* header information -- eg. image dimensions, etc. */ /* --------------------------------------------------------------------- */ img = record->lib_state->malloc(sizeof(res_img_t)); if (!img) { res_report_err(record, RES_e_no_mem); return -1; } decode_img_hdr(record->res.data, img); /* --------------------------------------------------------------------- */ /* Next, allocate some memory for the decoded image. Handle the case */ /* where the image isn't actually compressed here with a simple memcpy. */ /* --------------------------------------------------------------------- */ i = (int)img->x_dim * (int)img->y_dim; j = img->len; img->img = decoded = record->lib_state->malloc(i); if (!decoded) { res_report_err(record, RES_e_no_mem); return -1; } if (!img->len) { /* ----------------------------------------------------------------- */ /* Perform the memcpy, free old memory image, and get outta here! */ /* ----------------------------------------------------------------- */ memcpy(decoded, ((char *)record->res.data) + HDR_LEN_IMG, i); record->lib_state->free(record->res.data); record->res.img = img; return 0; } p0 = (unsigned char *)record->res.data + HDR_LEN_IMG; p1 = (unsigned char *)record->res.data + HDR_LEN_IMG + 1; po = decoded; /* --------------------------------------------------------------------- */ /* Decode the image by pulling out Run, Data pairs, copying them to */ /* the output image. Avoid overflowing the output image and */ /* underflowing the incoming data. (Shouldn't crash on bogus data!) */ /* --------------------------------------------------------------------- */ while (i-->0 && j >= 0) { r = !run; if (r) { u = *p0; type = u & 1; run = u >> 1; p1 = p0 + 1; v = *p1++; } if (r & type) { p0 += 2; j -= 2; } if (r & !type) { p0 += run + 1; j -= run + 1; } *po++ = v; if (!type) { v = *p1++; } run--; } /* --------------------------------------------------------------------- */ /* If our compressed data buffer isn't empty, or if our decompressed */ /* data buffer isn't full, or we're in the middle of a run, then we */ /* had a decoding error. */ /* --------------------------------------------------------------------- */ if (j != 0 || i != -1 || run) /* decoder error */ { res_report_err(record, RES_e_rle_decode); record->lib_state->free(img->img); record->lib_state->free(img); return -1; } /* --------------------------------------------------------------------- */ /* Now that we've successfully decoded the image, discard the packed */ /* memory image and install the decoded image record instead. Lastly, */ /* return SUCCESS! */ /* --------------------------------------------------------------------- */ record->lib_state->free(record->res.data); record->res.img = img; return 0; } /* * ============================================================================ * FMT_DECLE_TO_8P2 -- Converts decles stored in shorts to 8+2 format * * XXX: See "unpack_decles" below for current hack implementation. This * still needs to be rewritten in "library" form. * ============================================================================ */ int fmt_decle_to_8p2 ( res_rec_t *record ) { res_report_err(record, RES_e_not_impl); return -1; } /* * ============================================================================ * UNPACK_DECLES -- Takes a set of packed decles, and unpacks them * * This function accepts an array of bytes that correspond to a set of * packed decles. It returns an array of shorts which correspond to the * unpacked decles. * * The output array must have at least 4/5ths as many elements as the * input array. The output array, though, is comprised of shorts, so its * size is 8/5ths the size of the original array. * ============================================================================ */ void unpack_decles ( size_t len, unsigned char i_data[], unsigned short o_data[] ) { unsigned i, decle_count; unsigned lo_half, hi_half; decle_count = 4 * len / 5; if ( (decle_count * 5 / 4) != len) { printf("Warning: len %d doesn't seem right.\n", len); } for (i = 0; i < decle_count; i++) { lo_half = i_data[i]; hi_half = (i_data[decle_count + (i >> 2)] >> (((i & 3))<< 1))&3; o_data[i] = (hi_half << 8) | lo_half; } } /* * ============================================================================ * FMT_8P2_TO_DECLE -- Converts decles stored in 8+2 format to shorts * * XXX: See "repack_decles" below for current hack implementation. This * still needs to be rewritten in "library" form. * ============================================================================ */ int fmt_8p2_to_decle ( res_rec_t *record ) { res_report_err(record, RES_e_not_impl); return -1; } /* * ============================================================================ * REPACK_DECLES -- Takes a set of unpacked decles, and repacks them * * This function accepts an array of shorts that correspond to a set of * unpacked decles. It returns an array of bytes which correspond to the * packed decles. * * The output array must have 5/4ths as many elements as the input array. * Since the output array is comprised of bytes, and the input array is * comprised of shorts, the output array should be at least 5/8th the size * of the input array. * ============================================================================ */ void repack_decles ( size_t len, unsigned short i_data[], unsigned char o_data[] ) { unsigned i, j, decle_count; unsigned lo_half, hi_half; decle_count = len; for (i = 0, j = decle_count; i < decle_count; i++) { j = decle_count + (i>>2); lo_half = i_data[i] & 0xFF; hi_half = ((i_data[i] >> 8) & 3) << ((i & 3) << 1); o_data[i] = lo_half; o_data[j] = (o_data[j] & ~(3 << ((i & 3) << 1))) | hi_half; } } /* * ============================================================================ * ============================================================================ * NON-EXPORTED FUNCTION DEFINITIONS * ============================================================================ * ============================================================================ */ /* * ============================================================================ * ENCODE_IMG_HDR -- encodes an image header * DECODE_IMG_HDR -- decodes an image header * * Note: Fonts appear to use an identical file format to images, despite the * fact that they have different type numbers. So, for now, I reuse the * same routines for both. * * Image File Header Format: * * Offset Length Format Description * ------ ------ ------ -------------------------------------------- * 0 2 Word Width of uncompressed image * 2 2 Word Height of uncompressed image * 4 2 Word Length of compressed data, (0 == not compr.) * 6 2 Word Colors in palette (0 = no palette) * 8 len RLE Run-length encoded data * ============================================================================ */ static void encode_img_hdr (void *raw, res_img_t *header) { PACK_I16(raw, 0, header->x_dim); PACK_I16(raw, 2, header->y_dim); PACK_I16(raw, 4, header->len ); ZERO_RAW(raw, 6, 2); } static void decode_img_hdr (void *raw, res_img_t *header) { unsigned pal; UNPACK_I16(raw, 0, header->x_dim); UNPACK_I16(raw, 2, header->y_dim); UNPACK_I16(raw, 4, header->len ); UNPACK_I16(raw, 6, pal ); if (pal != 0) { fprintf(stderr,"Error: Image with palette detected. UNSUPPORTED!\n"); exit(1); } } /* * ============================================================================ * Copyright (c) 1998, Joseph Zbiciak. * ============================================================================ */