Created
April 19, 2023 15:58
-
-
Save LingDong-/87f8f53caf062fa2f19f77186a96dde1 to your computer and use it in GitHub Desktop.
tiny .BMP image reader/writer in C99
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
bmprw.h | |
tiny .BMP image reader/writer in C99 | |
with no memory allocations | |
reader supports: | |
- 1, 4, 8, 24, 32 bit depth, palette / no palette | |
- RLE4 / RLE8 compression / no compression | |
- BI_BITFIELDS and BI_ALPHABITFIELDS | |
- alpha channel | |
reader does not support: | |
- OS/2 features (huffman, halftone) | |
writer format: | |
- 32 bit RGBA with BI_BITFIELDS (8.8.8.8) | |
see bottom of this file for usage examples. | |
(c) lingdong huang 2023, MIT license | |
references: | |
- https://en.wikipedia.org/wiki/BMP_file_format | |
- https://learn.microsoft.com/en-us/windows/win32/gdi/bitmap-compression | |
- http://entropymine.com/jason/bmpsuite/bmpsuite/html/bmpsuite.html | |
*/ | |
#ifndef __BMPRW_H__ | |
#define __BMPRW_H__ | |
#include <stdio.h> | |
#include <stdint.h> | |
#ifndef BMP_NOREAD | |
void bmp_read(FILE* fd, void (*onread_wh)(int,int), void (*onread_px)(int,int,int,int)){ | |
fseek(fd,10,SEEK_SET); | |
uint32_t pxoff = fgetc(fd) | ((uint32_t)fgetc(fd)<<8) | ((uint32_t)fgetc(fd)<<16) | ((uint32_t)fgetc(fd)<<24); | |
uint32_t hdsz = fgetc(fd) | ((uint32_t)fgetc(fd)<<8) | ((uint32_t)fgetc(fd)<<16) | ((uint32_t)fgetc(fd)<<24); | |
int32_t w,h,bitdep,plane,compr=0; | |
if (hdsz == 12){ | |
w = fgetc(fd) | (fgetc(fd)<<8); | |
h = fgetc(fd) | (fgetc(fd)<<8); | |
plane = fgetc(fd) | ((uint32_t)fgetc(fd)<<8); | |
bitdep = fgetc(fd) | ((uint32_t)fgetc(fd)<<8); | |
}else{ | |
w = fgetc(fd) | ((int32_t)fgetc(fd)<<8) | ((int32_t)fgetc(fd)<<16) | ((int32_t)fgetc(fd)<<24); | |
h = fgetc(fd) | ((int32_t)fgetc(fd)<<8) | ((int32_t)fgetc(fd)<<16) | ((int32_t)fgetc(fd)<<24); | |
plane = fgetc(fd) | ((uint32_t)fgetc(fd)<<8); | |
bitdep = fgetc(fd) | ((uint32_t)fgetc(fd)<<8); | |
compr = fgetc(fd); | |
} | |
(void)plane; | |
uint32_t mkr, mkg, mkb, mka; | |
int shr=0, shg=0, shb=0, sha=0; | |
uint32_t mxr, mxg, mxb, mxa; | |
int has_alpha = (compr == 6) || (hdsz > 52); | |
if (compr == 3 || compr == 6){ | |
fseek(fd,54,SEEK_SET); | |
mxr = mkr = fgetc(fd) | ((uint32_t)fgetc(fd)<<8) | ((uint32_t)fgetc(fd)<<16) | ((uint32_t)fgetc(fd)<<24); | |
mxg = mkg = fgetc(fd) | ((uint32_t)fgetc(fd)<<8) | ((uint32_t)fgetc(fd)<<16) | ((uint32_t)fgetc(fd)<<24); | |
mxb = mkb = fgetc(fd) | ((uint32_t)fgetc(fd)<<8) | ((uint32_t)fgetc(fd)<<16) | ((uint32_t)fgetc(fd)<<24); | |
mxa = mka = fgetc(fd) | ((uint32_t)fgetc(fd)<<8) | ((uint32_t)fgetc(fd)<<16) | ((uint32_t)fgetc(fd)<<24); | |
if (mxr) while (! (mxr & 1)) {mxr >>= 1; shr++;} | |
if (mxg) while (! (mxg & 1)) {mxg >>= 1; shg++;} | |
if (mxb) while (! (mxb & 1)) {mxb >>= 1; shb++;} | |
if (mxa) while (! (mxa & 1)) {mxa >>= 1; sha++;} | |
has_alpha &= !!mxa; | |
} | |
uint32_t tclr = 14+hdsz; | |
fseek(fd,tclr,SEEK_SET); | |
uint32_t clr0 = fgetc(fd) | ((uint32_t)fgetc(fd)<<8) | ((uint32_t)fgetc(fd)<<16) | ((uint32_t)fgetc(fd)<<24); | |
uint32_t clr1 = fgetc(fd) | ((uint32_t)fgetc(fd)<<8) | ((uint32_t)fgetc(fd)<<16); | |
uint32_t rowsz = ((bitdep * w + 31)/32) *4; | |
int8_t flipy = 0; | |
if (h < 0){ | |
h = -h; | |
flipy = 1; | |
} | |
if (compr == 3 || compr == 6){ | |
onread_wh(w,-h); | |
}else{ | |
onread_wh(w,h); | |
} | |
if (compr == 0){ | |
if (bitdep == 1){ | |
int q; | |
for (int i = 0; i < h; i++){ | |
fseek(fd,pxoff+(flipy?i:(h-i-1))*rowsz,SEEK_SET); | |
for (int j = 0; j < w; j++){ | |
if (j % 8 == 0) q = fgetc(fd); | |
int k = (q>>(7-(j%8)))&1; | |
int32_t c = k ? clr1 : clr0; | |
onread_px((c>>16)&0xff,(c>>8)&0xff,(c&0xff),255); | |
} | |
} | |
}else if (bitdep == 4){ | |
for (int i = 0; i < h; i++){ | |
for (int j = 0; j < w; j++){ | |
fseek(fd,pxoff+(flipy?i:(h-i-1))*rowsz+(j/2),SEEK_SET); | |
int k = fgetc(fd); | |
fseek(fd,tclr+((k>>((!(j&1))<<2))&0xf)*4,SEEK_SET); | |
int b = fgetc(fd); | |
int g = fgetc(fd); | |
int r = fgetc(fd); | |
onread_px(r,g,b,255); | |
} | |
} | |
}else if (bitdep == 8){ | |
for (int i = 0; i < h; i++){ | |
for (int j = 0; j < w; j++){ | |
fseek(fd,pxoff+(flipy?i:(h-i-1))*rowsz+j,SEEK_SET); | |
int k = fgetc(fd); | |
fseek(fd,tclr+k*4,SEEK_SET); | |
int b = fgetc(fd); | |
int g = fgetc(fd); | |
int r = fgetc(fd); | |
onread_px(r,g,b,255); | |
} | |
} | |
}else if (bitdep == 16){ | |
for (int i = 0; i < h; i++){ | |
fseek(fd,pxoff+(flipy?i:(h-i-1))*rowsz,SEEK_SET); | |
for (int j = 0; j < w; j++){ | |
uint32_t c = fgetc(fd) | ((uint32_t)fgetc(fd)<<8); | |
int r = ((c>>10)&0x1f)*255/0x1f; | |
int g = ((c>>5)&0x1f)*255/0x1f; | |
int b = (c&0x1f)*255/0x1f; | |
onread_px(r,g,b,255); | |
} | |
} | |
}else if (bitdep == 24){ | |
for (int i = 0; i < h; i++){ | |
fseek(fd,pxoff+(flipy?i:(h-i-1))*rowsz,SEEK_SET); | |
for (int j = 0; j < w; j++){ | |
int b = fgetc(fd); | |
int g = fgetc(fd); | |
int r = fgetc(fd); | |
onread_px(r,g,b,255); | |
} | |
} | |
}else if (bitdep == 32){ | |
for (int i = 0; i < h; i++){ | |
fseek(fd,pxoff+(flipy?i:(h-i-1))*rowsz,SEEK_SET); | |
for (int j = 0; j < w; j++){ | |
int b = fgetc(fd); | |
int g = fgetc(fd); | |
int r = fgetc(fd); | |
fgetc(fd); // not alpha, "high byte unused" | |
onread_px(r,g,b,255); | |
} | |
} | |
} | |
}else if (compr == 3 || compr == 6){ | |
if (bitdep == 16){ | |
for (int i = 0; i < h; i++){ | |
fseek(fd,pxoff+(flipy?i:(h-i-1))*rowsz,SEEK_SET); | |
for (int j = 0; j < w; j++){ | |
uint32_t c = fgetc(fd) | ((uint32_t)fgetc(fd)<<8); | |
int r = ((c & mkr)>>shr)*255/(mxr|1); | |
int g = ((c & mkg)>>shg)*255/(mxg|1); | |
int b = ((c & mkb)>>shb)*255/(mxb|1); | |
int a = 255; | |
if (has_alpha){ | |
a = ((c & mka)>>sha)*255/mxa; | |
} | |
onread_px(r,g,b,a); | |
} | |
} | |
}else if (bitdep == 32){ | |
for (int i = 0; i < h; i++){ | |
fseek(fd,pxoff+(flipy?i:(h-i-1))*rowsz,SEEK_SET); | |
for (int j = 0; j < w; j++){ | |
uint32_t c = fgetc(fd) | ((uint32_t)fgetc(fd)<<8) | ((uint32_t)fgetc(fd)<<16) | ((uint32_t)fgetc(fd)<<24); | |
int r = ((c & mkr)>>shr)*255/(mxr|1); | |
int g = ((c & mkg)>>shg)*255/(mxg|1); | |
int b = ((c & mkb)>>shb)*255/(mxb|1); | |
int a = 255; | |
if (has_alpha){ | |
a = ((c & mka)>>sha)*255/mxa; | |
} | |
onread_px(r,g,b,a); | |
} | |
} | |
} | |
}else if (compr == 1 || compr == 2){ | |
fseek(fd,pxoff,SEEK_SET); | |
int pos = pxoff; | |
int col = 0; | |
int cnt = 0; | |
do{ | |
int b0 = fgetc(fd); | |
int b1 = fgetc(fd); | |
if (b0 < 0 || b1 < 0){ | |
break; | |
} | |
pos += 2; | |
if (b0 == 0){ | |
if (b1 == 0 || b1 == 1){ | |
while (col && (col < w)){ | |
onread_px(0,0,0,0); | |
col++; | |
cnt++; | |
} | |
col = 0; | |
if (b1){ | |
while (cnt < w*h){ | |
onread_px(0,0,0,0); | |
cnt++; | |
} | |
break; | |
} | |
}else if (b1 == 2){ | |
int dx = fgetc(fd); | |
int dy = fgetc(fd); | |
for (int i = 0; i < dx; i++){ | |
onread_px(0,0,0,0); | |
col++; | |
cnt++; | |
} | |
for (int i = 0; i < dy; i++){ | |
for (int j = 0; j < w; j++){ | |
onread_px(0,0,0,0); | |
cnt++; | |
} | |
} | |
pos += 2; | |
}else{ | |
int k; | |
for (int i = 0; i < b1; i++){ | |
if (compr == 1 || (!(i & 1))){ | |
fseek(fd,pos++,SEEK_SET); | |
} | |
if (compr == 1){ | |
k = fgetc(fd); | |
fseek(fd,tclr+k*4,SEEK_SET); | |
}else if (i & 1){ | |
fseek(fd,tclr+(k&0xf)*4,SEEK_SET); | |
}else{ | |
k = fgetc(fd); | |
fseek(fd,tclr+((k>>4)&0xf)*4,SEEK_SET); | |
} | |
int b = fgetc(fd); | |
int g = fgetc(fd); | |
int r = fgetc(fd); | |
onread_px(r,g,b,255); | |
col = (col + 1)%w; | |
cnt++; | |
} | |
if (compr == 1){ | |
if (b1&1) pos ++; | |
}else{ | |
if (b1&1) b1 ++; | |
if (((b1/2)&1)) pos++; | |
} | |
fseek(fd,pos,SEEK_SET); | |
} | |
}else if (compr == 1){ | |
fseek(fd,tclr+b1*4,SEEK_SET); | |
int b = fgetc(fd); | |
int g = fgetc(fd); | |
int r = fgetc(fd); | |
for (int i = 0; i < b0; i++){ | |
onread_px(r,g,b,255); | |
col = (col + 1)%w; | |
cnt++; | |
} | |
fseek(fd,pos,SEEK_SET); | |
}else{ | |
fseek(fd,tclr+((b1>>4)&0xf)*4,SEEK_SET); | |
int ba = fgetc(fd); | |
int ga = fgetc(fd); | |
int ra = fgetc(fd); | |
fseek(fd,tclr+((b1)&0xf)*4,SEEK_SET); | |
int bb = fgetc(fd); | |
int gb = fgetc(fd); | |
int rb = fgetc(fd); | |
for (int i = 0; i < b0; i++){ | |
if (i & 1){ | |
onread_px(rb,gb,bb,255); | |
}else{ | |
onread_px(ra,ga,ba,255); | |
} | |
col = (col + 1)%w; | |
cnt++; | |
} | |
fseek(fd,pos,SEEK_SET); | |
} | |
}while(1); | |
} | |
} | |
#endif | |
#ifndef BMP_NOWRITE | |
void bmp_write_header(FILE* fd, int w, int h){ | |
fputc('B',fd);fputc('M',fd); | |
int32_t dsz = (w*4) * h; | |
int32_t fsz = 138+dsz; | |
fputc(fsz&0xff,fd); | |
fputc((fsz>>8)&0xff,fd); | |
fputc((fsz>>16)&0xff,fd); | |
fputc((fsz>>24)&0xff,fd); | |
fputc(0,fd);fputc(0,fd);fputc(0,fd);fputc(0,fd); | |
fputc(138,fd);fputc(0,fd);fputc(0,fd);fputc(0,fd); | |
fputc(124,fd);fputc(0,fd);fputc(0,fd);fputc(0,fd); | |
int nh = -h; | |
fputc(w&0xff,fd); | |
fputc((w>>8)&0xff,fd); | |
fputc((w>>16)&0xff,fd); | |
fputc((w>>24)&0xff,fd); | |
fputc(nh&0xff,fd); | |
fputc((nh>>8)&0xff,fd); | |
fputc((nh>>16)&0xff,fd); | |
fputc((nh>>24)&0xff,fd); | |
fputc(1,fd);fputc(0,fd); | |
fputc(32,fd);fputc(0,fd); | |
fputc(3,fd);fputc(0,fd);fputc(0,fd);fputc(0,fd); | |
fputc(dsz&0xff,fd); | |
fputc((dsz>>8)&0xff,fd); | |
fputc((dsz>>16)&0xff,fd); | |
fputc((dsz>>24)&0xff,fd); | |
fputc(0x13,fd);fputc(0xb,fd);fputc(0,fd);fputc(0,fd); | |
fputc(0x13,fd);fputc(0xb,fd);fputc(0,fd);fputc(0,fd); | |
fputc(0,fd);fputc(0,fd);fputc(0,fd);fputc(0,fd); | |
fputc(0,fd);fputc(0,fd);fputc(0,fd);fputc(0,fd); | |
fputc(0,fd);fputc(0,fd);fputc(0xff,fd);fputc(0,fd); | |
fputc(0,fd);fputc(0xff,fd);fputc(0,fd);fputc(0,fd); | |
fputc(0xff,fd);fputc(0,fd);fputc(0,fd);fputc(0,fd); | |
fputc(0,fd);fputc(0,fd);fputc(0,fd);fputc(0xff,fd); | |
fputc('B',fd);fputc('G',fd);fputc('R',fd);fputc('s',fd); | |
for (int i = 0; i < 64; i++) fputc(0,fd); | |
} | |
void bmp_write_pixel(FILE* fd, int r, int g, int b, int a){ | |
fputc(b,fd); | |
fputc(g,fd); | |
fputc(r,fd); | |
fputc(a,fd); | |
} | |
#endif | |
#endif | |
/* | |
// reader example: read BMP from file, writes PPM to stdout | |
#include "bmprw.h" | |
void got_wh(int w, int h){ | |
printf("P3\n%d %d\n255\n",w,h<0?-h:h); | |
} | |
void got_px(int r, int g, int b, int a){ | |
printf("%d %d %d # %d\n",r,g,b,a); | |
} | |
int main(){ | |
FILE* fd = fopen("tests/pal4.bmp","r"); | |
bmp_read(fd,&got_wh,&got_px); | |
fclose(fd); | |
} | |
*/ | |
/* | |
// writer example: generate pixels, write BMP to stdout | |
#include "bmprw.h" | |
int main(){ | |
FILE* fd = stdout; | |
bmp_write_header(fd, 255, 255); | |
for (int i = 0; i < 255; i++){ | |
for (int j = 0; j < 255; j++){ | |
bmp_write_pixel(fd,i,j,255,255); | |
} | |
} | |
} | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment