/* pbmpscale.c - pixel scaling with jagged edge smoothing. * AJCD 13/8/90 */ #include #include "pbm.h" #include "mallocvar.h" #include "bitarith.h" #define LEFTBITS pm_byteLeftBits #define RIGHTBITS pm_byteRightBits /* Table for translating bit pattern into "corners" flag element */ unsigned char const transTable[512] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x00, 0x00, 0x04, 0x04, 0xaa, 0xa2, 0x82, 0x82, 0x8a, 0x82, 0x82, 0x82, 0xa0, 0xa0, 0x40, 0x40, 0xc0, 0xc0, 0xc0, 0xc0, 0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x28, 0x28, 0x00, 0x00, 0x28, 0x28, 0x0a, 0x03, 0x01, 0x03, 0x0a, 0x03, 0x01, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x00, 0x00, 0x04, 0x04, 0xa8, 0xa0, 0xc0, 0xc0, 0x88, 0x80, 0x80, 0x80, 0xa0, 0xa0, 0xc0, 0xc0, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x28, 0x28, 0x00, 0x00, 0x28, 0x28, 0x0c, 0xff, 0xff, 0xff, 0x08, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0x01, 0x0a, 0x0a, 0x01, 0x01, 0x0a, 0x0a, 0x28, 0x30, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x10, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0xa0, 0xa0, 0x40, 0x40, 0xa0, 0xa0, 0x82, 0x82, 0xff, 0xff, 0x82, 0x82, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0x01, 0x0a, 0x0a, 0x01, 0x01, 0x0a, 0x0a, 0x28, 0x30, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x10, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0xa0, 0xa0, 0x40, 0x40, 0xa0, 0xa0, 0x82, 0x82, 0xff, 0xff, 0x82, 0x82, 0xff, 0xff, 0x0c, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x00, 0x00, 0x04, 0x04, 0x2a, 0x22, 0x03, 0x02, 0x0a, 0x02, 0x03, 0x02, 0x30, 0x20, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x28, 0x28, 0x00, 0x00, 0x28, 0x28, 0x0a, 0x02, 0x03, 0x02, 0x0a, 0x02, 0x03, 0x02, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x00, 0x00, 0x04, 0x04, 0x28, 0x20, 0xff, 0xff, 0x08, 0xff, 0xff, 0xff, 0x30, 0x20, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x28, 0x28, 0x00, 0x00, 0x28, 0x28, 0x0c, 0xff, 0xff, 0xff, 0x08, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0x01, 0x0a, 0x0a, 0x01, 0x01, 0x0a, 0x0a, 0x28, 0x20, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x30, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0xa0, 0xa0, 0x40, 0x40, 0xa0, 0xa0, 0x82, 0x82, 0xff, 0xff, 0x82, 0x82, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0x01, 0x0a, 0x0a, 0x01, 0x01, 0x0a, 0x0a, 0x28, 0x20, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x30, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0xa0, 0xa0, 0x40, 0x40, 0xa0, 0xa0, 0x82, 0x82, 0xff, 0xff, 0x82, 0x82, 0xff, 0xff, 0x0c, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; /* starting positions for corners */ #define NE(f) ((f) & 3) #define SE(f) (((f) >> 2) & 3) #define SW(f) (((f) >> 4) & 3) #define NW(f) (((f) >> 6) & 3) #define MAX(x,y) ((x) > (y) ? (x) : (y)) static void validateComputableDimensions(unsigned int const width, unsigned int const height, unsigned int const scaleFactor) { /*---------------------------------------------------------------------------- Make sure that multiplication for output image width and height do not overflow. See validateComputetableSize() in libpam.c and pbm_readpbminitrest() in libpbm2.c -----------------------------------------------------------------------------*/ unsigned int const maxWidthHeight = INT_MAX - 2; unsigned int const maxScaleFactor = maxWidthHeight / MAX(height, width); if (scaleFactor > maxScaleFactor) pm_error("Scale factor '%u' too large. " "The maximum for this %u x %u input image is %u.", scaleFactor, width, height, maxScaleFactor); } static void writeBitSpan(unsigned char * const packedBitrow, int const cols, int const offset, int const color) { /*---------------------------------------------------------------------------- Write white (color="0") or black (="1") bits into packedBitrow[], starting at 'offset', length 'cols'. -----------------------------------------------------------------------------*/ unsigned char * const dest = & packedBitrow[offset/8]; unsigned int const rs = offset % 8; unsigned int const trs = (cols + rs) % 8; unsigned int const colBytes = pbm_packed_bytes(cols + rs); unsigned int const last = colBytes - 1; unsigned char const origHead = dest[0]; unsigned char const origEnd = dest[last]; unsigned int i; for( i = 0; i < colBytes; ++i) dest[i] = color * 0xff; if (rs > 0) dest[0] = LEFTBITS(origHead, rs) | RIGHTBITS(dest[0], 8-rs); if (trs > 0) dest[last] = LEFTBITS(dest[last], trs) | RIGHTBITS(origEnd, 8-trs); } static void setFlags( const bit * const prevrow, const bit * const thisrow, const bit * const nextrow, unsigned char * const flags, unsigned int const cols ) { /*---------------------------------------------------------------------------- Scan one row, examining the row above and row below, and determine whether there are "corners" for each pixel. Feed a 9 bit sample into pre-calculated array transTable[512] to calculate all four corner statuses at once. Bits in the 9 bit sample represent the current pixel and neighbors: NW : N : NE : W: Current : E : SW : S : SE Bits in flag are divided into 4 fields of width 2 each: NW : SW : SE : NE Code 0xff is an exception. It is a variation of 0x00. 0x00 : no corners, no color change from above row (Current == N) 0xff : no corners, but color changed (Current != N) Most transTable[] entries are "no corners". 0x00 appears 180 times, 0xff 109 times. The following code is from the previous version, which examined the corners one by one: // list of corner patterns; bit 7 is current color, bits 0-6 are squares // around (excluding square behind), going clockwise. // The high byte of the patterns is a mask, which determines which bits are // not ignored. uint16_t const patterns[] = { 0x0000, 0xd555, // no corner 0x0001, 0xffc1, 0xd514, // normal corner 0x0002, 0xd554, 0xd515, 0xbea2, 0xdfc0, 0xfd81, 0xfd80, 0xdf80, // reduced corners 0x0003, 0xbfa1, 0xfec2 }; // reduced if cutoff > 1 For example, the NE corner is examined with the following 8 bit sample: Current : W : NW : N : NE : E : SE : S (SW is the "square behind") -----------------------------------------------------------------------------*/ uint32_t prevrow24 = prevrow[0]; /* higher bits are set to 0 */ uint32_t thisrow24 = thisrow[0]; uint32_t nextrow24 = nextrow[0]; unsigned int col; for (col = 0; col < cols; ++col) { unsigned int sample; unsigned int const col8=col/8; unsigned int const offset=col%8; if(offset ==0){ prevrow24 = prevrow24 << 8 | prevrow[ col8 +1 ]; thisrow24 = thisrow24 << 8 | thisrow[ col8 +1 ]; nextrow24 = nextrow24 << 8 | nextrow[ col8 +1 ]; } sample= ( ( prevrow24 >> ( 8 -offset) ) & 0x01c0 ) | ( ( thisrow24 >> (11 -offset) ) & 0x0038 ) | ( ( nextrow24 >> (14 -offset) ) & 0x0007 ); flags[col] = transTable[sample]; } } static void expandRow(const bit * const thisrow, const bit * const prevrow, bit * const outrow, unsigned char * const flags, unsigned int const cols, int const scale, int const cutoff, int const ucutoff) { /*---------------------------------------------------------------------------- Process one row, using flags array as reference. If pixel has no corners output a NxN square of the given color, otherwise output with the specified corner area(s) clipped off. -----------------------------------------------------------------------------*/ unsigned int i, col; unsigned int const outcols = cols * scale; for (i = 0; i < scale; i++) { unsigned int outcol=0; int const zone = (i > ucutoff) - (i < cutoff) ; int const cut1 = (zone < 0) ? (cutoff - i) : (zone > 0) ? (i - ucutoff) : 0 ; int cut[4]; cut[0] = 0; cut[1] = cut1; cut[2] = cut1 ? cut1-1 : 0; cut[3] = (cut1 && cutoff > 1) ? cut1-1 : cut1 ; for (col = 0; col < cols; ++col) { unsigned int const col8=col/8; unsigned int const offset=col%8; int const pix = ( thisrow[col8] >> (7-offset) ) &0x01 ; int const flag = flags[col] ; int cutl, cutr; if( flag == 0x00 ) { /* There are no corners, no color change */ outcol += scale; } else { switch (zone) { case -1: if( i==0 && flag == 0xff ) { /* No corners, color changed */ cutl = cutr = 0; flags[col]=0x00; /* Use above skip procedure next cycle */ } else { cutl = cut[ NW(flag) ]; cutr = cut[ NE(flag) ]; } break; case 0: cutl = cutr = 0; break ; case 1: cutl = cut[ SW(flag) ]; cutr = cut[ SE(flag) ]; break; } if (cutl>0){ writeBitSpan(outrow, cutl, outcol, !pix); outcol +=cutl; } { unsigned int const center = scale-cutl-cutr; if (center > 0){ writeBitSpan(outrow, center, outcol, pix); outcol +=center; } } if (cutr>0){ writeBitSpan(outrow, cutr, outcol, !pix); outcol +=cutr; } } } pbm_writepbmrow_packed(stdout, outrow, outcols, 0) ; } } int main(int argc, char ** argv) { FILE * ifP; bit ** buffer; bit * prevrow; bit * thisrow; bit * nextrow; bit * edgerow; bit * outrow; unsigned int row, i; int scale, cols, rows, format, outcols, outrows, cutoff, ucutoff ; unsigned char *flags; pbm_init( &argc, argv ); if (argc < 2) pm_usage("scale [pbmfile]"); scale = atoi(argv[1]); if (scale < 1) pm_error("Scale argument must be at least one. You specified '%s'", argv[1]); if (argc == 3) ifP = pm_openr(argv[2]); else ifP = stdin ; pbm_readpbminit(ifP, &cols, &rows, &format) ; validateComputableDimensions(cols, rows, scale); outcols= cols * scale; outrows= rows * scale; /* Initialize input buffers. We add a margin of 8 bits on the right of the three rows. On the top and bottom of the image we place an imaginary blank row ("edgerow") to facilitate the process. */ buffer = pbm_allocarray_packed(cols+8, 3); edgerow = pbm_allocrow_packed(cols+8); for(i=0; i < pbm_packed_bytes(cols+8); ++i) edgerow[i] = 0x00; for(i=0; i < 3; ++i) /* Add blank bytes at right edges */ buffer[i][ pbm_packed_bytes( cols +8 ) -1 ] = 0x00; thisrow = edgerow; nextrow = buffer[0]; /* Read the top line into nextrow and clean the right end. */ pbm_readpbmrow_packed(ifP, nextrow, cols, format); if (cols%8>0){ nextrow[pbm_packed_bytes(cols) -1 ] >>= (8 - cols%8); nextrow[pbm_packed_bytes(cols) -1 ] <<= (8 - cols%8); } outrow = pbm_allocrow_packed(outcols); for(i=0; i < pbm_packed_bytes(outcols); ++i) outrow[i] = 0x00; MALLOCARRAY_NOFAIL(flags, cols); pbm_writepbminit(stdout, outcols, outrows, 0) ; cutoff = scale / 2; ucutoff = scale - 1 - cutoff; for (row = 0; row < rows; ++row) { prevrow = thisrow; /* Slide up the input row window */ thisrow = nextrow; if( row < rows -1){ nextrow = buffer[(row+1)%3]; /* We take the address directly instead of shuffling the rows. This provision is for proper handling of the initial edgerow. */ pbm_readpbmrow_packed(ifP, nextrow, cols, format); if (cols%8>0){ nextrow[pbm_packed_bytes(cols) -1 ] >>= (8 - cols%8); nextrow[pbm_packed_bytes(cols) -1 ] <<= (8 - cols%8); } } else /* Bottom of image. */ nextrow = edgerow; setFlags( prevrow, thisrow, nextrow, flags, cols); expandRow(thisrow, prevrow, outrow, flags, cols, scale, cutoff, ucutoff); } pbm_freearray(buffer,3); pbm_freerow(edgerow); pbm_freerow(outrow); free (flags); pm_close(ifP); return 0; }