|
|
/* 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 bitmaps, including reading pbm files. */
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include "bitmap.h"
#include "bitmap_io.h"
#include "bitops.h"
#define INTBITS ( 8 * sizeof( int ) )
static int bm_readbody_bmp( FILE* f, double threshold, potrace_bitmap_t** bmp );static int bm_readbody_pnm( FILE* f, double threshold, potrace_bitmap_t** bmp, int magic );
#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
/* ---------------------------------------------------------------------- *//* 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 bitmap. Return bitmap in *bmp. Return 0 on * success, -1 on error with errno set, -2 on bad file format (with * error message in bm_read_error), and 1 on premature end of file, -3 * on empty file (including files which contain only whitespace and * comments), -4 if wrong magic number. If the return value is >=0, * bmp is valid. */
const char* bm_read_error = NULL;
int bm_read( FILE* f, double threshold, potrace_bitmap_t** bmp ){ 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 ) { return -3; }
magic[1] = fgetc( f );
if( magic[0] == 'P' && magic[1] >= '1' && magic[1] <= '6' ) { return bm_readbody_pnm( f, threshold, bmp, magic[1] ); }
if( magic[0] == 'B' && magic[1] == 'M' ) { return bm_readbody_bmp( f, threshold, bmp ); }
return -4;}
/* ---------------------------------------------------------------------- *//* read PNM format */
/* read PNM stream after magic number. Return values as for bm_read */static int bm_readbody_pnm( FILE* f, double threshold, potrace_bitmap_t** bmp, int magic ){ potrace_bitmap_t* bm; int x, y, i, b, b1, sum; int bpr; /* bytes per row (as opposed to 4*bm->c) */ int w, h, max; int realheight; /* in case of incomplete file, keeps track of how
* many scan lines actually contain data */
bm = NULL;
w = readnum( f );
if( w < 0 ) { goto format_error; }
h = readnum( f );
if( h < 0 ) { goto format_error; }
/* allocate bitmap */ bm = bm_new( w, h );
if( !bm ) { 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; }
BM_UPUT( bm, x, y, b ); } }
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; }
BM_UPUT( bm, x, y, b > threshold * max ? 0 : 1 ); } }
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; }
BM_UPUT( bm, x, y, sum > 3 * threshold * max ? 0 : 1 ); } }
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; }
*bm_index( bm, i * 8, y ) |= ( (potrace_word) b ) << ( 8 * ( BM_WORDSIZE - 1 - ( i % BM_WORDSIZE ) ) ); } }
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; }
BM_UPUT( bm, x, y, b > threshold * max ? 0 : 1 ); } }
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; }
BM_UPUT( bm, x, y, sum > 3 * threshold * max ? 0 : 1 ); } }
break; }
bm_flip( bm ); *bmp = bm; return 0;
eof: TRY_STD( bm_resize( bm, realheight ) ); bm_flip( bm ); *bmp = bm; return 1;
format_error: bm_free( bm );
if( magic == '1' || magic == '4' ) { bm_read_error = "invalid pbm file"; } else if( magic == '2' || magic == '5' ) { bm_read_error = "invalid pgm file"; } else { bm_read_error = "invalid ppm file"; }
return -2;
std_error: bm_free( bm ); 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 bm_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 bm_readbody_bmp( FILE* f, double threshold, potrace_bitmap_t** bmp ){ bmp_info_t bmpinfo; int* coltable; unsigned int b, c; unsigned int i; potrace_bitmap_t* bm; int mask; unsigned int x, y; int col[2]; unsigned int bitbuf; unsigned int n; unsigned int redshift, greenshift, blueshift; int col1[2]; int realheight; /* in case of incomplete file, keeps track of how
* many scan lines actually contain data */
bm_read_error = NULL; bm = 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 ) { bm_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 bitmap, we can immediately convert
* the color table entries to bits. */ 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 * threshold * 255 ? 0 : 1 );
if( i < 2 ) { col1[i] = c; } } }
/* forward to data */ if( bmpinfo.InfoSize != 12 ) { /* not old OS/2 format */ TRY( bmp_forward( f, bmpinfo.DataOffset ) ); }
/* allocate bitmap */ bm = bm_new( bmpinfo.w, bmpinfo.h );
if( !bm ) { goto std_error; }
realheight = 0;
switch( bmpinfo.bits + 0x100 * bmpinfo.comp ) { default: goto format_error; break;
case 0x001: /* monochrome palette */
if( col1[0] < col1[1] ) { /* make the darker color black */ mask = 0xff; } else { mask = 0; }
/* 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 ) ); b ^= mask; *bm_index( bm, i * 8, y ) |= ( (potrace_word) b ) << ( 8 * ( BM_WORDSIZE - 1 - ( i % BM_WORDSIZE ) ) ); }
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; BM_UPUT( bm, 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 */ bm_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 ); BM_UPUT( bm, x, y, c > 3 * threshold * 255 ? 0 : 1 ); }
TRY( bmp_pad( f ) ); }
break;
case 0x320: /* 32-bit encoding with bitfields */
if( bmpinfo.RedMask == 0 || bmpinfo.GreenMask == 0 || bmpinfo.BlueMask == 0 ) { goto format_error; }
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 ); BM_UPUT( bm, x, y, c > 3 * threshold * 255 ? 0 : 1 ); }
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; BM_PUT( bm, x, y, col[i & 1] ); x++; } } else if( c == 0 ) { /* end of line */ y++; x = 0; } else if( c == 1 ) { /* end of bitmap */ 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; BM_PUT( bm, 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; BM_PUT( bm, x, y, COLTABLE( c ) ); x++; } } else if( c == 0 ) { /* end of line */ y++; x = 0; } else if( c == 1 ) { /* end of bitmap */ 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; BM_PUT( bm, 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 ) { bm_flip( bm ); }
*bmp = bm; return 0;
eof: TRY_STD( bm_resize( bm, realheight ) ); free( coltable );
if( bmpinfo.topdown ) { bm_flip( bm ); }
*bmp = bm; return 1;
format_error:try_error: free( coltable ); bm_free( bm );
if( !bm_read_error ) { bm_read_error = "invalid bmp file"; }
return -2;
std_error: free( coltable ); bm_free( bm ); return -1;}
/* ---------------------------------------------------------------------- *//* output pbm format */
void bm_writepbm( FILE* f, potrace_bitmap_t* bm ){ int w, h, bpr, y, i, c;
w = bm->w; h = bm->h;
bpr = ( w + 7 ) / 8;
fprintf( f, "P4\n%d %d\n", w, h );
for( y = h - 1; y >= 0; y-- ) { for( i = 0; i < bpr; i++ ) { c = ( *bm_index( bm, i * 8, y ) >> ( 8 * ( BM_WORDSIZE - 1 - ( i % BM_WORDSIZE ) ) ) ) & 0xff; fputc( c, f ); } }}
/* ---------------------------------------------------------------------- *//* output - for primitive debugging purposes only! */
/* print bitmap to screen */int bm_print( FILE* f, potrace_bitmap_t* bm ){ int x, y; int xx, yy; int d; int sw, sh;
sw = bm->w < 79 ? bm->w : 79; sh = bm->w < 79 ? bm->h : bm->h * sw * 44 / ( 79 * bm->w );
for( yy = sh - 1; yy >= 0; yy-- ) { for( xx = 0; xx < sw; xx++ ) { d = 0;
for( x = xx * bm->w / sw; x < ( xx + 1 ) * bm->w / sw; x++ ) { for( y = yy * bm->h / sh; y < ( yy + 1 ) * bm->h / sh; y++ ) { if( BM_GET( bm, x, y ) ) { d++; } } }
fputc( d ? '*' : ' ', f ); }
fputc( '\n', f ); }
return 0;}
|