Commits

Anonymous committed d4fafbb

Support for RLE, at the moment the option to enable it is not exposed until further cleanup and testing.

Comments (0)

Files changed (2)

 #define BMP_RLE_ENDOFBITMAP 1
 #define BMP_RLE_DELTA 2
 
+#define BMP_RLE_TYPE_RAW 0
+#define BMP_RLE_TYPE_RLE 1
+
 /* BMP header. */
 typedef struct
 {
 #include "gdhelpers.h"
 #include "bmp.h"
 
+static int compress_row(unsigned char *uncompressed_row, int length);
+static int build_rle_packet(unsigned char *row, int packet_type, int length, unsigned char *data);
+
 static int bmp_read_header(gdIOCtxPtr infile, bmp_hdr_t *hdr);
 static int bmp_read_info(gdIOCtxPtr infile, bmp_info_t *info);
 static int bmp_read_windows_v3_info(gdIOCtxPtr infile, bmp_info_t *info);
 
 BGD_DECLARE(void) gdImageBmpCtx(gdImagePtr im, gdIOCtxPtr out)
 {
-	int bitmap_size, info_size, total_size, padding;
+	int bitmap_size = 0, info_size, total_size, padding;
 	int i, row, xpos, pixel;
+	int compression = 0;
+	unsigned char *uncompressed_row = NULL, *uncompressed_row_start = NULL;
 
 	bitmap_size = ((im->sx * (im->trueColor ? 24 : 8)) / 8) * im->sy;
+
 	/* 40 byte Windows v3 header */
 	info_size = BMP_WINDOWS_V3;
 
 	/* data for the palette */
-	if (!im->trueColor)
-	{
+	if (!im->trueColor) {
 		info_size += im->colorsTotal * 4;
+		if (compression) {
+			bitmap_size = 0;
+		}
 	}
 
 	/* bitmap header + info header + data */
 	total_size = 14 + info_size + bitmap_size;
 
+	/* we need seek support at this moment in time */
+	if (!out->seek) {
+		return;
+	}
+
 	/* write bmp header info */
 	gdPutBuf("BM", 2, out);
 	gdBMPPutInt(out, total_size);
 	gdBMPPutWord(out, 0);
 	gdBMPPutWord(out, 0);
 	gdBMPPutInt(out, 14 + info_size);
-	
+
 	/* write Windows v3 headers */
 	gdBMPPutInt(out, BMP_WINDOWS_V3); /* header size */
 	gdBMPPutInt(out, im->sx); /* width */
 	gdBMPPutInt(out, im->sy); /* height */
 	gdBMPPutWord(out, 1); /* colour planes */
 	gdBMPPutWord(out, (im->trueColor ? 24 : 8)); /* bit count */
-	gdBMPPutInt(out, BMP_BI_RGB); /* compression */
+	gdBMPPutInt(out, ((!compression || im->trueColor) ? BMP_BI_RGB : BMP_BI_RLE8)); /* compression */
 	gdBMPPutInt(out, bitmap_size); /* image size */
 	gdBMPPutInt(out, 0); /* H resolution */
 	gdBMPPutInt(out, 0); /* V ressolution */
 
 	/* 8-bit colours */
 	if (!im->trueColor) {
-		for(i = 0; i< im->colorsTotal; ++i) {			
+		for(i = 0; i< im->colorsTotal; ++i) {
 			Putchar(gdImageBlue(im, i), out);
 			Putchar(gdImageGreen(im, i), out);
 			Putchar(gdImageRed(im, i), out);
 			Putchar(0, out);
         }
+
+		if (compression) {
+			/* Can potentially change this to X + ((X / 128) * 3) */
+			uncompressed_row = uncompressed_row_start = (unsigned char *) gdMalloc(gdImageSX(im) * 2);
+			if (!uncompressed_row) {
+				/* malloc failed */
+				return;
+			}
+			memset (uncompressed_row, 0, gdImageSX(im) * 2);
+		}
+
         for (row = (im->sy - 1); row >= 0; row--) {
+			if (compression) {
+				memset (uncompressed_row, 0, gdImageSX(im));
+			}
+
 			for (xpos = 0; xpos < im->sx; xpos++) {
-				Putchar(gdImageGetPixel(im, xpos, row), out);
+				if (compression) {
+					*uncompressed_row++ = (unsigned char)gdImageGetPixel(im, xpos, row);
+				} else {
+					Putchar(gdImageGetPixel(im, xpos, row), out);
+				}
 			}
-			/* Add padding to make sure we have n mod 4 == 0 bytes per row */
-			for (xpos = padding; xpos > 0; --xpos) {
-				Putchar('\0', out);
+
+			if (!compression) {
+				/* Add padding to make sure we have n mod 4 == 0 bytes per row */
+				for (xpos = padding; xpos > 0; --xpos) {
+					Putchar('\0', out);
+				}
+			} else {
+				int compressed_size = 0;
+				uncompressed_row = uncompressed_row_start;
+				compressed_size = compress_row(uncompressed_row, gdImageSX(im));
+				bitmap_size += compressed_size;
+
+
+				gdPutBuf(uncompressed_row, compressed_size, out);
+				Putchar(BMP_RLE_COMMAND, out);
+				Putchar(BMP_RLE_ENDOFLINE, out);
+				bitmap_size += 2;
 			}
 		}
+
+		if (compression && uncompressed_row) {
+			gdFree(uncompressed_row);
+			/* Update filesize based on new values and set compression flag */
+			Putchar(BMP_RLE_COMMAND, out);
+			Putchar(BMP_RLE_ENDOFBITMAP, out);
+			bitmap_size += 2;
+
+			/* Write new total bitmap size */
+			gdSeek(out, 2);
+			gdBMPPutInt(out, total_size + bitmap_size);
+
+			/* Total length of image data */
+			gdSeek(out, 34);
+			gdBMPPutInt(out, bitmap_size);
+		}
+
 	} else {
         for (row = (im->sy - 1); row >= 0; row--) {
 			for (xpos = 0; xpos < im->sx; xpos++) {
 				pixel = gdImageGetPixel(im, xpos, row);
-				
+
 				Putchar(gdTrueColorGetBlue(pixel), out);
 				Putchar(gdTrueColorGetGreen(pixel), out);
 				Putchar(gdTrueColorGetRed(pixel), out);
 	}
 }
 
+static int compress_row(unsigned char *row, int length)
+{
+	int i = 0, rle_type = 0;
+	int compressed_length = 0;
+	int pixel = 0, compressed_run = 0, rle_compression = 0;
+	unsigned char *uncompressed_row = NULL, *uncompressed_rowp = NULL;
+	uncompressed_row = (unsigned char *) gdMalloc(length);
+	memcpy(uncompressed_row, row, length);
+	uncompressed_rowp = uncompressed_row;
+
+	for (pixel = 0; pixel < length; pixel++)
+	{
+		if (compressed_run == 0) {
+			uncompressed_row = uncompressed_rowp;
+			compressed_run++;
+			uncompressed_rowp++;
+			rle_type = BMP_RLE_TYPE_RAW;
+			continue;
+		}
+
+		if (compressed_run == 1) {
+			/* Compare next byte */
+			if (memcmp(uncompressed_rowp, uncompressed_rowp - 1, 1) == 0) {
+				rle_type = BMP_RLE_TYPE_RLE;
+			}
+		}
+
+		if (rle_type == BMP_RLE_TYPE_RLE) {
+			if (compressed_run >= 128 || memcmp(uncompressed_rowp, uncompressed_rowp - 1, 1) != 0) {
+				/* more than what we can store in a single run or run is over due to non match, force write */
+				rle_compression = build_rle_packet(row, rle_type, compressed_run, uncompressed_row);
+				row += rle_compression;
+				compressed_length += rle_compression;
+				compressed_run = 0;
+				pixel--;
+			} else {
+				compressed_run++;
+				uncompressed_rowp++;
+			}
+		} else {
+			if (compressed_run >= 128 || memcmp(uncompressed_rowp, uncompressed_rowp - 1, 1) == 0) {
+				/* more than what we can store in a single run or run is over due to match, force write */
+				rle_compression = build_rle_packet(row, rle_type, compressed_run, uncompressed_row);
+				row += rle_compression;
+				compressed_length += rle_compression;
+				compressed_run = 0;
+				pixel--;
+			} else {
+				/* add this pixel to the row */
+				compressed_run++;
+				uncompressed_rowp++;
+			}
+
+		}
+	}
+
+	if (compressed_run) {
+		if (rle_type == BMP_RLE_TYPE_RLE) {
+			compressed_length += build_rle_packet(row, rle_type, compressed_run, uncompressed_row);
+		} else {
+			compressed_length += build_rle_packet(row, rle_type, compressed_run, uncompressed_row);
+		}
+	}
+
+	return compressed_length;
+}
+
+static int build_rle_packet(unsigned char *row, int packet_type, int length, unsigned char *data)
+{
+	int compressed_size = 0;
+	if (length < 1 || length > 128) {
+		return 0;
+	}
+
+	/* Bitmap specific cases is that we can't have uncompressed rows of length 1 or 2 */
+	if (packet_type == BMP_RLE_TYPE_RAW && length < 3) {
+		int i = 0;
+		for (i = 0; i < length; i++) {
+			compressed_size += 2;
+			memset(row, 1, 1);
+			row++;
+
+			memcpy(row, data++, 1);
+			row++;
+		}
+	} else if (packet_type == BMP_RLE_TYPE_RLE) {
+		compressed_size = 2;
+		memset(row, length, 1);
+		row++;
+
+		memcpy(row, data, 1);
+		row++;
+	} else {
+		compressed_size = 2 + length;
+		memset(row, BMP_RLE_COMMAND, 1);
+		row++;
+
+		memset(row, length, 1);
+		row++;
+
+		memcpy(row, data, length);
+		row += length;
+
+		/* Must be an even number for an uncompressed run */
+		if (length % 2) {
+			memset(row, 0, 1);
+			row++;
+			compressed_size++;
+		}
+	}
+	return compressed_size;
+}
+
 BGD_DECLARE(gdImagePtr) gdImageCreateFromBmp(FILE * inFile)
 {
 	gdImagePtr im = 0;
 				if (xpos >= info->width) {
 					break;
 				}
-	
+
 				index = current_byte & 0x0f;
 				if (im->open[index]) {
 					im->open[index] = 0;