#include <painter/bmp.h>
#include <painter/memory.h>
#include <painter/error.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
/*==============================================================================
Windows Bitmap File Format Implementation
Actually compression is not supported.
Supported color depths:
1bpp - w/r
4bpp - w/r
8bpp - w/r
16bpp - w/r
24bpp - w/r
32bpp - w/r
During writing a pixmap with 2bpp or 15bpp it will be automatically written
as 4bpp or 16bpp.
==============================================================================*/
#define BMPID 0x4d42
#define BMPFILEHDR_SZ 14
#define BMPINFOHDR_SZ 40
/* File Header */
typedef struct BMPFILEHEADER {
uint16_t type;
uint32_t fileSize;
uint16_t reserved0;
uint16_t reserved1;
uint32_t dataOffset;
} BMPFILEHEADER;
/* Info Header */
typedef struct BMPINFOHEADER {
uint32_t hdrSize;
uint32_t width;
uint32_t height;
uint16_t planes;
uint16_t depth;
uint32_t compression;
uint32_t bmpDataSize;
uint32_t hResolution;
uint32_t vResolution;
uint32_t nColors;
uint32_t nImportantColors;
} BMPINFOHEADER;
/**
* painterSaveBMP - Save bitmap to file.
* @fileName - file name
* @pixmap - pixmap
* @palette - palette (used in indexed color mode - can be NULL)
*
* Return 0 if bitmap was saved properly, otherwise -1.
* Conversions:
* 2 -> 4 bit,
* 15 -> 16 bit.
*
*/
int painterSaveBMP(const char *fileName, P_Pixmap *pixmap, P_Palette *pal)
{
FILE *fileHandle = NULL;
BMPFILEHEADER fileHdr;
BMPINFOHEADER infoHdr;
P_Palette tmppal;
int i = 0;
int depth = 0;
uint8_t *scanLine = NULL;
size_t rowSize = 0;
size_t scanLineSize = 0;
size_t dataSize = 0;
size_t palSize = 0;
uint8_t bSrc, bDest;
size_t j, x;
assert(fileName);
assert(pixmap);
memset(&fileHdr, 0, sizeof (fileHdr));
memset(&infoHdr, 0, sizeof (infoHdr));
memset(tmppal, 0, sizeof (tmppal));
/* saved color depth */
depth = pixmap->depth;
if (depth == 2) depth = 4;
else if (depth == 15) depth = 16;
/* calculate size of a row */
/* NOTE: rowSize might be not equal to pixmap->rowSize (ex: 2bpp) */
rowSize = painterCalcRowSize(depth, pixmap->width); /* NOTE: custom */
/* calculate size of a scan line and create buffer */
scanLineSize = rowSize;
if (scanLineSize % 4 != 0)
scanLineSize = ((scanLineSize>>2) + 1)<<2; /* 4 byte align */
scanLine = (uint8_t *)painter_malloc(scanLineSize);
if (!scanLine) {
return -1;
}
/* calculate data size */
dataSize = pixmap->height * scanLineSize;
/* calculate size of the color map table */
if (depth <= 8)
palSize = (1<<depth)<<2;
/* fill file header */
fileHdr.type = BMPID;
fileHdr.fileSize = BMPFILEHDR_SZ + BMPINFOHDR_SZ + palSize + dataSize;
fileHdr.dataOffset = BMPFILEHDR_SZ + BMPINFOHDR_SZ + palSize;
/* fill info header */
infoHdr.hdrSize = BMPINFOHDR_SZ;
infoHdr.width = pixmap->width;
infoHdr.height = pixmap->height;
infoHdr.planes = 1;
infoHdr.depth = depth;
infoHdr.compression = 0; /* uncompressed */
infoHdr.bmpDataSize = dataSize;
infoHdr.hResolution = 0;
infoHdr.vResolution = 0;
if (depth <= 8)
infoHdr.nColors = infoHdr.nImportantColors = 1<<depth;
/* open file and write header */
fileHandle = fopen(fileName, "wb");
if (!fileHandle) {
painterThrowError(P_ERR_FILESYSTEM, "painterSaveBMP", "Cannot save bitmap to file - '%s'", fileName);
return -1;
}
/* file header */
fwrite(&fileHdr.type, 2, 1, fileHandle);
fwrite(&fileHdr.fileSize, 4, 1, fileHandle);
fwrite(&fileHdr.reserved0, 2, 1, fileHandle);
fwrite(&fileHdr.reserved1, 2, 1, fileHandle);
fwrite(&fileHdr.dataOffset, 4, 1, fileHandle);
/* info header */
fwrite(&infoHdr.hdrSize, 4, 1, fileHandle);
fwrite(&infoHdr.width, 4, 1, fileHandle);
fwrite(&infoHdr.height, 4, 1, fileHandle);
fwrite(&infoHdr.planes, 2, 1, fileHandle);
fwrite(&infoHdr.depth, 2, 1, fileHandle);
fwrite(&infoHdr.compression, 4, 1, fileHandle);
fwrite(&infoHdr.bmpDataSize, 4, 1, fileHandle);
fwrite(&infoHdr.hResolution, 4, 1, fileHandle);
fwrite(&infoHdr.vResolution, 4, 1, fileHandle);
fwrite(&infoHdr.nColors, 4, 1, fileHandle);
fwrite(&infoHdr.nImportantColors, 4, 1, fileHandle);
/* write color map if needed */
if (depth <= 8) {
assert(pal);
/* change RGB format to BGR */
for (i = 0; i < (1<<depth); i++) {
tmppal[i].r = (*pal)[i].b;
tmppal[i].g = (*pal)[i].g;
tmppal[i].b = (*pal)[i].r;
}
fwrite(tmppal, 4, 1<<depth, fileHandle);
}
/* write bitmap data */
i = pixmap->height;
while (i--) {
/* clear aligning */
scanLine[scanLineSize-1] = 0;
scanLine[scanLineSize-2] = 0;
scanLine[scanLineSize-3] = 0;
scanLine[scanLineSize-4] = 0;
/* copydata, change pixels order or color depth */
switch (pixmap->depth) {
case 1: { /* 12345678 -> 87654321 */
j = pixmap->rowSize;
while (j--) {
bSrc = pixmap->rows[i][j];
bDest = 0;
x = 8;
while (x--) {
if (bSrc & (1<<x))
bDest |= (1<<(7-x));
}
scanLine[j] = bDest;
}
} break;
case 2: { /* 2bpp -> 4bpp */
x = 0;
for (j = 0; j < (unsigned)pixmap->width; j += 4) {
bSrc = pixmap->rows[i][j>>2];
scanLine[j>>1] = ((bSrc&0x3)<<4) | ((bSrc&0xc)>>2);
x += 2;
if (x+2 <= (unsigned)pixmap->width) {
scanLine[(j>>1)+1] = (bSrc&0x30) | ((bSrc&0xc0)>>6);
}
}
} break;
case 4: { /* 12 -> 21 */
j = pixmap->rowSize;
while (j--) {
bSrc = pixmap->rows[i][j];
bDest = 0;
bDest = bSrc>>4;
bSrc <<= 4;
bSrc |= bDest;
scanLine[j] = bSrc;
}
} break;
case 32: { /* RGBA -> ARGB */
j = pixmap->width;
while (j--) {
scanLine[(j<<2)] = pixmap->rows[i][(j<<2)+1] ;
scanLine[(j<<2)+1] = pixmap->rows[i][(j<<2)+2];
scanLine[(j<<2)+2] = pixmap->rows[i][(j<<2)+3];
scanLine[(j<<2)+3] = pixmap->rows[i][(j<<2)];
}
} break;
default: {
memcpy(scanLine, pixmap->rows[i], pixmap->rowSize);
} break;
}
/* write scan line */
fwrite(scanLine, 1, scanLineSize, fileHandle);
}
return 0;
}
/**
* painterLoadBMP - Create new pixmap and load bitmap data
* @fileName - file name
* @pal - pointer to palette
*
* Return pointer to new pixmap or NULL if error.
*/
P_Pixmap *painterLoadBMP(const char *fileName, P_Palette *pal)
{
P_Pixmap *pixmap = NULL;
FILE *fileHandle = NULL;
BMPFILEHEADER fileHdr;
BMPINFOHEADER infoHdr;
P_Palette tmppal;
uint8_t *scanLine = NULL;
size_t scanLineSize = 0;
size_t rowSize = 0;
int i = 0;
uint8_t bSrc, bDest;
size_t j, x;
assert(fileName);
memset(&fileHdr, 0, sizeof (fileHdr));
memset(&infoHdr, 0, sizeof (infoHdr));
fileHandle = fopen(fileName, "rb+");
if (!fileHandle) {
painterThrowError(P_ERR_FILESYSTEM, "painterLoadBMP", "Cannot load bitmap from file - '%s'", fileName);
return NULL;
}
/* read and verify file header */
fread(&fileHdr.type, 2, 1, fileHandle);
fread(&fileHdr.fileSize, 4, 1, fileHandle);
fread(&fileHdr.reserved0, 2, 1, fileHandle);
fread(&fileHdr.reserved1, 2, 1, fileHandle);
fread(&fileHdr.dataOffset, 4, 1, fileHandle);
if (fileHdr.type != BMPID) {
fclose(fileHandle);
painterThrowError(P_ERR_BMP_INVALIDHEADER, "painterLoadBMP", "Invalid file type - '%s'", fileName);
return NULL;
}
/* read info header */
fread(&infoHdr.hdrSize, 4, 1, fileHandle);
fread(&infoHdr.width, 4, 1, fileHandle);
fread(&infoHdr.height, 4, 1, fileHandle);
fread(&infoHdr.planes, 2, 1, fileHandle);
fread(&infoHdr.depth, 2, 1, fileHandle);
fread(&infoHdr.compression, 4, 1, fileHandle);
fread(&infoHdr.bmpDataSize, 4, 1, fileHandle);
fread(&infoHdr.hResolution, 4, 1, fileHandle);
fread(&infoHdr.vResolution, 4, 1, fileHandle);
fread(&infoHdr.nColors, 4, 1, fileHandle);
fread(&infoHdr.nImportantColors, 4, 1, fileHandle);
/* debug */
/*
printf("type=%u\n", fileHdr.type);
printf("fileSize=%u\n", fileHdr.fileSize);
printf("dataOffset=%u\n", fileHdr.dataOffset);
printf("hdrSize=%u\n", infoHdr.hdrSize);
printf("width=%u\n", infoHdr.width);
printf("height=%u\n", infoHdr.height);
printf("planes=%u\n", infoHdr.planes);
printf("depth=%u\n", infoHdr.depth);
printf("compression=%u\n", infoHdr.compression);
printf("bmpDataSize=%u\n", infoHdr.bmpDataSize);
printf("hResolution=%u\n", infoHdr.hResolution);
printf("vResolution=%u\n", infoHdr.vResolution);
printf("nColors=%u\n", infoHdr.nColors);
printf("nImportantColors=%u\n", infoHdr.nImportantColors);
*/
/* check colour depth */
if (infoHdr.depth != 1 && infoHdr.depth != 4 &&
infoHdr.depth != 8 && infoHdr.depth != 16 &&
infoHdr.depth != 24 && infoHdr.depth != 32) {
fclose(fileHandle);
painterThrowError(P_ERR_BMP_UNSUPPORTED, "painterLoadBMP", "Unsupported color depth - %ubpp", infoHdr.depth);
return NULL;
}
/* compression support */
if (infoHdr.compression != 0) {
fclose(fileHandle);
painterThrowError(P_ERR_BMP_UNSUPPORTED, "painterLoadBMP", "Compression is not supported");
return NULL;
}
/* read color map */
if (infoHdr.depth <= 8) {
assert(pal);
/*fseek(fileHandle, 14 + infoHdr.hdrSize, SEEK_SET);*/
fread(tmppal, 4, 1<<infoHdr.depth, fileHandle);
/* move data and change BGR format to RGB */
for (i = 0; i < (1<<infoHdr.depth); i++) {
(*pal)[i].r = tmppal[i].b;
(*pal)[i].g = tmppal[i].g;
(*pal)[i].b = tmppal[i].r;
(*pal)[i].a = 0;
}
}
/* calculate size of each row in bytes */
rowSize = painterCalcRowSize(infoHdr.depth, infoHdr.width);
/* create scan line buffer */
scanLineSize = rowSize;
if (scanLineSize % 4 != 0)
scanLineSize = ((scanLineSize>>2) + 1)<<2; /* 4 byte align */
scanLine = (uint8_t *)painter_malloc(scanLineSize);
if (!scanLine) {
fclose(fileHandle);
return NULL;
}
/* debug */
/*
printf("rowSize=%u\n", rowSize);
printf("scanLineSize=%u\n", scanLineSize);
*/
/* create new pixmap */
pixmap = painterCreatePixmap(infoHdr.width, infoHdr.height, infoHdr.depth);
if (!pixmap) {
fclose(fileHandle);
return NULL;
}
/* read bitmap data */
/*
fseek(fileHandle, 14 + infoHdr.hdrSize, SEEK_SET);
if (infoHdr.depth <= 8)
fseek(fileHandle, (1<<infoHdr.depth)<<2, SEEK_CUR);
*/
i = infoHdr.height;
while (i--) {
fread(scanLine, 1, scanLineSize, fileHandle);
/* copy data, change pixels order */
switch (infoHdr.depth) {
case 1: { /* 87654321 -> 12345678 */
j = rowSize;
while (j--) {
bSrc = scanLine[j];
bDest = 0;
x = 8;
while (x--) {
if (bSrc & (1<<x))
bDest |= (1<<(7-x));
}
pixmap->rows[i][j] = bDest;
}
} break;
case 4: { /* 21 -> 12 */
j = rowSize;
while (j--) {
bSrc = scanLine[j];
bDest = 0;
bDest = bSrc>>4;
bSrc <<= 4;
bSrc |= bDest;
pixmap->rows[i][j] = bSrc;
}
} break;
case 32: { /* RGBA -> ARGB */
j = pixmap->width;
while (j--) {
pixmap->rows[i][(j<<2)] = scanLine[(j<<2)+1];
pixmap->rows[i][(j<<2)+1] = scanLine[(j<<2)+2];
pixmap->rows[i][(j<<2)+2] = scanLine[(j<<2)+3];
pixmap->rows[i][(j<<2)+3] = scanLine[(j<<2)];
}
} break;
default: {
memcpy(pixmap->rows[i], scanLine, rowSize);
} break;
}
}
fclose(fileHandle);
return pixmap;
}
mj: na koncu nie ma jednego bajta tylko jest wyrownywanie do wielokrotnosci 4 bajtow dla kazdego wiersza...