/*====================================================================* - Copyright (C) 2001 Leptonica. All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - 1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials - provided with the distribution. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *====================================================================*/ /*! * \file bmpio.c *
 *
 *      Read bmp
 *           PIX          *pixReadStreamBmp()
 *           PIX          *pixReadMemBmp()
 *
 *      Write bmp
 *           l_int32       pixWriteStreamBmp()
 *           l_int32       pixWriteMemBmp()
 *
 * 
*/ #ifdef HAVE_CONFIG_H #include #endif /* HAVE_CONFIG_H */ #include #include "allheaders.h" #include "bmp.h" /* --------------------------------------------*/ #if USE_BMPIO /* defined in environ.h */ /* --------------------------------------------*/ /* Here we're setting the pixel value 0 to white (255) and the * value 1 to black (0). This is the convention for grayscale, but * the opposite of the convention for 1 bpp, where 0 is white * and 1 is black. Both colormap entries are opaque (alpha = 255) */ RGBA_QUAD bwmap[2] = { {255,255,255,255}, {0,0,0,255} }; /* Image dimension limits */ static const l_int32 L_MAX_ALLOWED_WIDTH = 1000000; static const l_int32 L_MAX_ALLOWED_HEIGHT = 1000000; static const l_int64 L_MAX_ALLOWED_PIXELS = 400000000LL; static const l_int32 L_MAX_ALLOWED_RES = 10000000; /* pixels/meter */ #ifndef NO_CONSOLE_IO #define DEBUG 0 #endif /* ~NO_CONSOLE_IO */ /*--------------------------------------------------------------* * Read bmp * *--------------------------------------------------------------*/ /*! * \brief pixReadStreamBmp() * * \param[in] fp file stream opened for read * \return pix, or NULL on error * *
 * Notes:
 *      (1) Here are references on the bmp file format:
 *          http://en.wikipedia.org/wiki/BMP_file_format
 *          http://www.fortunecity.com/skyscraper/windows/364/bmpffrmt.html
 * 
