Skip to content

Instantly share code, notes, and snippets.

@LingDong-
Created April 19, 2023 15:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save LingDong-/87f8f53caf062fa2f19f77186a96dde1 to your computer and use it in GitHub Desktop.
Save LingDong-/87f8f53caf062fa2f19f77186a96dde1 to your computer and use it in GitHub Desktop.
tiny .BMP image reader/writer in C99
/*
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