/* Copyright (C) 2001-2017 Peter Selinger. * This file is part of Potrace. It is free software and it is covered * by the GNU General Public License. See the file COPYING for details. */ /* Routines for manipulating greymaps, including reading pgm files. We * only deal with greymaps of depth 8 bits. */ #ifdef HAVE_CONFIG_H #include <config.h> #endif #include <errno.h> #include <math.h> #include <stddef.h> #include <stdlib.h> #include <string.h> #include "bitops.h" #include "greymap.h" #define INTBITS ( 8 * sizeof( int ) ) #define mod( a, n ) \ ( ( a ) >= ( n ) ? ( a ) % ( n ) : ( a ) >= 0 ? ( a ) : (n) - 1 - ( -1 - ( a ) ) % ( n ) ) static int gm_readbody_pnm( FILE* f, greymap_t** gmp, int magic ); static int gm_readbody_bmp( FILE* f, greymap_t** gmp ); #define TRY( x ) \ if( x ) \ goto try_error #define TRY_EOF( x ) \ if( x ) \ goto eof #define TRY_STD( x ) \ if( x ) \ goto std_error /* ---------------------------------------------------------------------- */ /* basic greymap routines */ /* calculate the size, in bytes, required for the data area of a * greymap of the given dy and h. Assume h >= 0. Return -1 if the size * does not fit into the ptrdiff_t type. */ static inline ptrdiff_t getsize( int dy, int h ) { ptrdiff_t size; if( dy < 0 ) { dy = -dy; } size = (ptrdiff_t) dy * (ptrdiff_t) h * (ptrdiff_t) sizeof( gm_sample_t ); /* check for overflow error */ if( size < 0 || ( h != 0 && dy != 0 && size / h / dy != sizeof( gm_sample_t ) ) ) { return -1; } return size; } /* return the size, in bytes, of the data area of the greymap. Return * -1 if the size does not fit into the ptrdiff_t type; however, this * cannot happen if the bitmap is well-formed, i.e., if created with * gm_new or gm_dup. */ static inline ptrdiff_t gm_size( const greymap_t* gm ) { return getsize( gm->dy, gm->h ); } /* return new greymap initialized to 0. NULL with errno on error. * Assumes w, h >= 0. */ greymap_t* gm_new( int w, int h ) { greymap_t* gm; int dy = w; ptrdiff_t size; size = getsize( dy, h ); if( size < 0 ) { errno = ENOMEM; return NULL; } if( size == 0 ) { size = 1; /* make surecmalloc() doesn't return NULL */ } gm = (greymap_t*) malloc( sizeof( greymap_t ) ); if( !gm ) { return NULL; } gm->w = w; gm->h = h; gm->dy = dy; gm->base = (gm_sample_t*) calloc( 1, size ); if( !gm->base ) { free( gm ); return NULL; } gm->map = gm->base; return gm; } /* free the given greymap */ void gm_free( greymap_t* gm ) { if( gm ) { free( gm->base ); } free( gm ); } /* duplicate the given greymap. Return NULL on error with errno set. */ greymap_t* gm_dup( greymap_t* gm ) { greymap_t* gm1 = gm_new( gm->w, gm->h ); int y; if( !gm1 ) { return NULL; } for( y = 0; y < gm->h; y++ ) { memcpy( gm_scanline( gm1, y ), gm_scanline( gm, y ), (size_t) gm1->dy * sizeof( gm_sample_t ) ); } return gm1; } /* clear the given greymap to color b. */ void gm_clear( greymap_t* gm, int b ) { ptrdiff_t size = gm_size( gm ); int x, y; if( b == 0 ) { memset( gm->base, 0, size ); } else { for( y = 0; y < gm->h; y++ ) { for( x = 0; x < gm->w; x++ ) { GM_UPUT( gm, x, y, b ); } } } } /* turn the given greymap upside down. This does not move the pixel * data or change the base address. */ static inline void gm_flip( greymap_t* gm ) { int dy = gm->dy; if( gm->h == 0 || gm->h == 1 ) { return; } gm->map = gm_scanline( gm, gm->h - 1 ); gm->dy = -dy; } /* resize the greymap to the given new height. The pixel data remains * bottom-aligned (truncated at the top) when dy >= 0 and top-aligned * (truncated at the bottom) when dy < 0. Return 0 on success, or 1 on * error with errno set. If the new height is <= the old one, no error * should occur. If the new height is larger, the additional pixel * data is *not* initialized. */ static inline int gm_resize( greymap_t* gm, int h ) { int dy = gm->dy; ptrdiff_t newsize; gm_sample_t* newbase; if( dy < 0 ) { gm_flip( gm ); } newsize = getsize( dy, h ); if( newsize < 0 ) { errno = ENOMEM; goto error; } if( newsize == 0 ) { newsize = 1; /* make sure realloc() doesn't return NULL */ } newbase = (gm_sample_t*) realloc( gm->base, newsize ); if( newbase == NULL ) { goto error; } gm->base = newbase; gm->map = newbase; gm->h = h; if( dy < 0 ) { gm_flip( gm ); } return 0; error: if( dy < 0 ) { gm_flip( gm ); } return 1; } /* ---------------------------------------------------------------------- */ /* routines for reading pnm streams */ /* read next character after whitespace and comments. Return EOF on * end of file or error. */ static int fgetc_ws( FILE* f ) { int c; while( 1 ) { c = fgetc( f ); if( c == '#' ) { while( 1 ) { c = fgetc( f ); if( c == '\n' || c == EOF ) { break; } } } /* space, tab, line feed, carriage return, form-feed */ if( c != ' ' && c != '\t' && c != '\r' && c != '\n' && c != 12 ) { return c; } } } /* skip whitespace and comments, then read a non-negative decimal * number from a stream. Return -1 on EOF. Tolerate other errors (skip * bad characters). Do not the read any characters following the * number (put next character back into the stream) */ static int readnum( FILE* f ) { int c; int acc; /* skip whitespace and comments */ while( 1 ) { c = fgetc_ws( f ); if( c == EOF ) { return -1; } if( c >= '0' && c <= '9' ) { break; } } /* first digit is already in c */ acc = c - '0'; while( 1 ) { c = fgetc( f ); if( c == EOF ) { break; } if( c < '0' || c > '9' ) { ungetc( c, f ); break; } acc *= 10; acc += c - '0'; } return acc; } /* similar to readnum, but read only a single 0 or 1, and do not read * any characters after it. */ static int readbit( FILE* f ) { int c; /* skip whitespace and comments */ while( 1 ) { c = fgetc_ws( f ); if( c == EOF ) { return -1; } if( c >= '0' && c <= '1' ) { break; } } return c - '0'; } /* ---------------------------------------------------------------------- */ /* read a PNM stream: P1-P6 format (see pnm(5)), or a BMP stream, and * convert the output to a greymap. Return greymap in *gmp. Return 0 * on success, -1 on error with errno set, -2 on bad file format (with * error message in gm_read_error), and 1 on premature end of file, -3 * on empty file (including files with only whitespace and comments), * -4 if wrong magic number. If the return value is >=0, *gmp is * valid. */ const char* gm_read_error = NULL; int gm_read( FILE* f, greymap_t** gmp ) { int magic[2]; /* read magic number. We ignore whitespace and comments before the * magic, for the benefit of concatenated files in P1-P3 format. * Multiple P1-P3 images in a single file are not formally allowed * by the PNM standard, but there is no harm in being lenient. */ magic[0] = fgetc_ws( f ); if( magic[0] == EOF ) { /* files which contain only comments and whitespace count as "empty" */ return -3; } magic[1] = fgetc( f ); if( magic[0] == 'P' && magic[1] >= '1' && magic[1] <= '6' ) { return gm_readbody_pnm( f, gmp, magic[1] ); } if( magic[0] == 'B' && magic[1] == 'M' ) { return gm_readbody_bmp( f, gmp ); } return -4; } /* ---------------------------------------------------------------------- */ /* read PNM format */ /* read PNM stream after magic number. Return values as for gm_read */ static int gm_readbody_pnm( FILE* f, greymap_t** gmp, int magic ) { greymap_t* gm; int x, y, i, j, b, b1, sum; int bpr; /* bytes per row (as opposed to 4*gm->c) */ int w, h, max; int realheight; /* in case of incomplete file, keeps track of how * many scan lines actually contain data */ gm = NULL; w = readnum( f ); if( w < 0 ) { goto format_error; } h = readnum( f ); if( h < 0 ) { goto format_error; } /* allocate greymap */ gm = gm_new( w, h ); if( !gm ) { goto std_error; } realheight = 0; switch( magic ) { default: /* not reached */ goto format_error; case '1': /* read P1 format: PBM ascii */ for( y = 0; y < h; y++ ) { realheight = y + 1; for( x = 0; x < w; x++ ) { b = readbit( f ); if( b < 0 ) { goto eof; } GM_UPUT( gm, x, y, b ? 0 : 255 ); } } break; case '2': /* read P2 format: PGM ascii */ max = readnum( f ); if( max < 1 ) { goto format_error; } for( y = 0; y < h; y++ ) { realheight = y + 1; for( x = 0; x < w; x++ ) { b = readnum( f ); if( b < 0 ) { goto eof; } GM_UPUT( gm, x, y, b * 255 / max ); } } break; case '3': /* read P3 format: PPM ascii */ max = readnum( f ); if( max < 1 ) { goto format_error; } for( y = 0; y < h; y++ ) { realheight = y + 1; for( x = 0; x < w; x++ ) { sum = 0; for( i = 0; i < 3; i++ ) { b = readnum( f ); if( b < 0 ) { goto eof; } sum += b; } GM_UPUT( gm, x, y, sum * ( 255 / 3 ) / max ); } } break; case '4': /* read P4 format: PBM raw */ b = fgetc( f ); /* read single white-space character after height */ if( b == EOF ) { goto format_error; } bpr = ( w + 7 ) / 8; for( y = 0; y < h; y++ ) { realheight = y + 1; for( i = 0; i < bpr; i++ ) { b = fgetc( f ); if( b == EOF ) { goto eof; } for( j = 0; j < 8; j++ ) { GM_PUT( gm, i * 8 + j, y, b & ( 0x80 >> j ) ? 0 : 255 ); } } } break; case '5': /* read P5 format: PGM raw */ max = readnum( f ); if( max < 1 ) { goto format_error; } b = fgetc( f ); /* read single white-space character after max */ if( b == EOF ) { goto format_error; } for( y = 0; y < h; y++ ) { realheight = y + 1; for( x = 0; x < w; x++ ) { b = fgetc( f ); if( b == EOF ) goto eof; if( max >= 256 ) { b <<= 8; b1 = fgetc( f ); if( b1 == EOF ) goto eof; b |= b1; } GM_UPUT( gm, x, y, b * 255 / max ); } } break; case '6': /* read P6 format: PPM raw */ max = readnum( f ); if( max < 1 ) { goto format_error; } b = fgetc( f ); /* read single white-space character after max */ if( b == EOF ) { goto format_error; } for( y = 0; y < h; y++ ) { realheight = y + 1; for( x = 0; x < w; x++ ) { sum = 0; for( i = 0; i < 3; i++ ) { b = fgetc( f ); if( b == EOF ) { goto eof; } if( max >= 256 ) { b <<= 8; b1 = fgetc( f ); if( b1 == EOF ) goto eof; b |= b1; } sum += b; } GM_UPUT( gm, x, y, sum * ( 255 / 3 ) / max ); } } break; } gm_flip( gm ); *gmp = gm; return 0; eof: TRY_STD( gm_resize( gm, realheight ) ); gm_flip( gm ); *gmp = gm; return 1; format_error: gm_free( gm ); if( magic == '1' || magic == '4' ) { gm_read_error = "invalid pbm file"; } else if( magic == '2' || magic == '5' ) { gm_read_error = "invalid pgm file"; } else { gm_read_error = "invalid ppm file"; } return -2; std_error: gm_free( gm ); return -1; } /* ---------------------------------------------------------------------- */ /* read BMP format */ struct bmp_info_s { unsigned int FileSize; unsigned int reserved; unsigned int DataOffset; unsigned int InfoSize; unsigned int w; /* width */ unsigned int h; /* height */ unsigned int Planes; unsigned int bits; /* bits per sample */ unsigned int comp; /* compression mode */ unsigned int ImageSize; unsigned int XpixelsPerM; unsigned int YpixelsPerM; unsigned int ncolors; /* number of colors in palette */ unsigned int ColorsImportant; unsigned int RedMask; unsigned int GreenMask; unsigned int BlueMask; unsigned int AlphaMask; unsigned int ctbits; /* sample size for color table */ int topdown; /* top-down mode? */ }; typedef struct bmp_info_s bmp_info_t; /* auxiliary */ static int bmp_count = 0; /* counter for byte padding */ static int bmp_pos = 0; /* counter from start of BMP data */ /* read n-byte little-endian integer. Return 1 on EOF or error, else * 0. Assume n<=4. */ static int bmp_readint( FILE* f, int n, unsigned int* p ) { int i; unsigned int sum = 0; int b; for( i = 0; i < n; i++ ) { b = fgetc( f ); if( b == EOF ) { return 1; } sum += (unsigned) b << ( 8 * i ); } bmp_count += n; bmp_pos += n; *p = sum; return 0; } /* reset padding boundary */ static void bmp_pad_reset( void ) { bmp_count = 0; } /* read padding bytes to 4-byte boundary. Return 1 on EOF or error, * else 0. */ static int bmp_pad( FILE* f ) { int c, i, b; c = ( -bmp_count ) & 3; for( i = 0; i < c; i++ ) { b = fgetc( f ); if( b == EOF ) { return 1; } } bmp_pos += c; bmp_count = 0; return 0; } /* forward to the new file position. Return 1 on EOF or error, else 0 */ static int bmp_forward( FILE* f, int pos ) { int b; while( bmp_pos < pos ) { b = fgetc( f ); if( b == EOF ) { return 1; } bmp_pos++; bmp_count++; } return 0; } /* safe colortable access */ #define COLTABLE( c ) ( ( c ) < bmpinfo.ncolors ? coltable[( c )] : 0 ) /* read BMP stream after magic number. Return values as for gm_read. * We choose to be as permissive as possible, since there are many * programs out there which produce BMP. For instance, ppmtobmp can * produce codings with anywhere from 1-8 or 24 bits per sample, * although most specifications only allow 1,4,8,24,32. We can also * read both the old and new OS/2 BMP formats in addition to the * Windows BMP format. */ static int gm_readbody_bmp( FILE* f, greymap_t** gmp ) { bmp_info_t bmpinfo; int* coltable; unsigned int b, c; unsigned int i, j; greymap_t* gm; unsigned int x, y; int col[2]; unsigned int bitbuf; unsigned int n; unsigned int redshift, greenshift, blueshift; int realheight; /* in case of incomplete file, keeps track of how * many scan lines actually contain data */ gm_read_error = NULL; gm = NULL; coltable = NULL; bmp_pos = 2; /* set file position */ /* file header (minus magic number) */ TRY( bmp_readint( f, 4, &bmpinfo.FileSize ) ); TRY( bmp_readint( f, 4, &bmpinfo.reserved ) ); TRY( bmp_readint( f, 4, &bmpinfo.DataOffset ) ); /* info header */ TRY( bmp_readint( f, 4, &bmpinfo.InfoSize ) ); if( bmpinfo.InfoSize == 40 || bmpinfo.InfoSize == 64 || bmpinfo.InfoSize == 108 || bmpinfo.InfoSize == 124 ) { /* Windows or new OS/2 format */ bmpinfo.ctbits = 32; /* sample size in color table */ TRY( bmp_readint( f, 4, &bmpinfo.w ) ); TRY( bmp_readint( f, 4, &bmpinfo.h ) ); TRY( bmp_readint( f, 2, &bmpinfo.Planes ) ); TRY( bmp_readint( f, 2, &bmpinfo.bits ) ); TRY( bmp_readint( f, 4, &bmpinfo.comp ) ); TRY( bmp_readint( f, 4, &bmpinfo.ImageSize ) ); TRY( bmp_readint( f, 4, &bmpinfo.XpixelsPerM ) ); TRY( bmp_readint( f, 4, &bmpinfo.YpixelsPerM ) ); TRY( bmp_readint( f, 4, &bmpinfo.ncolors ) ); TRY( bmp_readint( f, 4, &bmpinfo.ColorsImportant ) ); if( bmpinfo.InfoSize >= 108 ) { /* V4 and V5 bitmaps */ TRY( bmp_readint( f, 4, &bmpinfo.RedMask ) ); TRY( bmp_readint( f, 4, &bmpinfo.GreenMask ) ); TRY( bmp_readint( f, 4, &bmpinfo.BlueMask ) ); TRY( bmp_readint( f, 4, &bmpinfo.AlphaMask ) ); } if( bmpinfo.w > 0x7fffffff ) { goto format_error; } if( bmpinfo.h > 0x7fffffff ) { bmpinfo.h = ( -bmpinfo.h ) & 0xffffffff; bmpinfo.topdown = 1; } else { bmpinfo.topdown = 0; } if( bmpinfo.h > 0x7fffffff ) { goto format_error; } } else if( bmpinfo.InfoSize == 12 ) { /* old OS/2 format */ bmpinfo.ctbits = 24; /* sample size in color table */ TRY( bmp_readint( f, 2, &bmpinfo.w ) ); TRY( bmp_readint( f, 2, &bmpinfo.h ) ); TRY( bmp_readint( f, 2, &bmpinfo.Planes ) ); TRY( bmp_readint( f, 2, &bmpinfo.bits ) ); bmpinfo.comp = 0; bmpinfo.ncolors = 0; bmpinfo.topdown = 0; } else { goto format_error; } if( bmpinfo.comp == 3 && bmpinfo.InfoSize < 108 ) { /* bitfield feature is only understood with V4 and V5 format */ goto format_error; } if( bmpinfo.comp > 3 || bmpinfo.bits > 32 ) { goto format_error; } /* forward to color table (e.g., if bmpinfo.InfoSize == 64) */ TRY( bmp_forward( f, 14 + bmpinfo.InfoSize ) ); if( bmpinfo.Planes != 1 ) { gm_read_error = "cannot handle bmp planes"; goto format_error; /* can't handle planes */ } if( bmpinfo.ncolors == 0 && bmpinfo.bits <= 8 ) { bmpinfo.ncolors = 1 << bmpinfo.bits; } /* color table, present only if bmpinfo.bits <= 8. */ if( bmpinfo.bits <= 8 ) { coltable = (int*) calloc( bmpinfo.ncolors, sizeof( int ) ); if( !coltable ) { goto std_error; } /* NOTE: since we are reading a greymap, we can immediately convert * the color table entries to grey values. */ for( i = 0; i < bmpinfo.ncolors; i++ ) { TRY( bmp_readint( f, bmpinfo.ctbits / 8, &c ) ); c = ( ( c >> 16 ) & 0xff ) + ( ( c >> 8 ) & 0xff ) + ( c & 0xff ); coltable[i] = c / 3; } } /* forward to data */ if( bmpinfo.InfoSize != 12 ) { /* not old OS/2 format */ TRY( bmp_forward( f, bmpinfo.DataOffset ) ); } /* allocate greymap */ gm = gm_new( bmpinfo.w, bmpinfo.h ); if( !gm ) { goto std_error; } realheight = 0; switch( bmpinfo.bits + 0x100 * bmpinfo.comp ) { default: goto format_error; break; case 0x001: /* monochrome palette */ /* raster data */ for( y = 0; y < bmpinfo.h; y++ ) { realheight = y + 1; bmp_pad_reset(); for( i = 0; 8 * i < bmpinfo.w; i++ ) { TRY_EOF( bmp_readint( f, 1, &b ) ); for( j = 0; j < 8; j++ ) { GM_PUT( gm, i * 8 + j, y, b & ( 0x80 >> j ) ? COLTABLE( 1 ) : COLTABLE( 0 ) ); } } TRY( bmp_pad( f ) ); } break; case 0x002: /* 2-bit to 8-bit palettes */ case 0x003: case 0x004: case 0x005: case 0x006: case 0x007: case 0x008: for( y = 0; y < bmpinfo.h; y++ ) { realheight = y + 1; bmp_pad_reset(); bitbuf = 0; /* bit buffer: bits in buffer are high-aligned */ n = 0; /* number of bits currently in bitbuffer */ for( x = 0; x < bmpinfo.w; x++ ) { if( n < bmpinfo.bits ) { TRY_EOF( bmp_readint( f, 1, &b ) ); bitbuf |= b << ( INTBITS - 8 - n ); n += 8; } b = bitbuf >> ( INTBITS - bmpinfo.bits ); bitbuf <<= bmpinfo.bits; n -= bmpinfo.bits; GM_UPUT( gm, x, y, COLTABLE( b ) ); } TRY( bmp_pad( f ) ); } break; case 0x010: /* 16-bit encoding */ /* can't do this format because it is not well-documented and I * don't have any samples */ gm_read_error = "cannot handle bmp 16-bit coding"; goto format_error; break; case 0x018: /* 24-bit encoding */ case 0x020: /* 32-bit encoding */ for( y = 0; y < bmpinfo.h; y++ ) { realheight = y + 1; bmp_pad_reset(); for( x = 0; x < bmpinfo.w; x++ ) { TRY_EOF( bmp_readint( f, bmpinfo.bits / 8, &c ) ); c = ( ( c >> 16 ) & 0xff ) + ( ( c >> 8 ) & 0xff ) + ( c & 0xff ); GM_UPUT( gm, x, y, c / 3 ); } TRY( bmp_pad( f ) ); } break; case 0x320: /* 32-bit encoding with bitfields */ redshift = lobit( bmpinfo.RedMask ); greenshift = lobit( bmpinfo.GreenMask ); blueshift = lobit( bmpinfo.BlueMask ); for( y = 0; y < bmpinfo.h; y++ ) { realheight = y + 1; bmp_pad_reset(); for( x = 0; x < bmpinfo.w; x++ ) { TRY_EOF( bmp_readint( f, bmpinfo.bits / 8, &c ) ); c = ( ( c & bmpinfo.RedMask ) >> redshift ) + ( ( c & bmpinfo.GreenMask ) >> greenshift ) + ( ( c & bmpinfo.BlueMask ) >> blueshift ); GM_UPUT( gm, x, y, c / 3 ); } TRY( bmp_pad( f ) ); } break; case 0x204: /* 4-bit runlength compressed encoding (RLE4) */ x = 0; y = 0; while( 1 ) { TRY_EOF( bmp_readint( f, 1, &b ) ); /* opcode */ TRY_EOF( bmp_readint( f, 1, &c ) ); /* argument */ if( b > 0 ) { /* repeat count */ col[0] = COLTABLE( ( c >> 4 ) & 0xf ); col[1] = COLTABLE( c & 0xf ); for( i = 0; i < b && x < bmpinfo.w; i++ ) { if( x >= bmpinfo.w ) { x = 0; y++; } if( x >= bmpinfo.w || y >= bmpinfo.h ) { break; } realheight = y + 1; GM_PUT( gm, x, y, col[i & 1] ); x++; } } else if( c == 0 ) { /* end of line */ y++; x = 0; } else if( c == 1 ) { /* end of greymap */ break; } else if( c == 2 ) { /* "delta": skip pixels in x and y directions */ TRY_EOF( bmp_readint( f, 1, &b ) ); /* x offset */ TRY_EOF( bmp_readint( f, 1, &c ) ); /* y offset */ x += b; y += c; } else { /* verbatim segment */ for( i = 0; i < c; i++ ) { if( ( i & 1 ) == 0 ) { TRY_EOF( bmp_readint( f, 1, &b ) ); } if( x >= bmpinfo.w ) { x = 0; y++; } if( x >= bmpinfo.w || y >= bmpinfo.h ) { break; } realheight = y + 1; GM_PUT( gm, x, y, COLTABLE( ( b >> ( 4 - 4 * ( i & 1 ) ) ) & 0xf ) ); x++; } if( ( c + 1 ) & 2 ) { /* pad to 16-bit boundary */ TRY_EOF( bmp_readint( f, 1, &b ) ); } } } break; case 0x108: /* 8-bit runlength compressed encoding (RLE8) */ x = 0; y = 0; while( 1 ) { TRY_EOF( bmp_readint( f, 1, &b ) ); /* opcode */ TRY_EOF( bmp_readint( f, 1, &c ) ); /* argument */ if( b > 0 ) { /* repeat count */ for( i = 0; i < b; i++ ) { if( x >= bmpinfo.w ) { x = 0; y++; } if( x >= bmpinfo.w || y >= bmpinfo.h ) { break; } realheight = y + 1; GM_PUT( gm, x, y, COLTABLE( c ) ); x++; } } else if( c == 0 ) { /* end of line */ y++; x = 0; } else if( c == 1 ) { /* end of greymap */ break; } else if( c == 2 ) { /* "delta": skip pixels in x and y directions */ TRY_EOF( bmp_readint( f, 1, &b ) ); /* x offset */ TRY_EOF( bmp_readint( f, 1, &c ) ); /* y offset */ x += b; y += c; } else { /* verbatim segment */ for( i = 0; i < c; i++ ) { TRY_EOF( bmp_readint( f, 1, &b ) ); if( x >= bmpinfo.w ) { x = 0; y++; } if( x >= bmpinfo.w || y >= bmpinfo.h ) { break; } realheight = y + 1; GM_PUT( gm, x, y, COLTABLE( b ) ); x++; } if( c & 1 ) { /* pad input to 16-bit boundary */ TRY_EOF( bmp_readint( f, 1, &b ) ); } } } break; } /* switch */ /* skip any potential junk after the data section, but don't * complain in case EOF is encountered */ bmp_forward( f, bmpinfo.FileSize ); free( coltable ); if( bmpinfo.topdown ) { gm_flip( gm ); } *gmp = gm; return 0; eof: TRY_STD( gm_resize( gm, realheight ) ); free( coltable ); if( bmpinfo.topdown ) { gm_flip( gm ); } *gmp = gm; return 1; format_error: try_error: free( coltable ); gm_free( gm ); if( !gm_read_error ) { gm_read_error = "invalid bmp file"; } return -2; std_error: free( coltable ); gm_free( gm ); return -1; } /* ---------------------------------------------------------------------- */ /* write a pgm stream, either P2 or (if raw != 0) P5 format. Include * one-line comment if non-NULL. Mode determines how out-of-range * color values are converted. Gamma is the desired gamma correction, * if any (set to 2.2 if the image is to look optimal on a CRT monitor, * 2.8 for LCD). Set to 1.0 for no gamma correction */ int gm_writepgm( FILE* f, greymap_t* gm, const char* comment, int raw, int mode, double gamma ) { int x, y, v; int gammatable[256]; /* prepare gamma correction lookup table */ if( gamma != 1.0 ) { gammatable[0] = 0; for( v = 1; v < 256; v++ ) { gammatable[v] = (int) ( 255 * exp( log( v / 255.0 ) / gamma ) + 0.5 ); } } else { for( v = 0; v < 256; v++ ) { gammatable[v] = v; } } fprintf( f, raw ? "P5\n" : "P2\n" ); if( comment && *comment ) { fprintf( f, "# %s\n", comment ); } fprintf( f, "%d %d 255\n", gm->w, gm->h ); for( y = gm->h - 1; y >= 0; y-- ) { for( x = 0; x < gm->w; x++ ) { v = GM_UGET( gm, x, y ); if( mode == GM_MODE_NONZERO ) { if( v > 255 ) { v = 510 - v; } if( v < 0 ) { v = 0; } } else if( mode == GM_MODE_ODD ) { v = mod( v, 510 ); if( v > 255 ) { v = 510 - v; } } else if( mode == GM_MODE_POSITIVE ) { if( v < 0 ) { v = 0; } else if( v > 255 ) { v = 255; } } else if( mode == GM_MODE_NEGATIVE ) { v = 510 - v; if( v < 0 ) { v = 0; } else if( v > 255 ) { v = 255; } } v = gammatable[v]; if( raw ) { fputc( v, f ); } else { fprintf( f, x == gm->w - 1 ? "%d\n" : "%d ", v ); } } } return 0; } /* ---------------------------------------------------------------------- */ /* output - for primitive debugging purposes only! */ /* print greymap to screen */ int gm_print( FILE* f, greymap_t* gm ) { int x, y; int xx, yy; int d, t; int sw, sh; sw = gm->w < 79 ? gm->w : 79; sh = gm->w < 79 ? gm->h : gm->h * sw * 44 / ( 79 * gm->w ); for( yy = sh - 1; yy >= 0; yy-- ) { for( xx = 0; xx < sw; xx++ ) { d = 0; t = 0; for( x = xx * gm->w / sw; x < ( xx + 1 ) * gm->w / sw; x++ ) { for( y = yy * gm->h / sh; y < ( yy + 1 ) * gm->h / sh; y++ ) { d += GM_GET( gm, x, y ); t += 256; } } fputc( "*#=- "[5 * d / t], f ); /* what a cute trick :) */ } fputc( '\n', f ); } return 0; }