*/ PIX * pixReadStreamBmp(FILE *fp) { l_uint8 *data; size_t size; PIX *pix; PROCNAME("pixReadStreamBmp"); if (!fp) return (PIX *)ERROR_PTR("fp not defined", procName, NULL); /* Read data from file and decode into Y,U,V arrays */ rewind(fp); if ((data = l_binaryReadStream(fp, &size)) == NULL) return (PIX *)ERROR_PTR("data not read", procName, NULL); pix = pixReadMemBmp(data, size); LEPT_FREE(data); return pix; } /*! * \brief pixReadMemBmp() * * \param[in] cdata bmp data * \param[in] size number of bytes of bmp-formatted data * \return pix, or NULL on error * *
 * Notes:
 *      (1) The BMP file is organized as follows:
 *          * 14 byte fileheader
 *          * Variable size infoheader: 40, 108 or 124 bytes.
 *            We only use data in he first 40 bytes.
 *          * Optional colormap, with size 4 * ncolors (in bytes)
 *          * Image data
 *      (2) 2 bpp bmp files are not valid in the original spec, but they
 *          are valid in later versions.
 * 
*/ PIX * pixReadMemBmp(const l_uint8 *cdata, size_t size) { l_uint8 pel[4]; l_uint8 *cmapBuf, *fdata, *data; l_int16 bftype, depth, d; l_int32 offset, ihbytes, width, height, height_neg, xres, yres; l_int32 compression, imagebytes, fdatabytes, cmapbytes, ncolors, maxcolors; l_int32 fdatabpl, extrabytes, pixWpl, pixBpl, i, j, k; l_uint32 *line, *pixdata, *pword; l_int64 npixels; BMP_FH *bmpfh; #if defined(__GNUC__) BMP_HEADER *bmph; #define bmpih (&bmph->bmpih) #else BMP_IH *bmpih; #endif PIX *pix, *pix1; PIXCMAP *cmap; PROCNAME("pixReadMemBmp"); if (!cdata) return (PIX *)ERROR_PTR("cdata not defined", procName, NULL); if (size < sizeof(BMP_FH) + sizeof(BMP_IH)) return (PIX *)ERROR_PTR("bmf size error", procName, NULL); /* Verify this is an uncompressed bmp */ bmpfh = (BMP_FH *)cdata; bftype = bmpfh->bfType[0] + ((l_int32)bmpfh->bfType[1] << 8); if (bftype != BMP_ID) return (PIX *)ERROR_PTR("not bmf format", procName, NULL); #if defined(__GNUC__) bmph = (BMP_HEADER *)bmpfh; #else bmpih = (BMP_IH *)(cdata + BMP_FHBYTES); #endif compression = convertOnBigEnd32(bmpih->biCompression); if (compression != 0) return (PIX *)ERROR_PTR("cannot read compressed BMP files", procName, NULL); /* Find the offset from the beginning of the file to the image data */ offset = bmpfh->bfOffBits[0]; offset += (l_int32)bmpfh->bfOffBits[1] << 8; offset += (l_int32)bmpfh->bfOffBits[2] << 16; offset += (l_uint32)bmpfh->bfOffBits[3] << 24; /* Read the remaining useful data in the infoheader. * Note that the first 4 bytes give the infoheader size. */ ihbytes = convertOnBigEnd32(*(l_uint32 *)(bmpih)); width = convertOnBigEnd32(bmpih->biWidth); height = convertOnBigEnd32(bmpih->biHeight); depth = convertOnBigEnd16(bmpih->biBitCount); imagebytes = convertOnBigEnd32(bmpih->biSizeImage); xres = convertOnBigEnd32(bmpih->biXPelsPerMeter); yres = convertOnBigEnd32(bmpih->biYPelsPerMeter); /* Some sanity checking. We impose limits on the image * dimensions, resolution and number of pixels. We make sure the * file is the correct size to hold the amount of uncompressed data * that is specified in the header. The number of colormap * entries is checked: it can be either 0 (no cmap) or some * number between 2 and 256. * Note that the imagebytes for uncompressed images is either * 0 or the size of the file data. (The fact that it can * be 0 is perhaps some legacy glitch). */ if (width < 1) return (PIX *)ERROR_PTR("width < 1", procName, NULL); if (width > L_MAX_ALLOWED_WIDTH) return (PIX *)ERROR_PTR("width too large", procName, NULL); if (height == 0 || height < -L_MAX_ALLOWED_HEIGHT || height > L_MAX_ALLOWED_HEIGHT) return (PIX *)ERROR_PTR("invalid height", procName, NULL); if (xres < 0 || xres > L_MAX_ALLOWED_RES || yres < 0 || yres > L_MAX_ALLOWED_RES) return (PIX *)ERROR_PTR("invalid resolution", procName, NULL); height_neg = 0; if (height < 0) { height_neg = 1; height = -height; } if (ihbytes != 40 && ihbytes != 108 && ihbytes != 124) { L_ERROR("invalid ihbytes = %d; not in {40, 108, 124}\n", procName, ihbytes); return NULL; } npixels = 1LL * width * height; if (npixels > L_MAX_ALLOWED_PIXELS) return (PIX *)ERROR_PTR("npixels too large", procName, NULL); if (depth != 1 && depth != 2 && depth != 4 && depth != 8 && depth != 16 && depth != 24 && depth != 32) { L_ERROR("invalid depth = %d; not in {1, 2, 4, 8, 16, 24, 32}\n", procName, depth); return NULL; } fdatabpl = 4 * ((1LL * width * depth + 31)/32); fdatabytes = fdatabpl * height; if (imagebytes != 0 && imagebytes != fdatabytes) { L_ERROR("invalid imagebytes = %d; not equal to fdatabytes = %d\n", procName, imagebytes, fdatabytes); return NULL; } /* In the original spec, BITMAPINFOHEADER is 40 bytes. * There have been a number of revisions, to capture more information. * For example, the fifth version, BITMAPV5HEADER, adds 84 bytes * of ICC color profiles. We use the size of the infoheader * to accommodate these newer formats. Knowing the size of the * infoheader gives more opportunity to sanity check input params. */ cmapbytes = offset - BMP_FHBYTES - ihbytes; ncolors = cmapbytes / sizeof(RGBA_QUAD); if (ncolors < 0 || ncolors == 1) return (PIX *)ERROR_PTR("invalid: cmap size < 0 or 1", procName, NULL); if (ncolors > 0 && depth > 8) return (PIX *)ERROR_PTR("can't have cmap for d > 8", procName, NULL); maxcolors = (depth <= 8) ? 1 << depth : 0; if (ncolors > maxcolors) { L_ERROR("cmap too large for depth %d: ncolors = %d > maxcolors = %d\n", procName, depth, ncolors, maxcolors); return NULL; } if (size != 1LL * offset + 1LL * fdatabytes) return (PIX *)ERROR_PTR("size incommensurate with image data", procName,NULL); /* Handle the colormap */ cmapBuf = NULL; if (ncolors > 0) { if ((cmapBuf = (l_uint8 *)LEPT_CALLOC(ncolors, sizeof(RGBA_QUAD))) == NULL) return (PIX *)ERROR_PTR("cmapBuf alloc fail", procName, NULL ); /* Read the colormap entry data from bmp. The RGBA_QUAD colormap * entries are used for both bmp and leptonica colormaps. */ memcpy(cmapBuf, cdata + BMP_FHBYTES + ihbytes, ncolors * sizeof(RGBA_QUAD)); } /* Make a 32 bpp pix if depth is 24 bpp */ d = (depth == 24) ? 32 : depth; if ((pix = pixCreate(width, height, d)) == NULL) { LEPT_FREE(cmapBuf); return (PIX *)ERROR_PTR( "pix not made", procName, NULL); } pixSetXRes(pix, (l_int32)((l_float32)xres / 39.37 + 0.5)); /* to ppi */ pixSetYRes(pix, (l_int32)((l_float32)yres / 39.37 + 0.5)); /* to ppi */ pixSetInputFormat(pix, IFF_BMP); pixWpl = pixGetWpl(pix); pixBpl = 4 * pixWpl; /* Convert the bmp colormap to a pixcmap */ cmap = NULL; if (ncolors > 0) { /* import the colormap to the pix cmap */ cmap = pixcmapCreate(L_MIN(d, 8)); LEPT_FREE(cmap->array); /* remove generated cmap array */ cmap->array = (void *)cmapBuf; /* and replace */ cmap->n = L_MIN(ncolors, 256); for (i = 0; i < cmap->n; i++) /* set all colors opaque */ pixcmapSetAlpha (cmap, i, 255); } if (pixSetColormap(pix, cmap)) { pixDestroy(&pix); return (PIX *)ERROR_PTR("invalid colormap", procName, NULL); } /* Acquire the image data. Image origin for bmp is at lower right. */ fdata = (l_uint8 *)cdata + offset; /* start of the bmp image data */ pixdata = pixGetData(pix); if (depth != 24) { /* typ. 1 or 8 bpp */ data = (l_uint8 *)pixdata + pixBpl * (height - 1); for (i = 0; i < height; i++) { memcpy(data, fdata, fdatabpl); fdata += fdatabpl; data -= pixBpl; } } else { /* 24 bpp file; 32 bpp pix * Note: for bmp files, pel[0] is blue, pel[1] is green, * and pel[2] is red. This is opposite to the storage * in the pix, which puts the red pixel in the 0 byte, * the green in the 1 byte and the blue in the 2 byte. * Note also that all words are endian flipped after * assignment on L_LITTLE_ENDIAN platforms. * * We can then make these assignments for little endians: * SET_DATA_BYTE(pword, 1, pel[0]); blue * SET_DATA_BYTE(pword, 2, pel[1]); green * SET_DATA_BYTE(pword, 3, pel[2]); red * This looks like: * 3 (R) 2 (G) 1 (B) 0 * |-----------|------------|-----------|-----------| * and after byte flipping: * 3 2 (B) 1 (G) 0 (R) * |-----------|------------|-----------|-----------| * * For big endians we set: * SET_DATA_BYTE(pword, 2, pel[0]); blue * SET_DATA_BYTE(pword, 1, pel[1]); green * SET_DATA_BYTE(pword, 0, pel[2]); red * This looks like: * 0 (R) 1 (G) 2 (B) 3 * |-----------|------------|-----------|-----------| * so in both cases we get the correct assignment in the PIX. * * Can we do a platform-independent assignment? * Yes, set the bytes without using macros: * *((l_uint8 *)pword) = pel[2]; red * *((l_uint8 *)pword + 1) = pel[1]; green * *((l_uint8 *)pword + 2) = pel[0]; blue * For little endians, before flipping, this looks again like: * 3 (R) 2 (G) 1 (B) 0 * |-----------|------------|-----------|-----------| */ extrabytes = fdatabpl - 3 * width; line = pixdata + pixWpl * (height - 1); for (i = 0; i < height; i++) { for (j = 0; j < width; j++) { pword = line + j; memcpy(&pel, fdata, 3); fdata += 3; *((l_uint8 *)pword + COLOR_RED) = pel[2]; *((l_uint8 *)pword + COLOR_GREEN) = pel[1]; *((l_uint8 *)pword + COLOR_BLUE) = pel[0]; /* should not use alpha byte, but for buggy readers, * set it to opaque */ *((l_uint8 *)pword + L_ALPHA_CHANNEL) = 255; } if (extrabytes) { for (k = 0; k < extrabytes; k++) { memcpy(&pel, fdata, 1); fdata++; } } line -= pixWpl; } } pixEndianByteSwap(pix); if (height_neg) pixFlipTB(pix, pix); /* ---------------------------------------------- * We do not use 1 bpp pix with colormaps in leptonica. * The colormap must be removed in such a way that the pixel * values are not changed. If the values are only black and * white, return a 1 bpp image; if gray, return an 8 bpp pix; * otherwise, return a 32 bpp rgb pix. * ---------------------------------------------- */ if (depth == 1 && cmap) { L_INFO("removing opaque cmap from 1 bpp\n", procName); pix1 = pixRemoveColormap(pix, REMOVE_CMAP_BASED_ON_SRC); pixDestroy(&pix); pix = pix1; /* rename */ } return pix; } /*--------------------------------------------------------------* * Write bmp * *--------------------------------------------------------------*/ /*! * \brief pixWriteStreamBmp() * * \param[in] fp file stream * \param[in] pix all depths * \return 0 if OK, 1 on error */ l_ok pixWriteStreamBmp(FILE *fp, PIX *pix) { l_uint8 *data; size_t size, nbytes; PROCNAME("pixWriteStreamBmp"); if (!fp) return ERROR_INT("stream not defined", procName, 1); if (!pix) return ERROR_INT("pix not defined", procName, 1); pixWriteMemBmp(&data, &size, pix); rewind(fp); nbytes = fwrite(data, 1, size, fp); free(data); if (nbytes != size) return ERROR_INT("Write error", procName, 1); return 0; } /*! * \brief pixWriteMemBmp() * * \param[out] pfdata data of bmp formatted image * \param[out] pfsize size of returned data * \param[in] pixs 1, 2, 4, 8, 16, 32 bpp * \return 0 if OK, 1 on error * *
 * Notes:
 *      (1) 2 bpp bmp files are not valid in the original spec, and are
 *          written as 8 bpp.
 *      (2) pix with depth <= 8 bpp are written with a colormap.
 *          16 bpp gray and 32 bpp rgb pix are written without a colormap.
 *      (3) The transparency component in an rgb pix is ignored.
 *          All 32 bpp pix have the bmp alpha component set to 255 (opaque).
 *      (4) The bmp colormap entries, RGBA_QUAD, are the same as
 *          the ones used for colormaps in leptonica.  This allows
 *          a simple memcpy for bmp output.
 * 
*/ l_ok pixWriteMemBmp(l_uint8 **pfdata, size_t *pfsize, PIX *pixs) { l_uint8 pel[4]; l_uint8 *cta = NULL; /* address of the bmp color table array */ l_uint8 *fdata, *data, *fmdata; l_int32 cmaplen; /* number of bytes in the bmp colormap */ l_int32 ncolors, val, stepsize, w, h, d, fdepth, xres, yres, valid; l_int32 pixWpl, pixBpl, extrabytes, fBpl, fWpl, i, j, k; l_int32 heapcm; /* extra copy of cta on the heap ? 1 : 0 */ l_uint32 offbytes, fimagebytes; l_uint32 *line, *pword; size_t fsize; BMP_FH *bmpfh; #if defined(__GNUC__) BMP_HEADER *bmph; #define bmpih (&bmph->bmpih) #else BMP_IH *bmpih; #endif PIX *pix; PIXCMAP *cmap; RGBA_QUAD *pquad; PROCNAME("pixWriteMemBmp"); if (pfdata) *pfdata = NULL; if (pfsize) *pfsize = 0; if (!pfdata) return ERROR_INT("&fdata not defined", procName, 1 ); if (!pfsize) return ERROR_INT("&fsize not defined", procName, 1 ); if (!pixs) return ERROR_INT("pixs not defined", procName, 1); /* Verify validity of colormap */ if ((cmap = pixGetColormap(pixs)) != NULL) { pixcmapIsValid(cmap, pixs, &valid); if (!valid) return ERROR_INT("colormap is not valid", procName, 1); } pixGetDimensions(pixs, &w, &h, &d); if (d == 2) { L_WARNING("2 bpp files can't be read; converting to 8 bpp\n", procName); pix = pixConvert2To8(pixs, 0, 85, 170, 255, 1); d = 8; } else { pix = pixCopy(NULL, pixs); } fdepth = (d == 32) ? 24 : d; /* Resolution is given in pixels/meter */ xres = (l_int32)(39.37 * (l_float32)pixGetXRes(pix) + 0.5); yres = (l_int32)(39.37 * (l_float32)pixGetYRes(pix) + 0.5); pixWpl = pixGetWpl(pix); pixBpl = 4 * pixWpl; fWpl = (w * fdepth + 31) / 32; fBpl = 4 * fWpl; fimagebytes = h * fBpl; if (fimagebytes > 4LL * L_MAX_ALLOWED_PIXELS) { pixDestroy(&pix); return ERROR_INT("image data is too large", procName, 1); } /* If not rgb or 16 bpp, the bmp data is required to have a colormap */ heapcm = 0; if (d == 32 || d == 16) { /* 24 bpp rgb or 16 bpp: no colormap */ ncolors = 0; cmaplen = 0; } else if ((cmap = pixGetColormap(pix))) { /* existing colormap */ ncolors = pixcmapGetCount(cmap); cmaplen = ncolors * sizeof(RGBA_QUAD); cta = (l_uint8 *)cmap->array; } else { /* no existing colormap; d <= 8; make a binary or gray one */ if (d == 1) { cmaplen = sizeof(bwmap); ncolors = 2; cta = (l_uint8 *)bwmap; } else { /* d = 2,4,8; use a grayscale output colormap */ ncolors = 1 << fdepth; cmaplen = ncolors * sizeof(RGBA_QUAD); heapcm = 1; cta = (l_uint8 *)LEPT_CALLOC(cmaplen, 1); stepsize = 255 / (ncolors - 1); for (i = 0, val = 0, pquad = (RGBA_QUAD *)cta; i < ncolors; i++, val += stepsize, pquad++) { pquad->blue = pquad->green = pquad->red = val; pquad->alpha = 255; /* opaque */ } } } #if DEBUG {l_uint8 *pcmptr; pcmptr = (l_uint8 *)pixGetColormap(pix)->array; lept_stderr("Pix colormap[0] = %c%c%c%d\n", pcmptr[0], pcmptr[1], pcmptr[2], pcmptr[3]); lept_stderr("Pix colormap[1] = %c%c%c%d\n", pcmptr[4], pcmptr[5], pcmptr[6], pcmptr[7]); } #endif /* DEBUG */ offbytes = BMP_FHBYTES + BMP_IHBYTES + cmaplen; fsize = offbytes + fimagebytes; fdata = (l_uint8 *)LEPT_CALLOC(fsize, 1); *pfdata = fdata; *pfsize = fsize; /* Write little-endian file header data */ bmpfh = (BMP_FH *)fdata; bmpfh->bfType[0] = (l_uint8)(BMP_ID >> 0); bmpfh->bfType[1] = (l_uint8)(BMP_ID >> 8); bmpfh->bfSize[0] = (l_uint8)(fsize >> 0); bmpfh->bfSize[1] = (l_uint8)(fsize >> 8); bmpfh->bfSize[2] = (l_uint8)(fsize >> 16); bmpfh->bfSize[3] = (l_uint8)(fsize >> 24); bmpfh->bfOffBits[0] = (l_uint8)(offbytes >> 0); bmpfh->bfOffBits[1] = (l_uint8)(offbytes >> 8); bmpfh->bfOffBits[2] = (l_uint8)(offbytes >> 16); bmpfh->bfOffBits[3] = (l_uint8)(offbytes >> 24); /* Convert to little-endian and write the info header data */ #if defined(__GNUC__) bmph = (BMP_HEADER *)bmpfh; #else bmpih = (BMP_IH *)(fdata + BMP_FHBYTES); #endif bmpih->biSize = convertOnBigEnd32(BMP_IHBYTES); bmpih->biWidth = convertOnBigEnd32(w); bmpih->biHeight = convertOnBigEnd32(h); bmpih->biPlanes = convertOnBigEnd16(1); bmpih->biBitCount = convertOnBigEnd16(fdepth); bmpih->biSizeImage = convertOnBigEnd32(fimagebytes); bmpih->biXPelsPerMeter = convertOnBigEnd32(xres); bmpih->biYPelsPerMeter = convertOnBigEnd32(yres); bmpih->biClrUsed = convertOnBigEnd32(ncolors); bmpih->biClrImportant = convertOnBigEnd32(ncolors); /* Copy the colormap data and free the cta if necessary */ if (ncolors > 0) { memcpy(fdata + BMP_FHBYTES + BMP_IHBYTES, cta, cmaplen); if (heapcm) LEPT_FREE(cta); } /* When you write a binary image with a colormap * that sets BLACK to 0, you must invert the data */ if (fdepth == 1 && cmap && ((l_uint8 *)(cmap->array))[0] == 0x0) { pixInvert(pix, pix); } /* An endian byte swap is also required */ pixEndianByteSwap(pix); /* Transfer the image data. Image origin for bmp is at lower right. */ fmdata = fdata + offbytes; if (fdepth != 24) { /* typ 1 or 8 bpp */ data = (l_uint8 *)pixGetData(pix) + pixBpl * (h - 1); for (i = 0; i < h; i++) { memcpy(fmdata, data, fBpl); data -= pixBpl; fmdata += fBpl; } } else { /* 32 bpp pix; 24 bpp file * See the comments in pixReadStreamBmp() to * understand the logic behind the pixel ordering below. * Note that we have again done an endian swap on * little endian machines before arriving here, so that * the bytes are ordered on both platforms as: Red Green Blue -- |-----------|------------|-----------|-----------| */ extrabytes = fBpl - 3 * w; line = pixGetData(pix) + pixWpl * (h - 1); for (i = 0; i < h; i++) { for (j = 0; j < w; j++) { pword = line + j; pel[2] = *((l_uint8 *)pword + COLOR_RED); pel[1] = *((l_uint8 *)pword + COLOR_GREEN); pel[0] = *((l_uint8 *)pword + COLOR_BLUE); memcpy(fmdata, &pel, 3); fmdata += 3; } if (extrabytes) { for (k = 0; k < extrabytes; k++) { memcpy(fmdata, &pel, 1); fmdata++; } } line -= pixWpl; } } pixDestroy(&pix); return 0; } /* --------------------------------------------*/ #endif /* USE_BMPIO */