diff options
Diffstat (limited to 'editor/pbmpscale.c')
-rw-r--r-- | editor/pbmpscale.c | 554 |
1 files changed, 404 insertions, 150 deletions
diff --git a/editor/pbmpscale.c b/editor/pbmpscale.c index 2e24f3cd..acdb862c 100644 --- a/editor/pbmpscale.c +++ b/editor/pbmpscale.c @@ -3,22 +3,63 @@ */ #include <stdio.h> -#include "pbm.h" +#include "pm_c_util.h" #include "mallocvar.h" +#include "shhopt.h" +#include "pbm.h" +#include "bitarith.h" -/* prototypes */ -void nextrow_pscale ARGS((FILE *ifd, int row)); -int corner ARGS((int pat)); +#define LEFTBITS pm_byteLeftBits +#define RIGHTBITS pm_byteRightBits -/* input bitmap size and storage */ -int rows, columns, format ; -bit *inrow[3] ; +/* Table for translating bit pattern into "corners" flag element */ -#define thisrow (1) +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 }; -/* compass directions from west clockwise */ -int xd_pscale[] = { -1, -1, 0, 1, 1, 1, 0, -1 } ; -int yd_pscale[] = { 0, -1, -1, -1, 0, 1, 1, 1 } ; /* starting positions for corners */ #define NE(f) ((f) & 3) @@ -26,180 +67,393 @@ int yd_pscale[] = { 0, -1, -1, -1, 0, 1, 1, 1 } ; #define SW(f) (((f) >> 4) & 3) #define NW(f) (((f) >> 6) & 3) -typedef unsigned short sixteenbits ; -/* 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. - */ -sixteenbits patterns[] = { 0x0000, 0xd555, /* no corner */ - 0x0001, 0xffc1, 0xd514, /* normal corner */ - 0x0002, 0xd554, 0xd515, /* reduced corners */ - 0xbea2, 0xdfc0, 0xfd81, - 0xfd80, 0xdf80, - 0x0003, 0xbfa1, 0xfec2 /* reduced if > 1 */ - }; - -/* search for corner patterns, return type of corner found: - * 0 = no corner, - * 1 = normal corner, - * 2 = reduced corner, - * 3 = reduced if cutoff > 1 - */ +struct cmdlineInfo { + /* All the information the user supplied in the command line, + in a form easy for the program to use. + */ + unsigned int scale; + const char * inputFileName; /* File name of input file */ +}; + + + +static void +parseCommandLine(int argc, const char ** const argv, + struct cmdlineInfo * const cmdlineP) { +/*---------------------------------------------------------------------------- + Note that the file spec array we return is stored in the storage that + was passed to us as the argv array. +-----------------------------------------------------------------------------*/ + optEntry * option_def; + /* Instructions to OptParseOptions3 on how to parse our options. + */ + optStruct3 opt; + unsigned int option_def_index; + + MALLOCARRAY_NOFAIL(option_def, 100); + + option_def_index = 0; /* incremented by OPTENT3 */ + + opt.opt_table = option_def; + opt.short_allowed = FALSE; /* We have no short (old-fashioned) options */ + opt.allowNegNum = FALSE; /* We may have parms that are negative numbers */ + + optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0); + /* Uses and sets argc, argv, and some of *cmdlineP and others. */ + + if (argc-1 < 1) + pm_error("You must specify the scale factor as an argument"); + else { + int const scale = atoi(argv[1]); + if (scale < 1) + pm_error("Scale argument must be at least one. You specified %d", + scale); + else + cmdlineP->scale = scale; -int corner(pat) - int pat; -{ - register int i, r=0; - for (i = 0; i < sizeof(patterns)/sizeof(sixteenbits); i++) - if (patterns[i] < 0x100) - r = patterns[i]; - else if ((pat & (patterns[i] >> 8)) == - (patterns[i] & (patterns[i] >> 8))) - return r; - return 0; + if (argc-1 < 2) + cmdlineP->inputFileName = "-"; + else { + cmdlineP->inputFileName = argv[2]; + + if (argc-1 > 2) + pm_error("Too many arguments. The only arguments are the " + "scale factor and optional input file name. " + "You specified %u", argc-1); + } + } } -/* get a new row - */ -void nextrow_pscale(ifd, row) - FILE *ifd; - int row; -{ - bit *shuffle = inrow[0] ; - inrow[0] = inrow[1]; - inrow[1] = inrow[2]; - inrow[2] = shuffle ; - if (row < rows) { - if (shuffle == NULL) - inrow[2] = shuffle = pbm_allocrow(columns); - pbm_readpbmrow(ifd, inrow[2], columns, format) ; - } else inrow[2] = NULL; /* discard storage */ +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); } -int -main(int argc, char ** argv) { +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; - FILE * ifP; - bit * outrow; - unsigned int row; - int scale, cutoff, ucutoff ; - unsigned char *flags; + unsigned char const origHead = dest[0]; + unsigned char const origEnd = dest[last]; - pbm_init( &argc, argv ); + unsigned int i; - if (argc < 2) - pm_usage("scale [pbmfile]"); + for (i = 0; i < colBytes; ++i) + dest[i] = color * 0xff; - scale = atoi(argv[1]); - if (scale < 1) - pm_error("Scale argument must be at least one. You specified '%s'", - argv[1]); + if (rs > 0) + dest[0] = LEFTBITS(origHead, rs) | RIGHTBITS(dest[0], 8-rs); - if (argc == 3) - ifP = pm_openr(argv[2]); - else - ifP = stdin ; + if (trs > 0) + dest[last] = LEFTBITS(dest[last], trs) | RIGHTBITS(origEnd, 8-trs); +} - inrow[0] = inrow[1] = inrow[2] = NULL; - pbm_readpbminit(ifP, &columns, &rows, &format) ; - outrow = pbm_allocrow(columns*scale) ; - MALLOCARRAY(flags, columns); - if (flags == NULL) - pm_error("out of memory") ; - pbm_writepbminit(stdout, columns*scale, rows*scale, 0) ; +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. - cutoff = scale / 2; - ucutoff = scale - 1 - cutoff; - nextrow_pscale(ifP, 0); - for (row = 0; row < rows; ++row) { - unsigned int col; - unsigned int i; - nextrow_pscale(ifP, row+1); - for (col = 0; col < columns; ++col) { - unsigned int i; - flags[col] = 0 ; - for (i = 0; i != 8; i += 2) { - int vec = inrow[thisrow][col] != PBM_WHITE; - unsigned int k; - for (k = 0; k < 7; ++k) { - int x = col + xd_pscale[(k+i)&7] ; - int y = thisrow + yd_pscale[(k+i)&7] ; - vec <<= 1; - if (x >=0 && x < columns && inrow[y]) - vec |= (inrow[y][x] != PBM_WHITE) ; - } - flags[col] |= corner(vec)<<i ; - } + 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. +-----------------------------------------------------------------------------*/ + +#if 0 + /* 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") + */ +#endif + + uint32_t prevrow24, thisrow24, nextrow24; + unsigned int col; + + /* higher bits are set to 0 */ + prevrow24 = prevrow[0]; /* initial value */ + thisrow24 = thisrow[0]; /* initial value */ + nextrow24 = nextrow[0]; /* initial value */ + + for (col = 0; col < cols; ++col) { + unsigned int const col8 = col / 8; + unsigned int const offset = col % 8; + + unsigned int sample; + + if (offset == 0) { + prevrow24 = prevrow24 << 8 | prevrow[col8 + 1]; + thisrow24 = thisrow24 << 8 | thisrow[col8 + 1]; + nextrow24 = nextrow24 << 8 | nextrow[col8 + 1]; } - for (i = 0; i < scale; i++) { - bit *ptr = outrow ; - int zone = (i > ucutoff) - (i < cutoff) ; - int cut = (zone < 0) ? (cutoff - i) : - (zone > 0) ? (i - ucutoff) : 0 ; - - for (col = 0; col < columns; ++col) { - int pix = inrow[thisrow][col] ; - int flag = flags[col] ; - int cutl, cutr; - unsigned int k; + 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 const outcols = cols * scale; + + unsigned int i; + unsigned int col; + + for (i = 0; i < scale; ++i) { + int const zone = (i > ucutoff) - (i < cutoff); + int const cut1 = + (zone < 0) ? (cutoff - i) : (zone > 0) ? (i - ucutoff) : 0; + + unsigned int outcol; + int cut[4]; + + outcol = 0; /* initial value */ + + 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: - switch (NW(flag)) { - case 0: cutl = 0; break; - case 1: cutl = cut; break; - case 2: cutl = cut ? cut-1 : 0; break; - case 3: cutl = (cut && cutoff > 1) ? cut-1 : cut; break; - default: cutl = 0; /* Should never reach here */ - } - switch (NE(flag)) { - case 0: cutr = 0; break; - case 1: cutr = cut; break; - case 2: cutr = cut ? cut-1 : 0; break; - case 3: cutr = (cut && cutoff > 1) ? cut-1 : cut; break; - default: cutr = 0; /* Should never reach here */ + 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: - switch (SW(flag)) { - case 0: cutl = 0; break; - case 1: cutl = cut; break; - case 2: cutl = cut ? cut-1 : 0; break; - case 3: cutl = (cut && cutoff > 1) ? cut-1 : cut; break; - default: cutl = 0; /* should never reach here */ - } - switch (SE(flag)) { - case 0: cutr = 0; break; - case 1: cutr = cut; break; - case 2: cutr = cut ? cut-1 : 0; break; - case 3: cutr = (cut && cutoff > 1) ? cut-1 : cut; break; - default: cutr = 0; /* should never reach here */ - } + cutl = cut[SW(flag)]; + cutr = cut[SE(flag)]; break; - default: cutl = 0; cutr = 0; /* Should never reach here */ } - for (k = 0; k < cutl; ++k) /* left part */ - *ptr++ = !pix ; - for (k = 0; k < scale-cutl-cutr; ++k) /* center part */ - *ptr++ = pix ; - for (k = 0; k < cutr; ++k) /* right part */ - *ptr++ = !pix ; + + 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(stdout, outrow, scale*columns, 0) ; } + pbm_writepbmrow_packed(stdout, outrow, outcols, 0) ; + } +} + + + +int +main(int argc, const char ** argv) { + + struct cmdlineInfo cmdline; + FILE * ifP; + bit ** buffer; + bit * prevrow; + bit * thisrow; + bit * nextrow; + bit * edgerow; + bit * outrow; + unsigned int row; + unsigned int i; + int cols, rows; + int format; + unsigned int outcols; + unsigned int outrows; + int cutoff; + int ucutoff ; + unsigned char * flags; /* malloc'ed */ + + pm_proginit(&argc, argv); + + parseCommandLine(argc, argv, &cmdline); + + ifP = pm_openr(cmdline.inputFileName); + + pbm_readpbminit(ifP, &cols, &rows, &format) ; + + validateComputableDimensions(cols, rows, cmdline.scale); + + outcols = cols * cmdline.scale; + outrows = rows * cmdline.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; + + /* Add blank bytes at right edges */ + for (i = 0; i < 3; ++i) + 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(flags, cols); + if (flags == NULL) + pm_error("Couldn't get memory for %u columns of flags", cols); + + pbm_writepbminit(stdout, outcols, outrows, 0) ; + + cutoff = cmdline.scale / 2; + ucutoff = cmdline.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, cmdline.scale, + cutoff, ucutoff); } + pbm_freearray(buffer,3); + pbm_freerow(edgerow); + pbm_freerow(outrow); + free (flags); pm_close(ifP); return 0; } |