| /*====================================================================* |
| - Copyright (C) 2001 Leptonica. All rights reserved. |
| - This software is distributed in the hope that it will be |
| - useful, but with NO WARRANTY OF ANY KIND. |
| - No author or distributor accepts responsibility to anyone for the |
| - consequences of using this software, or for whether it serves any |
| - particular purpose or works at all, unless he or she says so in |
| - writing. Everyone is granted permission to copy, modify and |
| - redistribute this source code, for commercial or non-commercial |
| - purposes, with the following restrictions: (1) the origin of this |
| - source code must not be misrepresented; (2) modified versions must |
| - be plainly marked as such; and (3) this notice may not be removed |
| - or altered from any source or modified source distribution. |
| *====================================================================*/ |
| |
| /* |
| * bmpio.c |
| * |
| * Read bmp from file |
| * PIX *pixReadStreamBmp() |
| * |
| * Write bmp to file |
| * l_int32 pixWriteStreamBmp() |
| * |
| * Read/write to memory [only on linux] |
| * PIX *pixReadMemBmp() |
| * l_int32 pixWriteMemBmp() |
| */ |
| |
| #include <stdio.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include "allheaders.h" |
| #include "bmp.h" |
| |
| /* --------------------------------------------*/ |
| #if USE_BMPIO /* defined in environ.h */ |
| /* --------------------------------------------*/ |
| |
| RGBA_QUAD bwmap[2] = { {255,255,255,0}, {0,0,0,0} }; |
| |
| #ifndef NO_CONSOLE_IO |
| #define DEBUG 0 |
| #endif /* ~NO_CONSOLE_IO */ |
| |
| |
| /*! |
| * pixReadStreamBmp() |
| * |
| * Input: stream opened for read |
| * Return: pix, or null on error |
| */ |
| PIX * |
| pixReadStreamBmp(FILE *fp) |
| { |
| l_uint16 sval; |
| l_uint32 ival; |
| l_int16 bfType, bfSize, bfFill1, bfReserved1, bfReserved2; |
| l_int16 offset, bfFill2, biPlanes, depth, d; |
| l_int32 biSize, width, height, xres, yres, compression; |
| l_int32 imagebytes, biClrUsed, biClrImportant; |
| l_uint8 *colormapBuf; |
| l_int32 colormapEntries; |
| l_int32 fileBpl, extrabytes, readerror; |
| l_int32 pixWpl, pixBpl; |
| l_int32 i, j, k; |
| l_uint8 pel[4]; |
| l_uint8 *data; |
| l_uint32 *line, *pword; |
| PIX *pix, *pixt; |
| PIXCMAP *cmap; |
| |
| PROCNAME("pixReadStreamBmp"); |
| |
| if (!fp) |
| return (PIX *)ERROR_PTR("fp not defined", procName, NULL); |
| |
| /* Read bitmap file header */ |
| fread((char *)&sval, 1, 2, fp); |
| bfType = convertOnBigEnd16(sval); |
| if (bfType != BMP_ID) |
| return (PIX *)ERROR_PTR("not bmf format", procName, NULL); |
| |
| fread((char *)&sval, 1, 2, fp); |
| bfSize = convertOnBigEnd16(sval); |
| fread((char *)&sval, 1, 2, fp); |
| bfFill1 = convertOnBigEnd16(sval); |
| fread((char *)&sval, 1, 2, fp); |
| bfReserved1 = convertOnBigEnd16(sval); |
| fread((char *)&sval, 1, 2, fp); |
| bfReserved2 = convertOnBigEnd16(sval); |
| fread((char *)&sval, 1, 2, fp); |
| offset = convertOnBigEnd16(sval); |
| fread((char *)&sval, 1, 2, fp); |
| bfFill2 = convertOnBigEnd16(sval); |
| |
| /* Read bitmap info header */ |
| fread((char *)&ival, 1, 4, fp); |
| biSize = convertOnBigEnd32(ival); |
| fread((char *)&ival, 1, 4, fp); |
| width = convertOnBigEnd32(ival); |
| fread((char *)&ival, 1, 4, fp); |
| height = convertOnBigEnd32(ival); |
| fread((char *)&sval, 1, 2, fp); |
| biPlanes = convertOnBigEnd16(sval); |
| fread((char *)&sval, 1, 2, fp); |
| depth = convertOnBigEnd16(sval); |
| fread((char *)&ival, 1, 4, fp); |
| compression = convertOnBigEnd32(ival); |
| fread((char *)&ival, 1, 4, fp); |
| imagebytes = convertOnBigEnd32(ival); |
| fread((char *)&ival, 1, 4, fp); |
| xres = convertOnBigEnd32(ival); |
| fread((char *)&ival, 1, 4, fp); |
| yres = convertOnBigEnd32(ival); |
| fread((char *)&ival, 1, 4, fp); |
| biClrUsed = convertOnBigEnd32(ival); |
| fread((char *)&ival, 1, 4, fp); |
| biClrImportant = convertOnBigEnd32(ival); |
| |
| if (compression != 0) |
| return (PIX *)ERROR_PTR("cannot read compressed BMP files", |
| procName,NULL); |
| |
| colormapEntries = (offset - BMP_FHBYTES - BMP_IHBYTES) / sizeof(RGBA_QUAD); |
| if (colormapEntries > 0) { |
| if ((colormapBuf = (l_uint8 *)CALLOC(colormapEntries, |
| sizeof(RGBA_QUAD))) == NULL) |
| return (PIX *)ERROR_PTR("colormapBuf alloc fail", procName, NULL ); |
| |
| /* Read colormap */ |
| if (fread(colormapBuf, sizeof(RGBA_QUAD), colormapEntries, fp) |
| != colormapEntries) { |
| FREE(colormapBuf); |
| return (PIX *)ERROR_PTR( "colormap read fail", procName, NULL); |
| } |
| } |
| |
| /* Make a 32 bpp pix if depth is 24 bpp */ |
| d = depth; |
| if (depth == 24) |
| d = 32; |
| if ((pix = pixCreate(width, height, d)) == NULL) |
| 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 */ |
| |
| cmap = NULL; |
| if (colormapEntries > 256) |
| L_WARNING("more than 256 colormap entries!", procName); |
| if (colormapEntries > 0) { /* import the colormap to the pix cmap */ |
| cmap = pixcmapCreate(L_MIN(d, 8)); |
| FREE(cmap->array); /* remove generated cmap array */ |
| cmap->array = (void *)colormapBuf; /* and replace */ |
| cmap->n = L_MIN(colormapEntries, 256); |
| } |
| pixSetColormap(pix, cmap); |
| |
| fileBpl = 4 * ((width * depth + 31)/32); |
| pixWpl = pixGetWpl(pix); |
| pixBpl = 4 * pixWpl; |
| |
| /* Seek to the start of the bitmap in the file */ |
| fseek(fp, offset, 0); |
| |
| if (depth != 24) { /* typ. 1 or 8 bpp */ |
| data = (l_uint8 *)pixGetData(pix) + pixBpl * (height - 1); |
| for (i = 0; i < height; i++) { |
| if (fread(data, 1, fileBpl, fp) != fileBpl) { |
| pixDestroy(&pix); |
| return (PIX *)ERROR_PTR("BMP read fail", procName, NULL); |
| } |
| 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 |
| * |-----------|------------|-----------|-----------| |
| */ |
| readerror = 0; |
| extrabytes = fileBpl - 3 * width; |
| line = pixGetData(pix) + pixWpl * (height - 1); |
| for (i = 0; i < height; i++) { |
| for (j = 0; j < width; j++) { |
| pword = line + j; |
| if (fread(&pel, 1, 3, fp) != 3) |
| readerror = 1; |
| *((l_uint8 *)pword + COLOR_RED) = pel[2]; |
| *((l_uint8 *)pword + COLOR_GREEN) = pel[1]; |
| *((l_uint8 *)pword + COLOR_BLUE) = pel[0]; |
| } |
| if (extrabytes) { |
| for (k = 0; k < extrabytes; k++) |
| fread(&pel, 1, 1, fp); |
| } |
| line -= pixWpl; |
| } |
| if (readerror) { |
| pixDestroy(&pix); |
| return (PIX *)ERROR_PTR("BMP read fail", procName, NULL); |
| } |
| } |
| |
| pixEndianByteSwap(pix); |
| |
| /* ---------------------------------------------- |
| * The bmp colormap determines the values of black |
| * and white pixels for binary in the following way: |
| * if black = 1 (255), white = 0 |
| * 255, 255, 255, 0, 0, 0, 0, 0 |
| * if black = 0, white = 1 (255) |
| * 0, 0, 0, 0, 255, 255, 255, 0 |
| * We have no need for a 1 bpp pix with a colormap! |
| * ---------------------------------------------- */ |
| if (depth == 1 && cmap) { |
| /* L_INFO("Removing colormap", procName); */ |
| pixt = pixRemoveColormap(pix, REMOVE_CMAP_BASED_ON_SRC); |
| pixDestroy(&pix); |
| pix = pixt; /* rename */ |
| } |
| |
| return pix; |
| } |
| |
| |
| |
| /*! |
| * pixWriteStreamBmp() |
| * |
| * Input: stream opened for write |
| * pix (1, 4, 8, 32 bpp) |
| * Return: 0 if OK, 1 on error |
| * |
| * Notes: |
| * (1) We position fp at the beginning of the stream, so it |
| * truncates any existing data |
| * (2) 2 bpp Bmp files are apparently not valid!. We can |
| * write and read them, but nobody else can read ours. |
| */ |
| l_int32 |
| pixWriteStreamBmp(FILE *fp, |
| PIX *pix) |
| { |
| l_uint32 offbytes, filebytes, fileimagebytes; |
| l_int32 width, height, depth, d, xres, yres; |
| l_uint16 bfType, bfSize, bfFill1, bfReserved1, bfReserved2; |
| l_uint16 bfOffBits, bfFill2, biPlanes, biBitCount; |
| l_uint16 sval; |
| l_uint32 biSize, biWidth, biHeight, biCompression, biSizeImage; |
| l_uint32 biXPelsPerMeter, biYPelsPerMeter, biClrUsed, biClrImportant; |
| l_int32 pixWpl, pixBpl, extrabytes, writeerror; |
| l_int32 fileBpl, fileWpl; |
| l_int32 i, j, k; |
| l_int32 heapcm; /* extra copy of cta on the heap ? 1 : 0 */ |
| l_uint8 *data; |
| l_uint8 pel[4]; |
| l_uint32 *line, *pword; |
| PIXCMAP *cmap; |
| l_uint8 *cta; /* address of the bmp color table array */ |
| l_int32 cmaplen; /* number of bytes in the bmp colormap */ |
| l_int32 ncolors, val, stepsize; |
| RGBA_QUAD *pquad; |
| |
| PROCNAME("pixWriteStreamBmp"); |
| |
| if (!fp) |
| return ERROR_INT("stream not defined", procName, 1); |
| if (!pix) |
| return ERROR_INT("pix not defined", procName, 1); |
| |
| width = pixGetWidth(pix); |
| height = pixGetHeight(pix); |
| d = pixGetDepth(pix); |
| if (d == 2) |
| L_WARNING("writing 2 bpp bmp file; nobody else can read", procName); |
| depth = d; |
| if (d == 32) |
| depth = 24; |
| xres = (l_int32)(39.37 * (l_float32)pixGetXRes(pix) + 0.5); /* to ppm */ |
| yres = (l_int32)(39.37 * (l_float32)pixGetYRes(pix) + 0.5); /* to ppm */ |
| |
| pixWpl = pixGetWpl(pix); |
| pixBpl = 4 * pixWpl; |
| fileWpl = (width * depth + 31) / 32; |
| fileBpl = 4 * fileWpl; |
| fileimagebytes = height * fileBpl; |
| |
| heapcm = 0; |
| if (d == 32) { /* 24 bpp rgb; 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; make a binary or gray one */ |
| if (d == 1) { |
| cmaplen = sizeof(bwmap); |
| ncolors = 2; |
| cta = (l_uint8 *)bwmap; |
| } |
| else { /* d != 32; output grayscale version */ |
| ncolors = 1 << depth; |
| cmaplen = ncolors * sizeof(RGBA_QUAD); |
| |
| heapcm = 1; |
| if ((cta = (l_uint8 *)CALLOC(cmaplen, 1)) == NULL) |
| return ERROR_INT("colormap alloc fail", procName, 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; |
| } |
| } |
| } |
| |
| #if DEBUG |
| {l_uint8 *pcmptr; |
| pcmptr = (l_uint8 *)pixGetColormap(pix)->array; |
| fprintf(stderr, "Pix colormap[0] = %c%c%c%d\n", |
| pcmptr[0], pcmptr[1], pcmptr[2], pcmptr[3]); |
| fprintf(stderr, "Pix colormap[1] = %c%c%c%d\n", |
| pcmptr[4], pcmptr[5], pcmptr[6], pcmptr[7]); |
| } |
| #endif /* DEBUG */ |
| |
| fseek(fp, 0L, 0); |
| |
| /* Convert to little-endian and write the file header data */ |
| bfType = convertOnBigEnd16(BMP_ID); |
| offbytes = BMP_FHBYTES + BMP_IHBYTES + cmaplen; |
| filebytes = offbytes + fileimagebytes; |
| sval = filebytes & 0x0000ffff; |
| bfSize = convertOnBigEnd16(sval); |
| sval = (filebytes >> 16) & 0x0000ffff; |
| bfFill1 = convertOnBigEnd16(sval); |
| bfReserved1 = 0; |
| bfReserved2 = 0; |
| sval = offbytes & 0x0000ffff; |
| bfOffBits = convertOnBigEnd16(sval); |
| sval = (offbytes >> 16) & 0x0000ffff; |
| bfFill2 = convertOnBigEnd16(sval); |
| fwrite(&bfType, 1, 2, fp); |
| fwrite(&bfSize, 1, 2, fp); |
| fwrite(&bfFill1, 1, 2, fp); |
| fwrite(&bfReserved1, 1, 2, fp); |
| fwrite(&bfReserved1, 1, 2, fp); |
| fwrite(&bfOffBits, 1, 2, fp); |
| fwrite(&bfFill2, 1, 2, fp); |
| |
| /* Convert to little-endian and write the info header data */ |
| biSize = convertOnBigEnd32(BMP_IHBYTES); |
| biWidth = convertOnBigEnd32(width); |
| biHeight = convertOnBigEnd32(height); |
| biPlanes = convertOnBigEnd16(1); |
| biBitCount = convertOnBigEnd16(depth); |
| biCompression = 0; |
| biSizeImage = convertOnBigEnd32(fileimagebytes); |
| biXPelsPerMeter = convertOnBigEnd32(xres); |
| biYPelsPerMeter = convertOnBigEnd32(yres); |
| biClrUsed = convertOnBigEnd32(ncolors); |
| biClrImportant = convertOnBigEnd32(ncolors); |
| fwrite(&biSize, 1, 4, fp); |
| fwrite(&biWidth, 1, 4, fp); |
| fwrite(&biHeight, 1, 4, fp); |
| fwrite(&biPlanes, 1, 2, fp); |
| fwrite(&biBitCount, 1, 2, fp); |
| fwrite(&biCompression, 1, 4, fp); |
| fwrite(&biSizeImage, 1, 4, fp); |
| fwrite(&biXPelsPerMeter, 1, 4, fp); |
| fwrite(&biYPelsPerMeter, 1, 4, fp); |
| fwrite(&biClrUsed, 1, 4, fp); |
| fwrite(&biClrImportant, 1, 4, fp); |
| |
| /* Write the colormap data */ |
| if (ncolors > 0) { |
| if (fwrite(cta, 1, cmaplen, fp) != cmaplen) { |
| if (heapcm) |
| FREE(cta); |
| return ERROR_INT("colormap write fail", procName, 1); |
| } |
| if (heapcm) |
| FREE(cta); |
| } |
| |
| /* When you write a binary image with a colormap |
| * that sets BLACK to 0, you must invert the data */ |
| if (depth == 1 && cmap && ((l_uint8 *)(cmap->array))[0] == 0x0) { |
| pixInvert(pix, pix); |
| } |
| |
| pixEndianByteSwap(pix); |
| |
| writeerror = 0; |
| if (depth != 24) { /* typ 1 or 8 bpp */ |
| data = (l_uint8 *)pixGetData(pix) + pixBpl * (height - 1); |
| for (i = 0; i < height; i++) { |
| if (fwrite(data, 1, fileBpl, fp) != fileBpl) |
| writeerror = 1; |
| data -= pixBpl; |
| } |
| } |
| 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 = fileBpl - 3 * width; |
| line = pixGetData(pix) + pixWpl * (height - 1); |
| for (i = 0; i < height; i++) { |
| for (j = 0; j < width; 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); |
| if (fwrite(&pel, 1, 3, fp) != 3) |
| writeerror = 1; |
| } |
| if (extrabytes) { |
| for (k = 0; k < extrabytes; k++) |
| fwrite(&pel, 1, 1, fp); |
| } |
| line -= pixWpl; |
| } |
| } |
| |
| /* Restore to original state */ |
| pixEndianByteSwap(pix); |
| if (depth == 1 && cmap && ((l_uint8 *)(cmap->array))[0] == 0x0) |
| pixInvert(pix, pix); |
| |
| if (writeerror) |
| return ERROR_INT("image write fail", procName, 1); |
| |
| return 0; |
| } |
| |
| |
| /*---------------------------------------------------------------------* |
| * Read/write to memory * |
| *---------------------------------------------------------------------*/ |
| #ifdef HAVE_CONFIG_H |
| #include "config_auto.h" |
| #endif /* HAVE_CONFIG_H */ |
| |
| #if HAVE_FMEMOPEN |
| |
| #include "_stdio.h" |
| /* extern FILE *open_memstream(char **data, size_t *size); */ |
| /* extern FILE *fmemopen(void *data, size_t size, const char *mode); */ |
| |
| /*! |
| * pixReadMemBmp() |
| * |
| * Input: cdata (const; bmp-encoded) |
| * size (of data) |
| * Return: pix, or null on error |
| * |
| * Notes: |
| * (1) The @size byte of @data must be a null character. |
| */ |
| PIX * |
| pixReadMemBmp(const l_uint8 *cdata, |
| size_t size) |
| { |
| l_uint8 *data; |
| FILE *fp; |
| PIX *pix; |
| |
| PROCNAME("pixReadMemBmp"); |
| |
| if (!cdata) |
| return (PIX *)ERROR_PTR("cdata not defined", procName, NULL); |
| |
| data = (l_uint8 *)cdata; /* we're really not going to change this */ |
| if ((fp = fmemopen(data, size, "r")) == NULL) |
| return (PIX *)ERROR_PTR("stream not opened", procName, NULL); |
| pix = pixReadStreamBmp(fp); |
| fclose(fp); |
| return pix; |
| } |
| |
| |
| /*! |
| * pixWriteMemBmp() |
| * |
| * Input: &data (<return> data of tiff compressed image) |
| * &size (<return> size of returned data) |
| * pix |
| * Return: 0 if OK, 1 on error |
| * |
| * Notes: |
| * (1) See pixWriteStreamBmp() for usage. This version writes to |
| * memory instead of to a file stream. |
| */ |
| l_int32 |
| pixWriteMemBmp(l_uint8 **pdata, |
| size_t *psize, |
| PIX *pix) |
| { |
| l_int32 ret; |
| FILE *fp; |
| |
| PROCNAME("pixWriteMemBmp"); |
| |
| if (!pdata) |
| return ERROR_INT("&data not defined", procName, 1 ); |
| if (!psize) |
| return ERROR_INT("&size not defined", procName, 1 ); |
| if (!pix) |
| return ERROR_INT("&pix not defined", procName, 1 ); |
| |
| if ((fp = open_memstream((char **)pdata, psize)) == NULL) |
| return ERROR_INT("stream not opened", procName, 1); |
| ret = pixWriteStreamBmp(fp, pix); |
| fclose(fp); |
| return ret; |
| } |
| |
| #else |
| |
| PIX * |
| pixReadMemBmp(const l_uint8 *cdata, |
| size_t size) |
| { |
| return (PIX *)ERROR_PTR( |
| "bmp read from memory not implemented on this platform", |
| "pixReadMemBmp", NULL); |
| } |
| |
| |
| l_int32 |
| pixWriteMemBmp(l_uint8 **pdata, |
| size_t *psize, |
| PIX *pix) |
| { |
| return ERROR_INT( |
| "bmp write to memory not implemented on this platform", |
| "pixWriteMemBmp", 1); |
| } |
| |
| #endif /* HAVE_FMEMOPEN */ |
| |
| /* --------------------------------------------*/ |
| #endif /* USE_BMPIO */ |
| /* --------------------------------------------*/ |
| |