/* ppmtobmp.c - Don Yang (uguu.org) 01/03/10 */ /*@ +boolint -compdef -mustfreefresh @*/ #include #include #include #include #include #include #ifdef __MINGW32__ #include #include #endif #define BLOCK_SIZE (1024 * 1024) #define BMP_HEADER 54 #define READ_PADDING 16 typedef unsigned char Byte; /* Singly linked list of read buffers */ typedef struct read_buffer { Byte data[BLOCK_SIZE]; size_t size; struct read_buffer *next; } ReadBuffer; /* Load file to memory */ static Byte *ReadFileToBuffer(FILE *infile) { /*@only@*/ReadBuffer *head, *tail; Byte *data; size_t total_size = 0, i; /* Allocate header block */ head = (ReadBuffer*)malloc(sizeof(ReadBuffer)); assert(head != NULL); head->size = 0; head->next = NULL; tail = head; /* Read blocks until end of file */ for(;;) { /* Read current block */ tail->size = fread(tail->data, 1, BLOCK_SIZE, infile); if( (int)(tail->size) < BLOCK_SIZE ) { if( (int)(tail->size) < 0 ) tail->size = 0; total_size += tail->size; break; } total_size += BLOCK_SIZE; /* Allocate next block */ tail->next = (ReadBuffer*)malloc(sizeof(ReadBuffer)); assert(tail->next != NULL); tail = tail->next; tail->size = 0; tail->next = NULL; } /* Combine all buffers */ data = (Byte*)malloc(total_size + READ_PADDING); assert(data != NULL); for(i = 0; head != NULL; head = tail) { memcpy(data + i, head->data, head->size); i += head->size; tail = head->next; free(head); } return data; } /* Check format parameters */ static int CheckParams(int width, int height, int levels) { return width > 0 && width < 10000 && height > 0 && height < 10000 && levels == 255; } /* Check header block */ static void CheckInputFormat(/*@observer@*/Byte *data, /*@out@*/int *width, /*@out@*/int *height, /*@out@*/int *format, /*@out@*/int *header_size) { char header[32]; int i, levels; /*@observer@*/Byte *p; *width = *height = *format = *header_size = 0; /* Find end of header */ p = data; for(i = 0; i < 3; i++) { p = (Byte*)memchr(p, (int)'\n', BLOCK_SIZE - (size_t)(p - data)); if( p == NULL ) return; p++; } *header_size = (int)(p - data); /* Parse header */ if( (size_t)*header_size >= sizeof(header) ) return; memset(header, 0, sizeof(header)); memcpy(header, data, (size_t)*header_size); if( sscanf(header, "P3 %d %d %d", width, height, &levels) == 3 && CheckParams(*width, *height, levels) != 0 ) { *format = 3; return; } if( sscanf(header, "P6 %d %d %d", width, height, &levels) == 3 && CheckParams(*width, *height, levels) != 0 ) { *format = 6; return; } /* Unsupported format */ assert(format == 0); } /* Compute word aligned width */ static int AlignedWidth(int width) { int w = width * 3; return (w + 3) & ~3; } /* Write a single number in little endian */ static void WriteNumber(int number, Byte *output) { output[0] = (Byte)((unsigned int)(number) & 0xff); output[1] = (Byte)((unsigned int)(number & 0xff00) >> 8); output[2] = (Byte)((unsigned int)(number & 0xff0000) >> 16); output[3] = (Byte)((unsigned int)(number & 0xff000000) >> 24); } /* Create output bitmap */ static Byte *CreateOutput(int width, int height, /*@out@*/int *output_size) { const int aligned_width = AlignedWidth(width); Byte *data; /* Allocate image */ *output_size = BMP_HEADER + aligned_width * height; data = (Byte*)calloc(1, (size_t)*output_size); assert(data != NULL); /* Populate header */ data[0] = (Byte)'B'; data[1] = (Byte)'M'; WriteNumber(*output_size, data + 2); WriteNumber(BMP_HEADER, data + 10); WriteNumber(40, data + 14); WriteNumber(width, data + 18); WriteNumber(height, data + 22); data[26] = (Byte)1; data[28] = (Byte)24; WriteNumber(aligned_width * height, data +34); return data; } /* Read a single number from input. Note that we will read into unallocated memory if input is bad. */ static int ReadNumber(Byte *input, int *offset) { int n, i, j; /* Find beginning of number */ for(i = 0; i < READ_PADDING - 4; i++) { if( isdigit(input[i + *offset]) ) { /* Find end of number */ n = 0; for(j = 0; j < 4; j++) { if( isdigit(input[i + j + *offset]) ) { n = n * 10 + (int)input[i + j + *offset] - (int)'0'; } else { *offset += i + j; return n; } } } } return 0; } /* Copy ASCII format pixels */ static void CopyP3Pixels(int width, int height, int header_size, Byte *input, Byte *output) { const int aligned_width = AlignedWidth(width); Byte *output_scanline; int x, y, offset; offset = header_size; for(y = 0; y < height; y++) { output_scanline = output + (height - y - 1) * aligned_width + BMP_HEADER; for(x = 0; x < width; x++) { output_scanline[x * 3 + 2] = (Byte)ReadNumber(input, &offset); output_scanline[x * 3 + 1] = (Byte)ReadNumber(input, &offset); output_scanline[x * 3] = (Byte)ReadNumber(input, &offset); } } } /* Copy binary format pixels */ static void CopyP6Pixels(int width, int height, int header_size, Byte *input, Byte *output) { const int aligned_width = AlignedWidth(width); Byte *input_pixel, *output_scanline; int x, y; input_pixel = input + header_size; for(y = 0; y < height; y++) { output_scanline = output + (height - y - 1) * aligned_width + BMP_HEADER; for(x = 0; x < width; x++) { output_scanline[x * 3 + 2] = input_pixel[0]; output_scanline[x * 3 + 1] = input_pixel[1]; output_scanline[x * 3] = input_pixel[2]; input_pixel += 3; } } } /* Program entry */ int main(/*@unused@*/int argc, /*@unused@*/char **argv) { Byte *input, *output; int width, height, format, header_size, output_size; if( isatty(fileno(stdin)) != 0 || isatty(fileno(stdout)) != 0 ) { (void)fputs("ppmtobmp < input.ppm > output.bmp\n", stderr); return 1; } #ifdef __MINGW32__ (void)setmode(fileno(stdin), O_BINARY); (void)setmode(fileno(stdout), O_BINARY); #endif input = ReadFileToBuffer(stdin); CheckInputFormat(input, &width, &height, &format, &header_size); if( format == 3 ) { output = CreateOutput(width, height, &output_size); CopyP3Pixels(width, height, header_size, input, output); } else if( format == 6 ) { output = CreateOutput(width, height, &output_size); CopyP6Pixels(width, height, header_size, input, output); } else { free(input); (void)fputs("Unsupported format\n", stderr); return 1; } free(input); (void)fwrite(output, (size_t)output_size, 1, stdout); free(output); return 0; }