summaryrefslogtreecommitdiff
path: root/src/bmp.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/bmp.c')
-rw-r--r--src/bmp.c378
1 files changed, 378 insertions, 0 deletions
diff --git a/src/bmp.c b/src/bmp.c
new file mode 100644
index 000000000..c2bd91270
--- /dev/null
+++ b/src/bmp.c
@@ -0,0 +1,378 @@
+/* $Id$ */
+
+#include "stdafx.h"
+#include "openttd.h"
+#include "gfx.h"
+#include "bmp.h"
+#include "macros.h"
+
+void BmpInitializeBuffer(BmpBuffer *buffer, FILE *file) {
+ buffer->pos = -1;
+ buffer->file = file;
+ buffer->read = 0;
+ buffer->real_pos = ftell(file);
+}
+
+static inline void AdvanceBuffer(BmpBuffer *buffer)
+{
+ buffer->read = (int)fread(buffer->data, 1, BMP_BUFFER_SIZE, buffer->file);
+ buffer->pos = 0;
+}
+
+static inline bool EndOfBuffer(BmpBuffer *buffer)
+{
+ if (buffer->pos == buffer->read || buffer->pos < 0) AdvanceBuffer(buffer);
+ return buffer->pos == buffer->read;
+}
+
+static inline byte ReadByte(BmpBuffer *buffer)
+{
+ if (buffer->pos == buffer->read || buffer->pos < 0) AdvanceBuffer(buffer);
+ buffer->real_pos++;
+ return buffer->data[buffer->pos++];
+}
+
+static inline uint16 ReadWord(BmpBuffer *buffer)
+{
+ uint16 var = ReadByte(buffer);
+ return var | (ReadByte(buffer) << 8);
+}
+
+static inline uint32 ReadDword(BmpBuffer *buffer)
+{
+ uint32 var = ReadWord(buffer);
+ return var | (ReadWord(buffer) << 16);
+}
+
+static inline void SkipBytes(BmpBuffer *buffer, int bytes)
+{
+ int i;
+ for (i = 0; i < bytes; i++) ReadByte(buffer);
+}
+
+static inline void SetStreamOffset(BmpBuffer *buffer, int offset)
+{
+ fseek(buffer->file, offset, SEEK_SET);
+ buffer->pos = -1;
+ buffer->real_pos = offset;
+ AdvanceBuffer(buffer);
+}
+
+/**
+ * Reads a 1 bpp uncompressed bitmap
+ * The bitmap is converted to a 8 bpp bitmap
+ */
+static inline bool BmpRead1(BmpBuffer *buffer, BmpInfo *info, BmpData *data)
+{
+ uint x, y, i;
+ byte pad = GB(4 - info->width / 8, 0, 2);
+ byte *pixel_row;
+ byte b;
+ for (y = info->height; y > 0; y--) {
+ x = 0;
+ pixel_row = &data->bitmap[(y - 1) * info->width];
+ while (x < info->width) {
+ if (EndOfBuffer(buffer)) return false; // the file is shorter than expected
+ b = ReadByte(buffer);
+ for (i = 8; i > 0; i--) {
+ if (x < info->width) *pixel_row++ = GB(b, i - 1, 1);
+ x++;
+ }
+ }
+ /* Padding for 32 bit align */
+ SkipBytes(buffer, pad);
+ }
+ return true;
+}
+
+/**
+ * Reads a 4 bpp uncompressed bitmap
+ * The bitmap is converted to a 8 bpp bitmap
+ */
+static inline bool BmpRead4(BmpBuffer *buffer, BmpInfo *info, BmpData *data)
+{
+ uint x, y;
+ byte pad = GB(4 - info->width / 2, 0, 2);
+ byte *pixel_row;
+ byte b;
+ for (y = info->height; y > 0; y--) {
+ x = 0;
+ pixel_row = &data->bitmap[(y - 1) * info->width];
+ while (x < info->width) {
+ if (EndOfBuffer(buffer)) return false; // the file is shorter than expected
+ b = ReadByte(buffer);
+ *pixel_row++ = GB(b, 4, 4);
+ x++;
+ if (x < info->width) {
+ *pixel_row++ = GB(b, 0, 4);
+ x++;
+ }
+ }
+ /* Padding for 32 bit align */
+ SkipBytes(buffer, pad);
+ }
+ return true;
+}
+
+/**
+ * Reads a 4-bit RLE compressed bitmap
+ * The bitmap is converted to a 8 bpp bitmap
+ */
+static inline bool BmpRead4Rle(BmpBuffer *buffer, BmpInfo *info, BmpData *data)
+{
+ uint i;
+ uint x = 0;
+ uint y = info->height - 1;
+ byte n, c, b;
+ byte *pixel = &data->bitmap[y * info->width];
+ while (y != 0 || x < info->width) {
+ if (EndOfBuffer(buffer)) return false; // the file is shorter than expected
+ n = ReadByte(buffer);
+ c = ReadByte(buffer);
+ if (n == 0) {
+ switch (c) {
+ case 0: // end of line
+ x = 0;
+ pixel = &data->bitmap[--y * info->width];
+ break;
+ case 1: // end of bitmap
+ x = info->width;
+ y = 0;
+ pixel = NULL;
+ break;
+ case 2: // delta
+ x += ReadByte(buffer);
+ i = ReadByte(buffer);
+ if (x >= info->width || (y == 0 && i > 0)) return false;
+ y -= i;
+ pixel = &data->bitmap[y * info->width + x];
+ break;
+ default: // uncompressed
+ i = 0;
+ while (i++ < c) {
+ if (EndOfBuffer(buffer) || x >= info->width) return false;
+ b = ReadByte(buffer);
+ *pixel++ = GB(b, 4, 4);
+ x++;
+ if (x < info->width && i++ < c) {
+ *pixel++ = GB(b, 0, 4);
+ x++;
+ }
+ }
+ /* Padding for 16 bit align */
+ SkipBytes(buffer, ((c + 1) / 2) % 2);
+ break;
+ }
+ } else {
+ i = 0;
+ while (i++ < n) {
+ if (EndOfBuffer(buffer) || x >= info->width) return false;
+ *pixel++ = GB(c, 4, 4);
+ x++;
+ if (x < info->width && i++ < n) {
+ *pixel++ = GB(c, 0, 4);
+ x++;
+ }
+ }
+ }
+ }
+ return true;
+}
+
+/**
+ * Reads a 8 bpp bitmap
+ */
+static inline bool BmpRead8(BmpBuffer *buffer, BmpInfo *info, BmpData *data)
+{
+ uint i;
+ uint y;
+ byte pad = GB(4 - info->width, 0, 2);
+ byte *pixel;
+ for (y = info->height; y > 0; y--) {
+ if (EndOfBuffer(buffer)) return false; // the file is shorter than expected
+ pixel = &data->bitmap[(y - 1) * info->width];
+ for (i = 0; i < info->width; i++) *pixel++ = ReadByte(buffer);
+ /* Padding for 32 bit align */
+ SkipBytes(buffer, pad);
+ }
+ return true;
+}
+
+/**
+ * Reads a 8-bit RLE compressed bpp bitmap
+ */
+static inline bool BmpRead8Rle(BmpBuffer *buffer, BmpInfo *info, BmpData *data)
+{
+ uint i;
+ uint x = 0;
+ uint y = info->height - 1;
+ byte n, c;
+ byte *pixel = &data->bitmap[y * info->width];
+ while (y != 0 || x < info->width) {
+ if (EndOfBuffer(buffer)) return false; // the file is shorter than expected
+ n = ReadByte(buffer);
+ c = ReadByte(buffer);
+ if (n == 0) {
+ switch (c) {
+ case 0: // end of line
+ x = 0;
+ pixel = &data->bitmap[--y * info->width];
+ break;
+ case 1: // end of bitmap
+ x = info->width;
+ y = 0;
+ pixel = NULL;
+ break;
+ case 2: // delta
+ x += ReadByte(buffer);
+ i = ReadByte(buffer);
+ if (x >= info->width || (y == 0 && i > 0)) return false;
+ y -= i;
+ pixel = &data->bitmap[y * info->width + x];
+ break;
+ default: // uncompressed
+ if ((x += c) > info->width) return false;
+ for (i = 0; i < c; i++) *pixel++ = ReadByte(buffer);
+ /* Padding for 16 bit align */
+ SkipBytes(buffer, c % 2);
+ break;
+ }
+ } else {
+ for (i = 0; i < n; i++) {
+ if (x >= info->width) return false;
+ *pixel++ = c;
+ x++;
+ }
+ }
+ }
+ return true;
+}
+
+/**
+ * Reads a 24 bpp uncompressed bitmap
+ */
+static inline bool BmpRead24(BmpBuffer *buffer, BmpInfo *info, BmpData *data)
+{
+ uint x, y;
+ byte pad = GB(4 - info->width * 3, 0, 2);
+ byte *pixel_row;
+ for (y = info->height; y > 0; y--) {
+ pixel_row = &data->bitmap[(y - 1) * info->width * 3];
+ for (x = 0; x < info->width; x++) {
+ if (EndOfBuffer(buffer)) return false; // the file is shorter than expected
+ *(pixel_row + 2) = ReadByte(buffer); // green
+ *(pixel_row + 1) = ReadByte(buffer); // blue
+ *pixel_row = ReadByte(buffer); // red
+ pixel_row += 3;
+ }
+ /* Padding for 32 bit align */
+ SkipBytes(buffer, pad);
+ }
+ return true;
+}
+
+/*
+ * Reads bitmap headers, and palette (if any)
+ */
+bool BmpReadHeader(BmpBuffer *buffer, BmpInfo *info, BmpData *data)
+{
+ uint32 header_size;
+ assert(info != NULL);
+
+ /* Reading BMP header */
+ if (ReadWord(buffer) != 0x4D42) return false; // signature should be 'BM'
+ SkipBytes(buffer, 8); // skip file size and reserved
+ info->offset = ReadDword(buffer);
+
+ /* Reading info header */
+ header_size = ReadDword(buffer);
+ if (header_size < 12) return false; // info header should be at least 12 bytes long
+
+ info->os2_bmp = (header_size == 12); // OS/2 1.x or windows 2.x info header is 12 bytes long
+
+ if (info->os2_bmp) {
+ info->width = ReadWord(buffer);
+ info->height = ReadWord(buffer);
+ header_size -= 8;
+ } else {
+ info->width = ReadDword(buffer);
+ info->height = ReadDword(buffer);
+ header_size -= 12;
+ }
+
+ if (ReadWord(buffer) != 1) return false; // BMP can have only 1 plane
+
+ info->bpp = ReadWord(buffer);
+ if (info->bpp != 1 && info->bpp != 4 && info->bpp != 8 && info->bpp != 24) {
+ /* Only 1 bpp, 4 bpp, 8bpp and 24 bpp bitmaps are supported */
+ return false;
+ }
+
+ /* Reads compression method if available in info header*/
+ if ((header_size -= 4) >= 4) {
+ info->compression = ReadDword(buffer);
+ header_size -= 4;
+ }
+
+ /* Only 4-bit and 8-bit rle compression is supported */
+ if (info->compression > 2 || (info->compression > 0 && !(info->bpp == 4 || info->bpp == 8))) return false;
+
+ if (info->bpp <= 8) {
+ uint i;
+
+ /* Reads number of colors if available in info header */
+ if (header_size >= 16) {
+ SkipBytes(buffer, 12); // skip image size and resolution
+ info->palette_size = ReadDword(buffer); // number of colors in palette
+ SkipBytes(buffer, header_size - 16); // skip the end of info header
+ }
+ if (info->palette_size == 0) info->palette_size = 1 << info->bpp;
+
+ data->palette = calloc(info->palette_size, sizeof(*(data->palette)));
+ if (data->palette == NULL) return false;
+
+ for (i = 0; i < info->palette_size; i++) {
+ data->palette[i].b = ReadByte(buffer);
+ data->palette[i].g = ReadByte(buffer);
+ data->palette[i].r = ReadByte(buffer);
+ if (!info->os2_bmp) SkipBytes(buffer, 1); // unused
+ }
+ }
+
+ return buffer->real_pos <= info->offset;
+}
+
+/*
+ * Reads the bitmap
+ * 1 bpp and 4 bpp bitmaps are converted to 8 bpp bitmaps
+ */
+bool BmpReadBitmap(BmpBuffer *buffer, BmpInfo *info, BmpData *data)
+{
+ assert(info != NULL && data != NULL);
+
+ data->bitmap = calloc(info->width * info->height, ((info->bpp == 24) ? 3 : 1) * sizeof(byte));
+ if (data->bitmap == NULL) return false;
+
+ /* Load image */
+ SetStreamOffset(buffer, info->offset);
+ switch (info->compression) {
+ case 0: // no compression
+ switch (info->bpp) {
+ case 1: return BmpRead1(buffer, info, data);
+ case 4: return BmpRead4(buffer, info, data);
+ case 8: return BmpRead8(buffer, info, data);
+ case 24: return BmpRead24(buffer, info, data);
+ default: NOT_REACHED(); return false;
+ }
+ case 1: return BmpRead8Rle(buffer, info, data); // 8-bit RLE compression
+ case 2: return BmpRead4Rle(buffer, info, data); // 4-bit RLE compression
+ default: NOT_REACHED(); return false;
+ }
+}
+
+void BmpDestroyData(BmpData *data)
+{
+ assert(data != NULL);
+ free(data->palette);
+ free(data->bitmap);
+}