blob: 0d2939ab3bc5daeb012745e2c9d3d92d6f3d293c [file] [log] [blame]
/*====================================================================*
- 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 */
/* --------------------------------------------*/