#define _POSIX_C_SOURCE 200809L #include /* open */ #include /* mmap, munmap */ #include /* malloc, free, NULL */ #include /* memset */ #include /* write, lseek */ #include /* uint32_t, UINT32_MAX */ #include /* ntohl, htonl */ #include #include #include /* V této ukázce naprogramujeme řešení analogického problému * k proceduře ‹bwconv› z prvního týdne, která převede obrázek ve * formátu BMP ze stupňů šedi do černé a bílé. Proti původní verzi * ale budeme pracovat „in situ“, tzn. budeme upravovat stávající * soubor a provedeme to za pomoci mapování do paměti. * * Procedura přijímá parametry: * * • ‹fd› – popisovač souboru s bitmapou, * • ‹offset› – začátek pixelů v souboru, * • ‹threshold› – mez rozdělující bílou a černou (hodnota 0–255). * * Pro každý bajt určující barvu zapíšeme na výstup černou (hodnotu * 0) je-li vstupní barva «menší nebo rovna» hodnotě ‹threshold› a * bílou (‹255›) jinak. */ int bwconv( int fd, int offset, int threshold ) { uint8_t *map; off_t len; /* Nejprve standardním způsobem zjistíme velikost souboru. */ len = lseek( fd, 0, SEEK_END ); if ( len == -1 ) return -1; /* Celý soubor pak mapujeme do paměti v režimu jak pro čtení tak * pro zápis. Zároveň požadujeme, aby se změny, které provedeme * v paměti, odrazily v původním souboru příznakem ‹MAP_SHARED›. * * Poslední parametr je začátek mapování v souboru – protože * typické implementace ‹mmap› vyžadují, aby byl dělitelný * velikostí stránky, budeme mapovat celý soubor a offset * vyřešíme až při jeho zpracování (na parametr ‹offset› * procedury ‹bwconv› žádná podobná omezení neklademe). */ map = mmap( NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0 ); /* Samozřejmě nezapomeneme ověřit, zda mapování do paměti * neselhalo. K tomu slouží speciální hodnota ‹MAP_FAILED› * (funguje podobně jako ‹NULL›, ale ‹mmap› při selhání * «nevrací» nulový ukazatel). */ if ( map == MAP_FAILED ) return -1; /* Upravíme hodnoty pixelů dle zadaného předpisu. */ for ( int i = offset; i < len; ++ i ) map[ i ] = map[ i ] <= threshold ? 0 : ( uint8_t ) 255; /* «Důležité!» Mapování musíme korektně zrušit. Bez tohoto kroku * nastanou dva problémy: * * 1. únik zdrojů – informace o existenci mapování by byla * návratem z procedury ‹bwconv› ztracena a zdroj by již * nebylo možné uvolnit, * 2. úpravy provedené v paměti (virtuálním adresním prostoru) * se nemusí projevit v původním souboru. */ if ( munmap( map, len ) != 0 ) return -1; return 0; } /* ┄┄┄┄┄┄┄ %< ┄┄┄┄┄┄┄┄┄┄ následují testy ┄┄┄┄┄┄┄┄┄┄ %< ┄┄┄┄┄┄┄ */ static void unlink_if_exists( const char *file ) { if ( unlink( file ) == -1 && errno != ENOENT ) err( 2, "unlink" ); } static void close_or_warn( int fd, const char *name ) { if ( close( fd ) == -1 ) warn( "closing %s", name ); } static void write_buffer( int fd, const void *data, int nbytes ) { int written = write( fd, data, nbytes ); if ( written == -1 ) err( 2, "write" ); if ( written < nbytes ) errx( 2, "write couldn't write all data" ); } static void write_le( int fd, int bytes, uint32_t num ) { assert( bytes <= 4 ); char out[ 4 ]; for ( int i = 0; i < bytes; ++ i ) out[ i ] = ( num >> ( i * 8 ) ) & 0xff; write_buffer( fd, out, bytes ); } enum { header_size = 54 + 256 * 4 }; static void write_header( int fd, uint32_t w, uint32_t h ) { const uint32_t bmap_size = w * h * 3 + ( w % 4 ) * h; const uint32_t total = header_size + bmap_size; /* file header */ write_buffer( fd, "BM", 2 ); write_le( fd, 4, total ); write_le( fd, 4, 0 ); write_le( fd, 4, header_size ); /* DIB header */ write_le( fd, 4, 0x28 ); /* size of the DIB header */ write_le( fd, 4, w ); write_le( fd, 4, h ); write_le( fd, 2, 1 ); /* 1 layer */ write_le( fd, 2, 8 ); /* 8 bits per pixel */ write_le( fd, 4, 0 ); write_le( fd, 4, bmap_size ); write_le( fd, 4, 315 ); /* horizontal DPI */ write_le( fd, 4, 315 ); /* vertical DPI */ write_le( fd, 4, 0 ); write_le( fd, 4, 0 ); uint8_t palette[ 4 * 256 ] = { 0 }; for ( unsigned i = 0; i < sizeof( palette ); i += 4 ) memset( palette + i, i, 3 ); write_buffer( fd, palette, sizeof( palette ) ); } static int create_file( const char *file ) { int fd = openat( AT_FDCWD, file, O_CREAT | O_TRUNC | O_RDWR, 0666 ); if ( fd == -1 ) err( 2, "creating %s", file ); return fd; } static int mk_bmp( const char *file, int w, int h, const unsigned char *data, int len ) { int fd = create_file( file ); write_header( fd, w, h ); write_buffer( fd, ( const char * ) data, len ); if ( lseek( fd, -len, SEEK_CUR ) == -1 ) err( 1, "seeking in %s", file ); return fd; } static int cmp_output( int fd, const uint8_t *expected, int len ) { char buffer[ len ]; if ( lseek( fd, header_size, SEEK_SET ) == ( off_t ) -1 ) err( 2, "lseek on fd %d", fd ); int bytes = read( fd, buffer, len ); if ( bytes == -1 ) err( 2, "reading from fd %d", fd ); int cmp = len - bytes; if ( cmp == 0 ) cmp = memcmp( expected, buffer, len ); return cmp; } int main( void ) { /* Testovací bitmapová data. Za povšimnutí stojí, že řádky jdou * odspodu nahoru, tedy v opačném pořadí, než jak je vidíme * v prohlížeči obrázků. */ const unsigned char small[] = { 0x7f, 0x41, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00 }; const unsigned char small_bw[] = { 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00 }; const unsigned char grad[] = { 0x00, 0x21, 0x42, 0x63, 0x83, 0xa4, 0xff, 0x00, 0x00, 0x1d, 0x3b, 0x58, 0x76, 0x93, 0xb1, 0x00, 0x00, 0x17, 0x31, 0x4b, 0x65, 0x7e, 0x98, 0x00, 0x00, 0x11, 0x27, 0x3d, 0x53, 0x68, 0x7e, 0x00, 0x00, 0x0b, 0x1d, 0x2e, 0x41, 0x53, 0x65, 0x00, 0x00, 0x06, 0x14, 0x21, 0x2f, 0x3c, 0x4b, 0x00, 0x00, 0x03, 0x0a, 0x13, 0x1d, 0x26, 0x31, 0x00, 0x00, 0x01, 0x03, 0x06, 0x0b, 0x11, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; const unsigned char grad_bw_29[] = { 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; unsigned char grad_bw_255[ 8 * 9 ] = { 0 }; grad_bw_255[ 6 ] = 0xff; int fd_small = mk_bmp( "zt.p2_small.bmp", 2, 2, small, 8 ); assert( bwconv( fd_small, header_size, 64 ) == 0 ); assert( cmp_output( fd_small, small_bw, 8 ) == 0 ); int fd_grad = mk_bmp( "zt.p2_grad.bmp", 7, 9, grad, 8 * 9 ); assert( bwconv( fd_grad, header_size, 0xfe ) == 0 ); assert( cmp_output( fd_grad, grad_bw_255, 8 * 9 ) == 0 ); if ( lseek( fd_grad, -8 * 9, SEEK_END ) == -1 ) err( 1, "seeking in zt.p2_grad.bmp" ); int fd_grae = mk_bmp( "zt.p2_grae.bmp", 7, 9, grad, 8 * 9 ); assert( bwconv( fd_grae, header_size, 0x1c ) == 0 ); assert( cmp_output( fd_grae, grad_bw_29, 8 * 9 ) == 0 ); close_or_warn( fd_grad, "zt.p2_grad.bmp" ); close_or_warn( fd_grae, "zt.p2_grae.bmp" ); close_or_warn( fd_small, "zt.p2_small.bmp" ); unlink_if_exists( "zt.p2_grad.bmp" ); unlink_if_exists( "zt.p2_grae.bmp" ); unlink_if_exists( "zt.p2_small.bmp" ); unlink_if_exists( "zt.p2_out.bmp" ); return 0; }