├── rebar ├── .gitignore ├── README ├── src ├── gpsinfo.erl ├── dbg.hrl ├── erl_img.app.src ├── image_undef.erl ├── video_mpeg.erl ├── api.hrl ├── jpeg.hrl ├── adler.erl ├── gpsinfo.hrl ├── exif.hrl ├── exif.erl ├── image_bmp.erl ├── tiff.hrl ├── image_tga.erl ├── erl_img.erl ├── image_jpeg.erl ├── image_x_xpixmap.erl ├── image_gif.erl ├── lzw.erl ├── image_tiff.erl └── image_png.erl ├── priv └── pngsuite │ ├── basi0g01.png │ ├── basi0g02.png │ ├── basi0g04.png │ ├── basi0g08.png │ ├── basi0g16.png │ ├── basi2c08.png │ ├── basi2c16.png │ ├── basi3p01.png │ ├── basi3p02.png │ ├── basi3p04.png │ ├── basi3p08.png │ ├── basi4a08.png │ ├── basi4a16.png │ ├── basi6a08.png │ ├── basi6a16.png │ ├── basn0g01.png │ ├── basn0g02.png │ ├── basn0g04.png │ ├── basn0g08.png │ ├── basn0g16.png │ ├── basn2c08.png │ ├── basn2c16.png │ ├── basn3p01.png │ ├── basn3p02.png │ ├── basn3p04.png │ ├── basn3p08.png │ ├── basn4a08.png │ ├── basn4a16.png │ ├── basn6a08.png │ ├── basn6a16.png │ ├── bgai4a08.png │ ├── bgai4a16.png │ ├── bgan6a08.png │ ├── bgan6a16.png │ ├── bgbn4a08.png │ ├── bggn4a16.png │ ├── bgwn6a08.png │ ├── bgyn6a16.png │ ├── ccwn2c08.png │ ├── ccwn3p08.png │ ├── cdfn2c08.png │ ├── cdhn2c08.png │ ├── cdsn2c08.png │ ├── cdun2c08.png │ ├── ch1n3p04.png │ ├── ch2n3p08.png │ ├── cm0n0g04.png │ ├── cm7n0g04.png │ ├── cm9n0g04.png │ ├── cs3n2c16.png │ ├── cs3n3p08.png │ ├── cs5n2c08.png │ ├── cs5n3p08.png │ ├── cs8n2c08.png │ ├── cs8n3p08.png │ ├── ct0n0g04.png │ ├── ct1n0g04.png │ ├── ctzn0g04.png │ ├── f00n0g08.png │ ├── f00n2c08.png │ ├── f01n0g08.png │ ├── f01n2c08.png │ ├── f02n0g08.png │ ├── f02n2c08.png │ ├── f03n0g08.png │ ├── f03n2c08.png │ ├── f04n0g08.png │ ├── f04n2c08.png │ ├── g03n0g16.png │ ├── g03n2c08.png │ ├── g03n3p04.png │ ├── g04n0g16.png │ ├── g04n2c08.png │ ├── g04n3p04.png │ ├── g05n0g16.png │ ├── g05n2c08.png │ ├── g05n3p04.png │ ├── g07n0g16.png │ ├── g07n2c08.png │ ├── g07n3p04.png │ ├── g10n0g16.png │ ├── g10n2c08.png │ ├── g10n3p04.png │ ├── g25n0g16.png │ ├── g25n2c08.png │ ├── g25n3p04.png │ ├── oi1n0g16.png │ ├── oi1n2c16.png │ ├── oi2n0g16.png │ ├── oi2n2c16.png │ ├── oi4n0g16.png │ ├── oi4n2c16.png │ ├── oi9n0g16.png │ ├── oi9n2c16.png │ ├── pp0n2c16.png │ ├── pp0n6a08.png │ ├── ps1n0g08.png │ ├── ps1n2c16.png │ ├── ps2n0g08.png │ ├── ps2n2c16.png │ ├── s01i3p01.png │ ├── s01n3p01.png │ ├── s02i3p01.png │ ├── s02n3p01.png │ ├── s03i3p01.png │ ├── s03n3p01.png │ ├── s04i3p01.png │ ├── s04n3p01.png │ ├── s05i3p02.png │ ├── s05n3p02.png │ ├── s06i3p02.png │ ├── s06n3p02.png │ ├── s07i3p02.png │ ├── s07n3p02.png │ ├── s08i3p02.png │ ├── s08n3p02.png │ ├── s09i3p02.png │ ├── s09n3p02.png │ ├── s32i3p04.png │ ├── s32n3p04.png │ ├── s33i3p04.png │ ├── s33n3p04.png │ ├── s34i3p04.png │ ├── s34n3p04.png │ ├── s35i3p04.png │ ├── s35n3p04.png │ ├── s36i3p04.png │ ├── s36n3p04.png │ ├── s37i3p04.png │ ├── s37n3p04.png │ ├── s38i3p04.png │ ├── s38n3p04.png │ ├── s39i3p04.png │ ├── s39n3p04.png │ ├── s40i3p04.png │ ├── s40n3p04.png │ ├── tbbn1g04.png │ ├── tbbn2c16.png │ ├── tbbn3p08.png │ ├── tbgn2c16.png │ ├── tbgn3p08.png │ ├── tbrn2c08.png │ ├── tbwn1g16.png │ ├── tbwn3p08.png │ ├── tbyn3p08.png │ ├── tp0n1g08.png │ ├── tp0n2c08.png │ ├── tp0n3p08.png │ ├── tp1n3p08.png │ ├── z00n2c08.png │ ├── z03n2c08.png │ ├── z06n2c08.png │ ├── z09n2c08.png │ ├── pngsuite_logo.png │ ├── corrupted │ ├── x00n0g01.png │ ├── xcrn0g04.png │ └── xlfn0g04.png │ └── pngsuite.doc ├── .travis.yml ├── rebar.config ├── Makefile ├── scripts └── mktab_lzw.escript └── include └── erl_img.hrl /rebar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/rebar -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.eunit 2 | /ebin 3 | /doc 4 | TEST-*.xml 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Image processing stuff (bmp, gif, jpeg, png, xpm, tiff, mpeg) 2 | -------------------------------------------------------------------------------- /src/gpsinfo.erl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/src/gpsinfo.erl -------------------------------------------------------------------------------- /priv/pngsuite/basi0g01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/basi0g01.png -------------------------------------------------------------------------------- /priv/pngsuite/basi0g02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/basi0g02.png -------------------------------------------------------------------------------- /priv/pngsuite/basi0g04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/basi0g04.png -------------------------------------------------------------------------------- /priv/pngsuite/basi0g08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/basi0g08.png -------------------------------------------------------------------------------- /priv/pngsuite/basi0g16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/basi0g16.png -------------------------------------------------------------------------------- /priv/pngsuite/basi2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/basi2c08.png -------------------------------------------------------------------------------- /priv/pngsuite/basi2c16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/basi2c16.png -------------------------------------------------------------------------------- /priv/pngsuite/basi3p01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/basi3p01.png -------------------------------------------------------------------------------- /priv/pngsuite/basi3p02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/basi3p02.png -------------------------------------------------------------------------------- /priv/pngsuite/basi3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/basi3p04.png -------------------------------------------------------------------------------- /priv/pngsuite/basi3p08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/basi3p08.png -------------------------------------------------------------------------------- /priv/pngsuite/basi4a08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/basi4a08.png -------------------------------------------------------------------------------- /priv/pngsuite/basi4a16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/basi4a16.png -------------------------------------------------------------------------------- /priv/pngsuite/basi6a08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/basi6a08.png -------------------------------------------------------------------------------- /priv/pngsuite/basi6a16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/basi6a16.png -------------------------------------------------------------------------------- /priv/pngsuite/basn0g01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/basn0g01.png -------------------------------------------------------------------------------- /priv/pngsuite/basn0g02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/basn0g02.png -------------------------------------------------------------------------------- /priv/pngsuite/basn0g04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/basn0g04.png -------------------------------------------------------------------------------- /priv/pngsuite/basn0g08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/basn0g08.png -------------------------------------------------------------------------------- /priv/pngsuite/basn0g16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/basn0g16.png -------------------------------------------------------------------------------- /priv/pngsuite/basn2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/basn2c08.png -------------------------------------------------------------------------------- /priv/pngsuite/basn2c16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/basn2c16.png -------------------------------------------------------------------------------- /priv/pngsuite/basn3p01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/basn3p01.png -------------------------------------------------------------------------------- /priv/pngsuite/basn3p02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/basn3p02.png -------------------------------------------------------------------------------- /priv/pngsuite/basn3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/basn3p04.png -------------------------------------------------------------------------------- /priv/pngsuite/basn3p08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/basn3p08.png -------------------------------------------------------------------------------- /priv/pngsuite/basn4a08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/basn4a08.png -------------------------------------------------------------------------------- /priv/pngsuite/basn4a16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/basn4a16.png -------------------------------------------------------------------------------- /priv/pngsuite/basn6a08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/basn6a08.png -------------------------------------------------------------------------------- /priv/pngsuite/basn6a16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/basn6a16.png -------------------------------------------------------------------------------- /priv/pngsuite/bgai4a08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/bgai4a08.png -------------------------------------------------------------------------------- /priv/pngsuite/bgai4a16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/bgai4a16.png -------------------------------------------------------------------------------- /priv/pngsuite/bgan6a08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/bgan6a08.png -------------------------------------------------------------------------------- /priv/pngsuite/bgan6a16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/bgan6a16.png -------------------------------------------------------------------------------- /priv/pngsuite/bgbn4a08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/bgbn4a08.png -------------------------------------------------------------------------------- /priv/pngsuite/bggn4a16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/bggn4a16.png -------------------------------------------------------------------------------- /priv/pngsuite/bgwn6a08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/bgwn6a08.png -------------------------------------------------------------------------------- /priv/pngsuite/bgyn6a16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/bgyn6a16.png -------------------------------------------------------------------------------- /priv/pngsuite/ccwn2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/ccwn2c08.png -------------------------------------------------------------------------------- /priv/pngsuite/ccwn3p08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/ccwn3p08.png -------------------------------------------------------------------------------- /priv/pngsuite/cdfn2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/cdfn2c08.png -------------------------------------------------------------------------------- /priv/pngsuite/cdhn2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/cdhn2c08.png -------------------------------------------------------------------------------- /priv/pngsuite/cdsn2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/cdsn2c08.png -------------------------------------------------------------------------------- /priv/pngsuite/cdun2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/cdun2c08.png -------------------------------------------------------------------------------- /priv/pngsuite/ch1n3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/ch1n3p04.png -------------------------------------------------------------------------------- /priv/pngsuite/ch2n3p08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/ch2n3p08.png -------------------------------------------------------------------------------- /priv/pngsuite/cm0n0g04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/cm0n0g04.png -------------------------------------------------------------------------------- /priv/pngsuite/cm7n0g04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/cm7n0g04.png -------------------------------------------------------------------------------- /priv/pngsuite/cm9n0g04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/cm9n0g04.png -------------------------------------------------------------------------------- /priv/pngsuite/cs3n2c16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/cs3n2c16.png -------------------------------------------------------------------------------- /priv/pngsuite/cs3n3p08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/cs3n3p08.png -------------------------------------------------------------------------------- /priv/pngsuite/cs5n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/cs5n2c08.png -------------------------------------------------------------------------------- /priv/pngsuite/cs5n3p08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/cs5n3p08.png -------------------------------------------------------------------------------- /priv/pngsuite/cs8n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/cs8n2c08.png -------------------------------------------------------------------------------- /priv/pngsuite/cs8n3p08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/cs8n3p08.png -------------------------------------------------------------------------------- /priv/pngsuite/ct0n0g04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/ct0n0g04.png -------------------------------------------------------------------------------- /priv/pngsuite/ct1n0g04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/ct1n0g04.png -------------------------------------------------------------------------------- /priv/pngsuite/ctzn0g04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/ctzn0g04.png -------------------------------------------------------------------------------- /priv/pngsuite/f00n0g08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/f00n0g08.png -------------------------------------------------------------------------------- /priv/pngsuite/f00n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/f00n2c08.png -------------------------------------------------------------------------------- /priv/pngsuite/f01n0g08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/f01n0g08.png -------------------------------------------------------------------------------- /priv/pngsuite/f01n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/f01n2c08.png -------------------------------------------------------------------------------- /priv/pngsuite/f02n0g08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/f02n0g08.png -------------------------------------------------------------------------------- /priv/pngsuite/f02n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/f02n2c08.png -------------------------------------------------------------------------------- /priv/pngsuite/f03n0g08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/f03n0g08.png -------------------------------------------------------------------------------- /priv/pngsuite/f03n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/f03n2c08.png -------------------------------------------------------------------------------- /priv/pngsuite/f04n0g08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/f04n0g08.png -------------------------------------------------------------------------------- /priv/pngsuite/f04n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/f04n2c08.png -------------------------------------------------------------------------------- /priv/pngsuite/g03n0g16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/g03n0g16.png -------------------------------------------------------------------------------- /priv/pngsuite/g03n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/g03n2c08.png -------------------------------------------------------------------------------- /priv/pngsuite/g03n3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/g03n3p04.png -------------------------------------------------------------------------------- /priv/pngsuite/g04n0g16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/g04n0g16.png -------------------------------------------------------------------------------- /priv/pngsuite/g04n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/g04n2c08.png -------------------------------------------------------------------------------- /priv/pngsuite/g04n3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/g04n3p04.png -------------------------------------------------------------------------------- /priv/pngsuite/g05n0g16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/g05n0g16.png -------------------------------------------------------------------------------- /priv/pngsuite/g05n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/g05n2c08.png -------------------------------------------------------------------------------- /priv/pngsuite/g05n3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/g05n3p04.png -------------------------------------------------------------------------------- /priv/pngsuite/g07n0g16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/g07n0g16.png -------------------------------------------------------------------------------- /priv/pngsuite/g07n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/g07n2c08.png -------------------------------------------------------------------------------- /priv/pngsuite/g07n3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/g07n3p04.png -------------------------------------------------------------------------------- /priv/pngsuite/g10n0g16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/g10n0g16.png -------------------------------------------------------------------------------- /priv/pngsuite/g10n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/g10n2c08.png -------------------------------------------------------------------------------- /priv/pngsuite/g10n3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/g10n3p04.png -------------------------------------------------------------------------------- /priv/pngsuite/g25n0g16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/g25n0g16.png -------------------------------------------------------------------------------- /priv/pngsuite/g25n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/g25n2c08.png -------------------------------------------------------------------------------- /priv/pngsuite/g25n3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/g25n3p04.png -------------------------------------------------------------------------------- /priv/pngsuite/oi1n0g16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/oi1n0g16.png -------------------------------------------------------------------------------- /priv/pngsuite/oi1n2c16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/oi1n2c16.png -------------------------------------------------------------------------------- /priv/pngsuite/oi2n0g16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/oi2n0g16.png -------------------------------------------------------------------------------- /priv/pngsuite/oi2n2c16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/oi2n2c16.png -------------------------------------------------------------------------------- /priv/pngsuite/oi4n0g16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/oi4n0g16.png -------------------------------------------------------------------------------- /priv/pngsuite/oi4n2c16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/oi4n2c16.png -------------------------------------------------------------------------------- /priv/pngsuite/oi9n0g16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/oi9n0g16.png -------------------------------------------------------------------------------- /priv/pngsuite/oi9n2c16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/oi9n2c16.png -------------------------------------------------------------------------------- /priv/pngsuite/pp0n2c16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/pp0n2c16.png -------------------------------------------------------------------------------- /priv/pngsuite/pp0n6a08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/pp0n6a08.png -------------------------------------------------------------------------------- /priv/pngsuite/ps1n0g08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/ps1n0g08.png -------------------------------------------------------------------------------- /priv/pngsuite/ps1n2c16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/ps1n2c16.png -------------------------------------------------------------------------------- /priv/pngsuite/ps2n0g08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/ps2n0g08.png -------------------------------------------------------------------------------- /priv/pngsuite/ps2n2c16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/ps2n2c16.png -------------------------------------------------------------------------------- /priv/pngsuite/s01i3p01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/s01i3p01.png -------------------------------------------------------------------------------- /priv/pngsuite/s01n3p01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/s01n3p01.png -------------------------------------------------------------------------------- /priv/pngsuite/s02i3p01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/s02i3p01.png -------------------------------------------------------------------------------- /priv/pngsuite/s02n3p01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/s02n3p01.png -------------------------------------------------------------------------------- /priv/pngsuite/s03i3p01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/s03i3p01.png -------------------------------------------------------------------------------- /priv/pngsuite/s03n3p01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/s03n3p01.png -------------------------------------------------------------------------------- /priv/pngsuite/s04i3p01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/s04i3p01.png -------------------------------------------------------------------------------- /priv/pngsuite/s04n3p01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/s04n3p01.png -------------------------------------------------------------------------------- /priv/pngsuite/s05i3p02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/s05i3p02.png -------------------------------------------------------------------------------- /priv/pngsuite/s05n3p02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/s05n3p02.png -------------------------------------------------------------------------------- /priv/pngsuite/s06i3p02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/s06i3p02.png -------------------------------------------------------------------------------- /priv/pngsuite/s06n3p02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/s06n3p02.png -------------------------------------------------------------------------------- /priv/pngsuite/s07i3p02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/s07i3p02.png -------------------------------------------------------------------------------- /priv/pngsuite/s07n3p02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/s07n3p02.png -------------------------------------------------------------------------------- /priv/pngsuite/s08i3p02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/s08i3p02.png -------------------------------------------------------------------------------- /priv/pngsuite/s08n3p02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/s08n3p02.png -------------------------------------------------------------------------------- /priv/pngsuite/s09i3p02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/s09i3p02.png -------------------------------------------------------------------------------- /priv/pngsuite/s09n3p02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/s09n3p02.png -------------------------------------------------------------------------------- /priv/pngsuite/s32i3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/s32i3p04.png -------------------------------------------------------------------------------- /priv/pngsuite/s32n3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/s32n3p04.png -------------------------------------------------------------------------------- /priv/pngsuite/s33i3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/s33i3p04.png -------------------------------------------------------------------------------- /priv/pngsuite/s33n3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/s33n3p04.png -------------------------------------------------------------------------------- /priv/pngsuite/s34i3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/s34i3p04.png -------------------------------------------------------------------------------- /priv/pngsuite/s34n3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/s34n3p04.png -------------------------------------------------------------------------------- /priv/pngsuite/s35i3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/s35i3p04.png -------------------------------------------------------------------------------- /priv/pngsuite/s35n3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/s35n3p04.png -------------------------------------------------------------------------------- /priv/pngsuite/s36i3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/s36i3p04.png -------------------------------------------------------------------------------- /priv/pngsuite/s36n3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/s36n3p04.png -------------------------------------------------------------------------------- /priv/pngsuite/s37i3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/s37i3p04.png -------------------------------------------------------------------------------- /priv/pngsuite/s37n3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/s37n3p04.png -------------------------------------------------------------------------------- /priv/pngsuite/s38i3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/s38i3p04.png -------------------------------------------------------------------------------- /priv/pngsuite/s38n3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/s38n3p04.png -------------------------------------------------------------------------------- /priv/pngsuite/s39i3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/s39i3p04.png -------------------------------------------------------------------------------- /priv/pngsuite/s39n3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/s39n3p04.png -------------------------------------------------------------------------------- /priv/pngsuite/s40i3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/s40i3p04.png -------------------------------------------------------------------------------- /priv/pngsuite/s40n3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/s40n3p04.png -------------------------------------------------------------------------------- /priv/pngsuite/tbbn1g04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/tbbn1g04.png -------------------------------------------------------------------------------- /priv/pngsuite/tbbn2c16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/tbbn2c16.png -------------------------------------------------------------------------------- /priv/pngsuite/tbbn3p08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/tbbn3p08.png -------------------------------------------------------------------------------- /priv/pngsuite/tbgn2c16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/tbgn2c16.png -------------------------------------------------------------------------------- /priv/pngsuite/tbgn3p08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/tbgn3p08.png -------------------------------------------------------------------------------- /priv/pngsuite/tbrn2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/tbrn2c08.png -------------------------------------------------------------------------------- /priv/pngsuite/tbwn1g16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/tbwn1g16.png -------------------------------------------------------------------------------- /priv/pngsuite/tbwn3p08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/tbwn3p08.png -------------------------------------------------------------------------------- /priv/pngsuite/tbyn3p08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/tbyn3p08.png -------------------------------------------------------------------------------- /priv/pngsuite/tp0n1g08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/tp0n1g08.png -------------------------------------------------------------------------------- /priv/pngsuite/tp0n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/tp0n2c08.png -------------------------------------------------------------------------------- /priv/pngsuite/tp0n3p08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/tp0n3p08.png -------------------------------------------------------------------------------- /priv/pngsuite/tp1n3p08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/tp1n3p08.png -------------------------------------------------------------------------------- /priv/pngsuite/z00n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/z00n2c08.png -------------------------------------------------------------------------------- /priv/pngsuite/z03n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/z03n2c08.png -------------------------------------------------------------------------------- /priv/pngsuite/z06n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/z06n2c08.png -------------------------------------------------------------------------------- /priv/pngsuite/z09n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/z09n2c08.png -------------------------------------------------------------------------------- /priv/pngsuite/pngsuite_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/pngsuite_logo.png -------------------------------------------------------------------------------- /priv/pngsuite/corrupted/x00n0g01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/corrupted/x00n0g01.png -------------------------------------------------------------------------------- /priv/pngsuite/corrupted/xcrn0g04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/corrupted/xcrn0g04.png -------------------------------------------------------------------------------- /priv/pngsuite/corrupted/xlfn0g04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mochi/erl_img/HEAD/priv/pngsuite/corrupted/xlfn0g04.png -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: erlang 2 | notifications: 3 | email: false 4 | otp_release: 5 | - R14B03 6 | - R14B04 7 | - R15B01 -------------------------------------------------------------------------------- /src/dbg.hrl: -------------------------------------------------------------------------------- 1 | 2 | -ifdef(debug). 3 | -define(dbg(Fmt,Args), io:format((Fmt),(Args))). 4 | -else. 5 | -define(dbg(Fmt,Args), ok). 6 | -endif. 7 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | % -*- mode: erlang -*- 2 | {erl_opts, [debug_info]}. 3 | {cover_enabled, true}. 4 | {eunit_opts, [verbose, {report,{eunit_surefire,[{dir,"."}]}}]}. 5 | -------------------------------------------------------------------------------- /src/erl_img.app.src: -------------------------------------------------------------------------------- 1 | {application, erl_img, 2 | [{description, "erl_img"}, 3 | {vsn, "1.7.0"}, 4 | {modules, []}, 5 | {registered, []}, 6 | {applications, [kernel, stdlib, crypto]}]}. 7 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | REBAR=./rebar 2 | 3 | .PHONY: all clean test 4 | 5 | all: 6 | @$(REBAR) compile 7 | 8 | edoc: 9 | @$(REBAR) doc 10 | 11 | test: 12 | @rm -rf .eunit 13 | @mkdir -p .eunit 14 | @$(REBAR) skip_deps=true eunit 15 | 16 | clean: 17 | @$(REBAR) clean 18 | 19 | build_plt: 20 | @$(REBAR) build-plt 21 | 22 | dialyzer: 23 | @$(REBAR) dialyze 24 | -------------------------------------------------------------------------------- /src/image_undef.erl: -------------------------------------------------------------------------------- 1 | %%% File : image_undef.erl 2 | %%% Author : Tony Rogvall 3 | %%% Description : Undefined format catch module 4 | %%% Created : 5 Mar 2003 by Tony Rogvall 5 | 6 | -module(image_undef). 7 | 8 | -include_lib("erl_img.hrl"). 9 | -include("api.hrl"). 10 | 11 | magic(_) -> false. 12 | 13 | mime_type() -> "". 14 | 15 | extensions() -> []. 16 | 17 | read_info(_Fd) -> 18 | {error, bad_magic}. 19 | 20 | write_info(_Fd, _IMG) -> 21 | {error, bad_image}. 22 | 23 | read(_Fd,_IMG) -> 24 | {error, bad_image}. 25 | 26 | read(_Fd,_IMG,_PixFun,_PixSt) -> 27 | {error, bad_image}. 28 | 29 | write(_Fd,_IMG) -> 30 | {error, bad_image}. 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/video_mpeg.erl: -------------------------------------------------------------------------------- 1 | %%% File : video_mpeg.erl 2 | %%% Author : Tony Rogvall 3 | %%% Description : MPEG image processing 4 | %%% Created : 5 Mar 2003 by Tony Rogvall 5 | 6 | -module(video_mpeg). 7 | 8 | -include("erl_img.hrl"). 9 | -include("api.hrl"). 10 | 11 | magic(_) -> false. 12 | 13 | mime_type() -> "video/mpeg". 14 | 15 | extensions() -> [".mpg", ".mpeg"]. 16 | 17 | read_info(_Fd) -> 18 | {error, bad_magic}. 19 | 20 | 21 | write_info(_Fd, _IMG) -> 22 | {error, bad_image}. 23 | 24 | read(_Fd,_IMG) -> 25 | {error, bad_image}. 26 | 27 | read(_Fd,_IMG,_PixFun,_PixSt) -> 28 | {error, bad_image}. 29 | 30 | write(_Fd,_IMG) -> 31 | {error, bad_image}. 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/api.hrl: -------------------------------------------------------------------------------- 1 | %% 2 | %% These are the functions that make the image api 3 | %% 4 | 5 | 6 | %% magic(Bin) -> true | false 7 | -export([magic/1]). 8 | 9 | %% mime_type() -> 10 | -export([mime_type/0]). 11 | 12 | %% extensions() -> [ "." ... "."] 13 | -export([extensions/0]). 14 | 15 | %% read_info(Fd) -> {ok, #erl_img} | Error 16 | -export([read_info/1]). 17 | 18 | %% write_info(Fd, #erl_img) -> ok | Error 19 | -export([write_info/2]). 20 | 21 | %% read(Fd, #erl_img) -> {ok, #erl_img'} | Error 22 | %% read(Fd, #erl_img, RowFun, State) -> {ok, #erl_img'} | Error 23 | %% RowFun = fun(#erl_img, Row, RowNumber, RowFormat, St) -> St' 24 | %% 25 | -export([read/2, read/4]). 26 | 27 | %% write(Fd, #erl_img) -> ok | Error 28 | -export([write/2]). 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /scripts/mktab_lzw.escript: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env escript 2 | %% -*- erlang -*- 3 | 4 | %%% File : mktab.erl 5 | %%% Author : Tony Rogvall 6 | %%% Description : reverse bits table 7 | %%% Created : 2 Apr 2003 by Tony Rogvall 8 | 9 | %% Used to generate the table in lzw.erl 10 | bin(N) -> 11 | bin(N,8,[]). 12 | 13 | bin(_N, 0, Acc) -> 14 | [$2,$#|Acc]; 15 | bin(N, I, Acc) -> 16 | bin(N bsr 1, I-1, [$0+(N band 1) | Acc]). 17 | 18 | 19 | revbits(N) -> 20 | revbits(N,8,0). 21 | 22 | revbits(_N,0,A) -> 23 | A; 24 | revbits(N,I,A) -> 25 | revbits(N bsr 1,I-1,(A bsl 1) bor (N band 1)). 26 | 27 | main([]) -> 28 | lists:foreach(fun(I) -> 29 | io:format(" ~s -> ~s;\n", 30 | [bin(I), bin(revbits(I))]) 31 | end, lists:seq(0, 255)). 32 | -------------------------------------------------------------------------------- /src/jpeg.hrl: -------------------------------------------------------------------------------- 1 | -ifndef(__IMAGE_JPG_HRL__). 2 | -define(__IMAGE_JPG_HRL__, true). 3 | 4 | -define(M_SOF0, 16#FFC0). %% Start Of Frame N 5 | -define(M_SOF1, 16#FFC1). %% N indicates which compression process 6 | -define(M_SOF2, 16#FFC2). %% Only SOF0-SOF2 are now in common use 7 | -define(M_SOF3, 16#FFC3). 8 | -define(M_DHT, 16#FFC4). 9 | -define(M_SOF5, 16#FFC5). %% NB: codes C4 and CC are NOT SOF markers 10 | -define(M_SOF6, 16#FFC6). 11 | -define(M_SOF7, 16#FFC7). 12 | -define(M_SOF9, 16#FFC9). 13 | -define(M_SOF10,16#FFCA). 14 | -define(M_SOF11,16#FFCB). 15 | -define(M_SOF13,16#FFCD). 16 | -define(M_SOF14,16#FFCE). 17 | -define(M_SOF15,16#FFCF). 18 | -define(M_SOI, 16#FFD8). %% Start Of Image (beginning of datastream) 19 | -define(M_EOI, 16#FFD9). %% End Of Image (end of datastream) 20 | -define(M_SOS, 16#FFDA). %% Start Of Scan (begins compressed data) 21 | -define(M_DQT, 16#FFDB). 22 | -define(M_JFIF, 16#FFE0). %% Jfif marker 23 | -define(M_APP1, 16#FFE1). %% Exif marker 24 | -define(M_COM, 16#FFFE). %% COMment 25 | 26 | -endif. 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/adler.erl: -------------------------------------------------------------------------------- 1 | %%% File : adler.erl 2 | %%% Author : Tony Rogvall 3 | %%% Description : adler checksum 4 | %%% Created : 8 Apr 2003 by Tony Rogvall 5 | 6 | -module(adler). 7 | 8 | -export([adler32/1, adler32/2]). 9 | 10 | -define(BASE, 65521). %% largest prime smaller than 65536 11 | -define(NMAX, 5552). 12 | 13 | adler32(Bin) -> 14 | adler32(0, Bin). 15 | 16 | 17 | adler32(Adler, Bin) -> 18 | S1 = Adler band 16#ffff, 19 | S2 = (Adler bsr 16) band 16#ffff, 20 | adler_n(Adler, 0, S1, S2, Bin, 0). 21 | 22 | adler_n(Adler, Offs, S1, S2, Bin, 0) -> 23 | S11 = S1 rem ?BASE, 24 | S12 = S2 rem ?BASE, 25 | Len = size(Bin) - Offs, 26 | K = if Len < ?NMAX -> Len; true -> ?NMAX end, 27 | if K == 0 -> 28 | (S2 bsl 16) bor S1; 29 | true -> 30 | adler_n(Adler, Offs, S11, S12, Bin, K) 31 | end; 32 | adler_n(Adler, Offs, S1, S2, Bin, I) when I >= 8 -> 33 | <<_:Offs/binary, C0,C1,C2,C3,C4,C5,C6,C7,_/binary>> = Bin, 34 | S11 = S1+C0, 35 | adler_n(Adler, Offs+8, 36 | S11+C1+C2+C3+C4+C5+C6+C7, 37 | S2+8*S11+7*C1+6*C2+5*C3+4*C4+3*C5+2*C6+C7, Bin, I-8); 38 | adler_n(Adler, Offs, S1, S2, Bin, I) -> 39 | <<_:Offs/binary, C0,_/binary>> = Bin, 40 | adler_n(Adler, Offs+1, S1+C0, S2+S1+C0, Bin, I-1). 41 | -------------------------------------------------------------------------------- /src/gpsinfo.hrl: -------------------------------------------------------------------------------- 1 | -ifndef(__IMAGE_GPSINFO_HRL__). 2 | -define(__IMAGE_GPSINFO_HRL__, true). 3 | 4 | %% http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/GPS.html 5 | 6 | -define(GPSVersionID, 16#0000). 7 | -define(GPSLatitudeRef, 16#0001). 8 | -define(GPSLatitude, 16#0002). 9 | -define(GPSLongitudeRef, 16#0003). 10 | -define(GPSLongitude, 16#0004). 11 | -define(GPSAltitudeRef, 16#0005). 12 | -define(GPSAltitude, 16#0006). 13 | -define(GPSTimeStamp, 16#0007). 14 | -define(GPSSatellites, 16#0008). 15 | -define(GPSStatus, 16#0009). 16 | -define(GPSMeasureMode, 16#000a). 17 | -define(GPSDOP, 16#000b). 18 | -define(GPSSpeedRef, 16#000c). 19 | -define(GPSSpeed, 16#000d). 20 | -define(GPSTrackRef, 16#000e). 21 | -define(GPSTrack, 16#000f). 22 | -define(GPSImgDirectionRef, 16#0010). 23 | -define(GPSImgDirection, 16#0011). 24 | -define(GPSMapDatum, 16#0012). 25 | -define(GPSDestLatitudeRef, 16#0013). 26 | -define(GPSDestLatitude, 16#0014). 27 | -define(GPSDestLongitudeRef, 16#0015). 28 | -define(GPSDestLongitude, 16#0016). 29 | -define(GPSDestBearingRef, 16#0017). 30 | -define(GPSDestBearing, 16#0018). 31 | -define(GPSDestDistanceRef, 16#0019). 32 | -define(GPSDestDistance, 16#001a). 33 | -define(GPSProcessingMethod, 16#001b). 34 | -define(GPSAreaInformation, 16#001c). 35 | -define(GPSDateStamp, 16#001d). 36 | -define(GPSDifferential, 16#001e). 37 | 38 | -endif. 39 | -------------------------------------------------------------------------------- /src/exif.hrl: -------------------------------------------------------------------------------- 1 | -ifndef(__IMAGE_EXIF_HRL__). 2 | -define(__IMAGE_EXIF_HRL__, true). 3 | 4 | %% http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/EXIF.html 5 | 6 | %% SubExif IFD usage 7 | -define(ExposureTime, 16#829a). 8 | -define(FNumber, 16#829d). 9 | -define(ExposureProgram, 16#8822). 10 | -define(ISOSpeedRatings, 16#8827). 11 | -define(CFARepeatPatternDim, 16#828d ). 12 | 13 | -define(ExifVersion, 16#9000). 14 | -define(DateTimeOriginal, 16#9003). 15 | -define(DateTimeDigitized, 16#9004). 16 | -define(ComponentsConfiguration, 16#9101). 17 | -define(CompressedBitsPerPixel, 16#9102). 18 | -define(ShutterSpeedValue, 16#9201). 19 | -define(ApertureValue, 16#9202). 20 | -define(BrightnessValue, 16#9203). 21 | -define(ExposureBiasValue, 16#9204). 22 | -define(MaxApertureValue, 16#9205). 23 | -define(SubjectDistance, 16#9206). 24 | -define(MeteringMode, 16#9207). 25 | -define(LightSource, 16#9208). 26 | -define(Flash, 16#9209). 27 | -define(FocalLength, 16#920a). 28 | -define(MakerNote, 16#927c). 29 | -define(UserComment, 16#9286). 30 | -define(SubsecTime, 16#9290). 31 | -define(SubsecTimeOriginal, 16#9291). 32 | -define(SubsecTimeDigitized, 16#9292). 33 | -define(FlashPixVersion, 16#a000). 34 | -define(ColorSpace, 16#a001). 35 | -define(ExifImageWidth, 16#a002). 36 | -define(ExifImageHeight, 16#a003). 37 | -define(RelatedSoundFile, 16#a004). 38 | -define(ExifInteroperabilityOffset, 16#a005 ). 39 | -define(FocalPlaneXResolution, 16#a20e). 40 | -define(FocalPlaneYResolution, 16#a20f). 41 | -define(FocalPlaneResolutionUnit, 16#a210). 42 | -define(ExposureIndex, 16#a215). 43 | -define(SensingMethod, 16#a217). 44 | -define(FileSource, 16#a300). 45 | -define(SceneType, 16#a301). 46 | -define(CFAPattern, 16#a302). 47 | 48 | %% Interoperability IFD 49 | 50 | -define(InteroperabilityIndex, 16#0001 ). 51 | -define(InteroperabilityVersion, 16#0002 ). 52 | -define(RelatedImageFileFormat, 16#1000 ). 53 | -define(RelatedImageWidth, 16#1001 ). 54 | -define(RelatedImageLength, 16#1002 ). %% a.k.a. RelatedImageHeight 55 | 56 | %% Other tags 57 | 58 | %% http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/gps.html 59 | %% http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/GPS.html 60 | -ifndef(GPSInfo). 61 | -define(GPSInfo, 16#8825). 62 | -endif. 63 | 64 | %% FIXME add OLYMP 65 | 66 | 67 | -endif. 68 | 69 | 70 | -------------------------------------------------------------------------------- /src/exif.erl: -------------------------------------------------------------------------------- 1 | %%% File : img_exif.erl 2 | %%% Author : Tony Rogvall 3 | %%% Description : Utils for decoding Exif tags 4 | %%% Created : 6 Mar 2003 by Tony Rogvall 5 | 6 | -module(exif). 7 | 8 | -export([decode_tag/1]). 9 | 10 | -include("exif.hrl"). 11 | 12 | decode_tag(Tag) when is_integer(Tag) -> 13 | case Tag of 14 | ?ExposureTime -> 'ExposureTime'; 15 | ?FNumber -> 'FNumber'; 16 | ?ExposureProgram -> 'ExposureProgram'; 17 | ?ISOSpeedRatings -> 'ISOSpeedRatings'; 18 | ?ExifVersion -> 'ExifVersion'; 19 | ?DateTimeOriginal -> 'DateTimeOriginal'; 20 | ?DateTimeDigitized -> 'DateTimeDigitized'; 21 | ?ComponentsConfiguration -> 'ComponentsConfiguration'; 22 | ?CompressedBitsPerPixel -> 'CompressedBitsPerPixel'; 23 | ?ShutterSpeedValue -> 'ShutterSpeedValue'; 24 | ?ApertureValue -> 'ApertureValue'; 25 | ?BrightnessValue -> 'BrightnessValue'; 26 | ?ExposureBiasValue -> 'ExposureBiasValue'; 27 | ?MaxApertureValue -> 'MaxApertureValue'; 28 | ?SubjectDistance -> 'SubjectDistance'; 29 | ?MeteringMode -> 'MeteringMode'; 30 | ?LightSource -> 'LightSource'; 31 | ?Flash -> 'Flash'; 32 | ?FocalLength -> 'FocalLength'; 33 | ?MakerNote -> 'MakerNote'; 34 | ?UserComment -> 'UserComment'; 35 | ?SubsecTime -> 'SubsecTime'; 36 | ?SubsecTimeOriginal -> 'SubsecTimeOriginal'; 37 | ?SubsecTimeDigitized -> 'SubsecTimeDigitized'; 38 | ?FlashPixVersion -> 'FlashPixVersion'; 39 | ?ColorSpace -> 'ColorSpace'; 40 | ?ExifImageWidth -> 'ExifImageWidth'; 41 | ?ExifImageHeight -> 'ExifImageHeight'; 42 | ?RelatedSoundFile -> 'RelatedSoundFile'; 43 | ?ExifInteroperabilityOffset -> 'ExifInteroperabilityOffset'; 44 | ?FocalPlaneXResolution -> 'FocalPlaneXResolution'; 45 | ?FocalPlaneYResolution -> 'FocalPlaneYResolution'; 46 | ?FocalPlaneResolutionUnit -> 'FocalPlaneResolutionUnit'; 47 | ?ExposureIndex -> 'ExposureIndex'; 48 | ?SensingMethod -> 'SensingMethod'; 49 | ?FileSource -> 'FileSource'; 50 | ?SceneType -> 'SceneType'; 51 | ?CFAPattern -> 'CFAPattern'; 52 | ?InteroperabilityIndex -> 'InteroperabilityIndex'; 53 | ?InteroperabilityVersion -> 'InteroperabilityVersion'; 54 | ?RelatedImageFileFormat -> 'RelatedImageFileFormat'; 55 | ?RelatedImageWidth -> 'RelatedImageWidth'; 56 | ?RelatedImageLength -> 'RelatedImageLength'; 57 | Tag -> Tag 58 | end; 59 | decode_tag(Tag) -> 60 | Tag. 61 | -------------------------------------------------------------------------------- /include/erl_img.hrl: -------------------------------------------------------------------------------- 1 | %% 2 | %% image format A R G B 3 | %% pixel data 16 16 16 16 4 | %% 5 | -ifndef(__ERL_IMG_HRL__). 6 | -define(__ERL_IMG_HRL__, true). 7 | 8 | -define(IMAGE_JPEG, image_jpeg). 9 | -define(IMAGE_TIFF, image_tiff). 10 | -define(IMAGE_GIF, image_gif). 11 | -define(IMAGE_PNG, image_png). 12 | -define(IMAGE_BMP, image_bmp). 13 | -define(IMAGE_X_XPIXMAP, image_x_xpixmap). 14 | -define(IMAGE_UNDEF, image_undef). 15 | -define(IMAGE_TGA, image_tga). 16 | -define(VIDEO_MPEG, video_mpeg). 17 | 18 | -define(PAD_Len(L,A), (((A)-((L) rem (A))) rem (A))). 19 | 20 | -define(PAD_Len8(L), ((8 - ((L) band 7)) band 7)). 21 | 22 | -define(PAD(L,A), 23 | case ?PAD4_Len(L,A) of 24 | 0 -> <<>>; 25 | 1 -> <<0>>; 26 | 2 -> <<0,0>>; 27 | 3 -> <<0,0,0>>; 28 | 4 -> <<0,0,0,0>>; 29 | 5 -> <<0,0,0,0,0>>; 30 | 6 -> <<0,0,0,0,0,0>>; 31 | 7 -> <<0,0,0,0,0,0,0>>; 32 | N -> list_to_binary(lists:duplicate(N,0)) 33 | end). 34 | 35 | -define(IMAGE_TYPES, [?IMAGE_JPEG, 36 | ?IMAGE_TIFF, 37 | ?IMAGE_GIF, 38 | ?IMAGE_PNG, 39 | ?IMAGE_BMP, 40 | ?IMAGE_X_XPIXMAP, 41 | ?IMAGE_TGA, 42 | ?VIDEO_MPEG]). 43 | 44 | -record(erl_pixmap, 45 | { 46 | top = 0, 47 | left = 0, 48 | width = 0, 49 | height = 0, 50 | palette, %% list [{R,G,B}] 51 | format, %% pixmap format 52 | attributes = [], %% extension codes 53 | pixels = [] %% [ {Ri,binary(Row)} ] 54 | }). 55 | 56 | 57 | -record(erl_image, 58 | { 59 | type, %% module name of image handler 60 | name, %% Image name (no path) 61 | filename, %% Full filename 62 | size, %% File size 63 | extension, %% extension used 64 | mtime, %% file creation date {{YYYY,MM,DD},{HH,MM,SS}} 65 | itime, %% image creation date {{YYYY,MM,DD},{HH,MM,SS}} 66 | comment = "", %% image comment (if present) 67 | format, %% pixel format: 68 | %% gray4, gray8, 69 | %% palette4, palette8 70 | %% b8g8r8 r8g8b8 r8g8b8a8 71 | width, %% Image width 72 | height, %% Image height 73 | depth, %% Image depth 74 | bytes_pp = 3, %% bytes per pixel 75 | alignment = 1, 76 | attributes = [], %% list of attributes [{atom(Key),term(Value)}] 77 | order, %% sample order left_to_right or right_to_left 78 | palette, %% list [{R,G,B}] 79 | pixmaps = [] %% [#erl_pixmap] 80 | }). 81 | 82 | -endif. 83 | -------------------------------------------------------------------------------- /src/image_bmp.erl: -------------------------------------------------------------------------------- 1 | %%% File : image_bmp.erl 2 | %%% Author : Tony Rogvall 3 | %%% Description : BMP Files 4 | %%% Created : 5 Mar 2003 by Tony Rogvall 5 | 6 | -module(image_bmp). 7 | 8 | -include_lib("erl_img.hrl"). 9 | -include("api.hrl"). 10 | -include("dbg.hrl"). 11 | 12 | -import(lists, [reverse/1]). 13 | 14 | 15 | -define(BMP_HEADER(FileSz, Offset), 16 | $B:8,$M:8, 17 | FileSz:32/little, 18 | 0:16, 0:16, 19 | Offset:32/little). 20 | 21 | -define(BMP_INFO(HSize,Width,Height,Planes,BitCount,Compression, 22 | ImageSize,XRes,YRes,ColorsUsed,ImportantColors), 23 | HSize:32/little, 24 | Width:32/little, 25 | Height:32/little, 26 | Planes:16/little, 27 | BitCount:16/little, 28 | Compression:32/little, 29 | ImageSize:32/little, 30 | XRes:32/little, 31 | YRes:32/little, 32 | ColorsUsed:32/little, 33 | ImportantColors:32/little). 34 | 35 | magic(<<$B,$M, _/binary>>) -> true; 36 | magic(_) -> false. 37 | 38 | mime_type() -> "image/bmp". 39 | 40 | extensions() -> [".bmp" ]. 41 | 42 | 43 | read_info(Fd) -> 44 | case file:read(Fd, 54) of 45 | {ok, << ?BMP_HEADER(_Size,_Offset), 46 | ?BMP_INFO(_,Width,Height,Planes,_BitCount, 47 | Compression,_,_,_,_,_) >> } -> 48 | {ok, #erl_image { type = ?MODULE, 49 | width = Width, 50 | height = Height, 51 | depth = Planes, 52 | format = b8g8r8, 53 | bytes_pp = 3, 54 | alignment = 4, 55 | order = left_to_right, 56 | attributes = [{'Compression',Compression}] 57 | }}; 58 | {ok, _} -> 59 | {error, bad_magic}; 60 | Error -> 61 | Error 62 | end. 63 | 64 | 65 | write_info(_Fd, _IMG) -> 66 | ok. 67 | 68 | read(Fd, IMG, RowFun, St0) -> 69 | file:position(Fd, 54), 70 | case read_pixels(Fd, IMG, RowFun, St0) of 71 | {ok,PIX} -> 72 | {ok, IMG#erl_image { pixmaps = [PIX] }}; 73 | Error -> Error 74 | end. 75 | 76 | %% load image 77 | read(Fd, IMG) -> 78 | read(Fd, IMG, 79 | fun(_, Row, Ri, St) -> 80 | ?dbg("bmp: load row ~p\n", [Ri]), 81 | [{Ri,Row}|St] end, 82 | []). 83 | 84 | %% save image 85 | write(_Fd, _IMG) -> 86 | ok. 87 | 88 | %% Read all rows 89 | read_pixels(Fd, IMG, RowFun, St0) -> 90 | Width = IMG#erl_image.width, 91 | Height = IMG#erl_image.height, 92 | RowLength = Width*3 + ?PAD_Len(Width*3, 4), 93 | PIX = #erl_pixmap { width = Width, height = Height, 94 | format = IMG#erl_image.format }, 95 | read_pixels(Fd, PIX, 0, Height, RowLength, RowFun, St0). 96 | 97 | 98 | read_pixels(_Fd, PIX, NRows, NRows, _BytesPerRow, _RowFun, St) -> 99 | {ok,PIX#erl_pixmap { pixels = St }}; 100 | read_pixels(Fd, PIX, Ri, NRows, BytesPerRow, RowFun, St) -> 101 | case file:read(Fd, BytesPerRow) of 102 | {ok,Row} -> 103 | St1 = RowFun(PIX, Row, Ri, St), 104 | read_pixels(Fd, PIX, Ri+1, NRows, BytesPerRow, RowFun, St1); 105 | Error -> 106 | Error 107 | end. 108 | -------------------------------------------------------------------------------- /src/tiff.hrl: -------------------------------------------------------------------------------- 1 | -ifndef(__IMAGE_TIFF_HRL__). 2 | -define(__IMAGE_TIFF_HRL__, true). 3 | 4 | -define(BYTE, 1). %% 8-bit unsigned integer 5 | -define(ASCII, 2). 6 | -define(SHORT, 3). %% 16-bit unsigned integer 7 | -define(LONG, 4). %% 32-bit unsigned integer 8 | -define(RATIONAL, 5). %% 32-bit numerator and 32-bit denominator 9 | -define(SBYTE, 6). 10 | -define(UNDEFINED, 7). 11 | -define(SSHORT, 8). 12 | -define(SLONG, 9). 13 | -define(SRATIONAL, 10). 14 | -define(FLOAT, 11). 15 | -define(DOUBLE, 12). 16 | 17 | -record(tiff_entry, { 18 | ifd, %% ifd number I.J.K (reversed) 19 | tag, %% tag name (atom) or number 20 | type, %% value type 21 | endian, %% little or big 22 | value, %% value 23 | offs %% offset to value start (strings,binaries...) 24 | }). 25 | 26 | %% TIFF tags 27 | -define(NewSubfileType,254). %% long 28 | -define(SubfileType, 255). %% short 29 | -define(ImageWidth, 256). %% short/long 30 | -define(ImageLength, 257). %% short/long 31 | -define(BitsPerSample,258). %% short N=SamplesPerPixel 32 | -define(Compression, 259). %% short N=1 33 | %% 1 no-compression (packed) 34 | %% 2 ccitt huffman run-length encofing 35 | %% 3 Group 3 Fax 36 | %% 4 Group 4 Fax 37 | %% 5 LZW 38 | %% 6 JPEG 39 | %% 32773 Packbits simple run-length 40 | -define(PhotoMetricInterpretation, 262). %% short 41 | -define(Threshholding, 263). %% short N=1 42 | -define(CellWidth, 264). %% short N=1 43 | -define(CellLength, 265). %% short N=1 44 | -define(FillOrder, 266). %% short N=1 45 | -define(DocumentName, 269). %% ASCII Document name 46 | -define(ImageDescription,270). %% ASCII subject of image 47 | -define(Make, 271). %% Scanner Manufaturer 48 | -define(Model, 272). %% Scanner Model 49 | -define(StripOffset, 273). %% short or long 50 | -define(Orientation, 274). %% short 51 | -define(SamplesPerPixel, 277). %% short 52 | -define(RowsPerStrip, 278). %% short or long 53 | -define(StripByteCounts, 279). %% short ot long 54 | -define(MinSampleValue,280). %% short 55 | -define(MaxSampleValue,281). %% short 56 | -define(XResolution, 282). %% rational 57 | -define(YResolution, 283). %% rational 58 | -define(PlanarConfiguration,284). %% short N=1 59 | -define(PageName, 285). %% ASCII page name 60 | -define(XPosition, 286). %% rational 61 | -define(YPosition, 287). %% rational 62 | -define(FreeOffsets, 288). %% long 63 | -define(FreeByteCounts, 289). %% long 64 | -define(GrayResponseUnit,290). %% short N=1 65 | -define(GrayResponseCurve,291). %% short N=2^BitsPerSample 66 | -define(T4Options, 292). %% long N=1 67 | -define(T6Options, 293). %% long N=1 68 | -define(ResolutionUnit, 296). %% short 69 | -define(PageNumber, 297). %% short N=2 70 | -define(TransferFunction,301). %% short 71 | -define(Software, 305). %% ASCII software used 72 | -define(DateTime, 306). %% ASCII YYYY:MM:DD:HH:MM:SS 73 | -define(Artist, 315). %% ASCII 74 | -define(HostComputer,316). %% ASCII computer/os where image was created 75 | -define(Predictor, 317). %% short 76 | -define(WhitePoint, 318). %% rational 77 | -define(PrimaryChromaticities,319). %% rational 78 | -define(ColorMap, 320). %% short N=3*(2^BitsPerSample) 79 | -define(HalftoneHints,321). %% short 80 | -define(TileWidth, 322). %% short/long 81 | -define(TileLength, 323). %% short/long 82 | -define(TileOffset, 324). %% long 83 | -define(TileByteCounts, 325). %% short/long 84 | -define(InkSet, 332). %% short N=1 85 | -define(InkNames, 333). %% ASCII 86 | -define(NumberOfInks,334). %% short 87 | -define(DotRange, 336). %% byte or short 88 | -define(TargetPrinter,337). %% ASCII 89 | -define(ExtraSamples,338). %% Short N=m 90 | -define(SampleFormat, 339). %% short 91 | -define(SMinSampleValue,340). %% Any 92 | -define(SMaxSampleValue,341). %% Any 93 | -define(TransferRange, 342). %% short N=6 94 | -define(JPEGProc, 512). %% short N=1 95 | -define(JPEGInterchangeFormat, 513). 96 | -define(JPEGInterchangeFormatLength, 514). 97 | -define(JPEGRestartInterval, 515). 98 | -define(JPEGLosslessPredictors, 517). 99 | -define(JPEGPointTransforms, 518). 100 | -define(JPEGQTables, 519). 101 | -define(JPEGDCTables, 520). 102 | -define(JPEGACTables, 5201). 103 | -define(YCbCrCoefficients, 529). 104 | -define(YCbCrSampling, 530). 105 | -define(YCbCrPositioning, 531). 106 | -define(ReferenceBlackWhite, 532). 107 | -define(Copyright, 33432). %% ASCII 108 | -define(ExifOffset, 16#8769). %% unsigned long1 Offset to Exif Sub IFD 109 | 110 | -ifndef(GPSInfo). 111 | -define(GPSInfo, 16#8825). 112 | -endif. 113 | 114 | -endif. 115 | -------------------------------------------------------------------------------- /src/image_tga.erl: -------------------------------------------------------------------------------- 1 | %%% File : image_tga.erl 2 | %%% Author : Bob Ippolito 3 | %%% Description : TGA image processing 4 | %%% Created : 27 Feb 2011 by Bob Ippolito 5 | 6 | %% Not a lot of practical use for TGA files but it's a dead simple format to 7 | %% implement. I decided to implement it to be able to render out images when 8 | %% testing. 9 | 10 | -module(image_tga). 11 | 12 | -include("erl_img.hrl"). 13 | 14 | -include("api.hrl"). 15 | 16 | %% -define(debug, true). 17 | -include("dbg.hrl"). 18 | 19 | -import(lists, [reverse/1]). 20 | 21 | 22 | read_magic(<<0, 23 | 0:7, ColorMapType:1, 24 | ImageType, 25 | ColorMapStart:16/little-signed-integer, 26 | ColorMapLength:16/little-signed-integer, 27 | ColorMapBits, 28 | XStart:16/little-signed-integer, 29 | YStart:16/little-signed-integer, 30 | Width:16/little-signed-integer, 31 | Height:16/little-signed-integer, 32 | Depth, 33 | 0:1, 0:1, VFlip:1, HFlip:1, 0:4, 34 | _/binary>>) 35 | when (Width > 0) andalso (Height > 0) andalso 36 | (XStart =:= 0) andalso (YStart =:= 0) andalso 37 | (Depth =:= 1 orelse Depth =:= 8 orelse 38 | Depth =:= 16 orelse Depth =:= 24 orelse 39 | Depth =:= 32) andalso 40 | %% Only 24-bit and 32-bit uncompressed left-to-right supported 41 | (HFlip =:= 0) andalso 42 | (ImageType =:= 2) andalso (ColorMapType =:= 0) andalso 43 | (ColorMapStart =:= 0) andalso (ColorMapLength =:= 0) andalso 44 | (ColorMapBits =:= 0) -> 45 | #erl_image{type=?MODULE, 46 | width=Width, 47 | height=Height, 48 | %% We transcode bgr to rgb when reading/writing 49 | format=case Depth of 50 | 24 -> r8g8b8; 51 | 32 -> r8g8b8a8 52 | end, 53 | order=left_to_right, 54 | depth=8, 55 | alignment=case VFlip of 56 | 0 -> 4; 57 | 1 -> 1 58 | end, 59 | bytes_pp=Depth div 8}. 60 | 61 | magic(B) -> 62 | try 63 | _ = read_magic(B), 64 | true 65 | catch _:_ -> 66 | false 67 | end. 68 | 69 | mime_type() -> "image/tga". 70 | 71 | extensions() -> [ ".tga" ]. 72 | 73 | read_info(Fd) -> 74 | try 75 | {ok, B} = file:read(Fd, 18), 76 | {ok, read_magic(B)} 77 | catch _:_ -> 78 | {error, bad_magic} 79 | end. 80 | 81 | write_info(Fd, #erl_image{width=Width, height=Height, 82 | bytes_pp=BPP, format=Format}) -> 83 | file:write(Fd, 84 | <<0, 0:7, 0:1, 85 | 2, 86 | 0:16/little-signed-integer, 87 | 0:16/little-signed-integer, 88 | 0, 89 | 0:16/little-signed-integer, 90 | 0:16/little-signed-integer, 91 | Width:16/little-signed-integer, 92 | Height:16/little-signed-integer, 93 | (bpp(BPP, Format) * 8), 94 | 0:1, 0:1, 1:1, 0:1, 0:4>>). 95 | 96 | read(Fd,IMG,RowFun,St0) -> 97 | file:position(Fd, 18), 98 | case read_pixels(Fd, IMG, RowFun, St0) of 99 | {ok, PIX} -> 100 | {ok, IMG#erl_image{pixmaps=[PIX]}}; 101 | Error -> 102 | Error 103 | end. 104 | 105 | %% Read all rows 106 | read_pixels(Fd, #erl_image{bytes_pp=BPP, width=Width, 107 | height=Height, format=Format}, 108 | RowFun, St0) -> 109 | RowLength = Width * BPP, 110 | PIX = #erl_pixmap { width = Width, height = Height, 111 | format = Format }, 112 | read_pixels(Fd, PIX, 0, Height, RowLength, RowFun, St0). 113 | 114 | 115 | read_pixels(_Fd, PIX, NRows, NRows, _BytesPerRow, _RowFun, St) -> 116 | {ok, PIX#erl_pixmap{pixels = St}}; 117 | read_pixels(Fd, PIX, Ri, NRows, BytesPerRow, RowFun, St) -> 118 | case file:read(Fd, BytesPerRow) of 119 | {ok,Row} -> 120 | St1 = RowFun(PIX, Row, Ri, St), 121 | read_pixels(Fd, PIX, Ri+1, NRows, BytesPerRow, RowFun, St1); 122 | Error -> 123 | Error 124 | end. 125 | 126 | rgb_to_bgr(Row) -> 127 | << <> || <> <= Row >>. 128 | 129 | rgba_to_bgra(Row) -> 130 | << <> || <> <= Row >>. 131 | 132 | bgr_to_rgb(Row) -> 133 | << <> || <> <= Row >>. 134 | 135 | bgra_to_rgba(Row) -> 136 | << <> || <> <= Row >>. 137 | 138 | bpp(_Bpp, r8g8b8a8) -> 139 | 4; 140 | bpp(_Bpp, r8g8b8) -> 141 | 3; 142 | bpp(Bpp, _Format) -> 143 | Bpp. 144 | 145 | read(Fd, IMG=#erl_image{bytes_pp=BPP}) -> 146 | read(Fd, IMG, 147 | case BPP of 148 | 3 -> 149 | fun(_, Row, Ri, St) -> 150 | ?dbg("tga: load bgr row ~p\n", [Ri]), 151 | [{Ri,bgr_to_rgb(Row)}|St] 152 | end; 153 | 4 -> 154 | fun(_, Row, Ri, St) -> 155 | ?dbg("tga: load bgra row ~p\n", [Ri]), 156 | [{Ri,bgra_to_rgba(Row)}|St] 157 | end 158 | end, 159 | []). 160 | 161 | 162 | write(Fd, IMG=#erl_image{pixmaps=[PM], bytes_pp=BPP, format=Format}) -> 163 | write_info(Fd, IMG), 164 | RowFun = case bpp(BPP, Format) of 165 | 3 -> fun rgb_to_bgr/1; 166 | 4 -> fun rgba_to_bgra/1 167 | end, 168 | write_rows(Fd, RowFun, lists:sort(PM#erl_pixmap.pixels)). 169 | 170 | write_rows(_Fd, _RowFun, []) -> 171 | ok; 172 | write_rows(Fd, RowFun, [{_Num, Row} | Rest]) -> 173 | ok = file:write(Fd, RowFun(Row)), 174 | write_rows(Fd, RowFun, Rest). 175 | 176 | 177 | -------------------------------------------------------------------------------- /src/erl_img.erl: -------------------------------------------------------------------------------- 1 | %%% File : erl_img.erl 2 | %%% Author : Tony Rogvall 3 | %%% Description : Image processing stuff 4 | %%% Created : 5 Mar 2003 by Tony Rogvall 5 | 6 | -module(erl_img). 7 | 8 | -include_lib("kernel/include/file.hrl"). 9 | 10 | -include_lib("erl_img.hrl"). 11 | 12 | -export([magic_info/1]). 13 | -export([mime_type/1]). 14 | -export([dir_info/1]). 15 | -export([read_file_info/1]). 16 | -export([read/2, write/2]). 17 | -export([load/1, save/1, save/2, to_binary/1]). 18 | -export([attribute/2, attribute/3, set_attribute/3]). 19 | -export([extensions/1]). 20 | -export([write_info/3]). 21 | -export([hex32/1, hex16/1, hex8/1]). 22 | 23 | hex32(X) -> 24 | hex8(X bsr 24) ++ hex8(X bsr 16) ++ hex8(X bsr 8) ++ hex8(X). 25 | 26 | hex16(X) -> 27 | hex8(X bsr 8) ++ hex8(X). 28 | 29 | %% convert a hex byte into two ascii letters 30 | hex8(X) -> 31 | [nib((X bsr 4) band 16#f),nib(X band 16#f)]. 32 | 33 | nib(N) when N =< 9 -> N+$0; 34 | nib(N) -> (N-10)+$A. 35 | 36 | %% Read magic info check MAGIC type and width and height (depth) 37 | %% of image 38 | 39 | %% Check a header of least 64 bytes 40 | magic([Type|Ts], Bin) -> 41 | case apply(Type, magic, [Bin]) of 42 | true -> {true, Type }; 43 | false -> magic(Ts, Bin) 44 | end; 45 | magic([], _Bin) -> 46 | false. 47 | 48 | 49 | %% Read file mtime information 50 | file_info(File, _IMG) -> 51 | case file:read_file_info(File) of 52 | {ok, Info} when Info#file_info.type == regular, 53 | Info#file_info.size > 0 -> 54 | {ok, {Info#file_info.mtime,Info#file_info.size}}; 55 | {ok, _Other} -> 56 | {error, bad_file}; 57 | Error -> 58 | Error 59 | end. 60 | 61 | 62 | 63 | read_magic_info(Fd) -> 64 | file:position(Fd, 0), 65 | case file:read(Fd, 64) of 66 | {ok, Bin} -> 67 | case magic(?IMAGE_TYPES, Bin) of 68 | {true, Type} -> 69 | read_info(Type, Fd); 70 | false -> 71 | {error, not_supported} 72 | end; 73 | Error -> 74 | Error 75 | end. 76 | 77 | 78 | magic_info(File) -> 79 | case file:open(File,[raw,binary,read]) of 80 | {ok,Fd} -> 81 | Res = read_magic_info(Fd), 82 | file:close(Fd), 83 | case Res of 84 | {ok,IMG} -> 85 | {ok,IMG#erl_image { filename = File, 86 | name = filename:basename(File) }}; 87 | Error -> 88 | Error 89 | end; 90 | Error -> Error 91 | end. 92 | 93 | 94 | 95 | mime_type(IMG) -> 96 | apply(IMG#erl_image.type, mime_type, []). 97 | 98 | extensions(IMG) -> 99 | apply(IMG#erl_image.type, extensions, []). 100 | 101 | 102 | read_file_info(File) -> 103 | case file_info(File, #erl_image { }) of 104 | {ok, {MTime,Size}} -> 105 | case magic_info(File) of 106 | {ok,IMG} -> 107 | {ok,IMG#erl_image { mtime = MTime, 108 | size = Size}}; 109 | Error -> 110 | Error 111 | end; 112 | Error -> Error 113 | end. 114 | 115 | 116 | load(File) -> 117 | case file:open(File, [raw, binary, read]) of 118 | {ok,Fd} -> 119 | Res = case read_magic_info(Fd) of 120 | {ok, IMG} -> 121 | read(Fd, IMG#erl_image { filename = File }); 122 | Error -> 123 | Error 124 | end, 125 | file:close(Fd), 126 | Res; 127 | Error -> Error 128 | end. 129 | 130 | save(IMG) -> 131 | save(IMG#erl_image.filename, IMG). 132 | 133 | save(File, IMG) -> 134 | case file:open(File, [raw, binary, write]) of 135 | {ok,Fd} -> 136 | Res = write(Fd, IMG), 137 | file:close(Fd), 138 | Res; 139 | Error -> 140 | Error 141 | end. 142 | 143 | to_binary(IMG) -> 144 | case file:open(<<>>, [ram, binary, write]) of 145 | {ok,Fd} -> 146 | ok = write(Fd, IMG), 147 | case ram_file:get_file_close(Fd) of 148 | {ok, Data} -> 149 | {ok,Data}; 150 | Error -> 151 | Error 152 | end; 153 | Error -> 154 | Error 155 | end. 156 | 157 | 158 | 159 | 160 | read_info(Type, Fd) -> 161 | file:position(Fd, 0), 162 | apply(Type, read_info, [Fd]). 163 | 164 | write_info(Type, Fd, IMG) -> 165 | file:position(Fd, 0), 166 | apply(Type, write_info, [Fd,IMG]). 167 | 168 | read(Fd,IMG) -> 169 | file:position(Fd, 0), 170 | apply(IMG#erl_image.type, read, [Fd,IMG]). 171 | 172 | write(Fd,IMG) -> 173 | file:position(Fd, 0), 174 | apply(IMG#erl_image.type, write, [Fd,IMG]). 175 | 176 | 177 | 178 | attribute(IMG, Key) -> 179 | {value, {_, Value}} = lists:keysearch(Key, 1, IMG#erl_image.attributes), 180 | Value. 181 | 182 | attribute(IMG, Key, Default) -> 183 | case lists:keysearch(Key, 1, IMG#erl_image.attributes) of 184 | {value, {_, Value}} -> Value; 185 | false -> Default 186 | end. 187 | 188 | set_attribute(IMG, Key, Value) -> 189 | As = IMG#erl_image.attributes, 190 | As1 = case lists:keysearch(Key, 1, As) of 191 | false -> [{Key,Value}|As]; 192 | {value,_} -> 193 | lists:keyreplace(Key, 1, As, {Key,Value}) 194 | end, 195 | IMG#erl_image { attributes = As1 }. 196 | 197 | 198 | dir_info(Dir) -> 199 | case file:list_dir(Dir) of 200 | {ok, Listing} -> 201 | dir_list(Listing,Dir); 202 | Error -> 203 | Error 204 | end. 205 | 206 | dir_list([File|Fs], Dir) -> 207 | case read_file_info(filename:join(Dir, File)) of 208 | {ok,IMG} -> 209 | [IMG|dir_list(Fs, Dir)]; 210 | _Error -> 211 | dir_list(Fs, Dir) 212 | end; 213 | dir_list([], _Dir) -> 214 | []. 215 | -------------------------------------------------------------------------------- /src/image_jpeg.erl: -------------------------------------------------------------------------------- 1 | %%% File : image_jpg.erl 2 | %%% Author : Tony Rogvall 3 | %%% Description : JPG image processing (Exif/JPG files) 4 | %%% Created : 5 Mar 2003 by Tony Rogvall 5 | 6 | -module(image_jpeg). 7 | 8 | -include_lib("erl_img.hrl"). 9 | 10 | -include("jpeg.hrl"). 11 | -include("tiff.hrl"). 12 | -include("exif.hrl"). 13 | 14 | -include("api.hrl"). 15 | %% -define(debug, true). 16 | -include("dbg.hrl"). 17 | 18 | %% YCbCr => RGB 19 | -define(R(Y,Cb,Cr), (Y + (1.402)*((Cr)-128))). 20 | -define(G(Y,Cb,Cr), (Y - 0.34414*((Cb)-128) - 0.71414*((Cr)-128))). 21 | -define(B(Y,Cb,Cr), (Y + 1.772*(Cb-128))). 22 | 23 | %% RGB => YCbCr 24 | -define(Y(R,G,B), (0.299*(R) + 0.587*(G) + 0.114*(B))). 25 | -define(Cb(R,G,B), (0.1687*(R) - 0.3313*(G) + 0.5*(B) + 128)). 26 | -define(Cr(R,G,B), (0.5*R - 0.4187*(G) - 0.0813*(B) + 128)). 27 | 28 | 29 | 30 | magic(<>) -> true; 31 | magic(<>) -> true; 32 | magic(<>) -> true; 33 | magic(<>) -> true; 34 | magic(<>) -> true; 35 | magic(<>) -> true; 36 | magic(<>) -> true; 37 | magic(_) -> false. 38 | 39 | mime_type() -> "image/jpeg". 40 | 41 | extensions() -> [".jpeg", ".jpg"]. 42 | 43 | 44 | read_info(Fd) -> 45 | case file:read(Fd, 2) of 46 | {ok, <>} -> 47 | read_sections(Fd, 48 | #erl_image { type = ?MODULE, 49 | order = upper_left 50 | }); 51 | {ok,_} -> 52 | {error, bad_magic}; 53 | Error -> Error 54 | end. 55 | 56 | write_info(_Fd, _IMG) -> 57 | ok. 58 | 59 | 60 | read(_Fd,IMG) -> 61 | {ok,IMG}. 62 | 63 | read(_Fd,IMG,_RowFun,_St0) -> 64 | {ok,IMG}. 65 | 66 | write(_Fd,_IMG) -> 67 | ok. 68 | 69 | 70 | read_sections(Fd, IMG) -> 71 | case file:read(Fd, 4) of 72 | eof -> 73 | {ok,IMG}; 74 | {ok,<>} -> 75 | read_section(Fd,Marker,Len-2,IMG); 76 | {ok,_} -> 77 | {error, bad_file}; 78 | Error -> Error 79 | end. 80 | 81 | read_section(Fd,Marker,Len,IMG) -> 82 | if Marker == ?M_SOS -> {ok,IMG}; 83 | Marker == ?M_EOI -> {ok,IMG}; 84 | Marker == ?M_COM -> 85 | case file:read(Fd, Len) of 86 | {ok,Bin} -> 87 | read_sections(Fd, IMG#erl_image {comment= 88 | binary_to_list(Bin)}); 89 | _Error -> 90 | {error, bad_file} 91 | end; 92 | Marker == ?M_APP1 -> 93 | case file:read(Fd, Len) of 94 | {ok,<<"Exif",0,0,Bin/binary>>} -> 95 | read_sections(Fd, process_exif(Bin,IMG)); 96 | {ok,_} -> 97 | read_sections(Fd, IMG) 98 | end; 99 | Marker == ?M_SOF0; 100 | Marker == ?M_SOF1; 101 | Marker == ?M_SOF2; 102 | Marker == ?M_SOF3; 103 | Marker == ?M_SOF5; 104 | Marker == ?M_SOF6; 105 | Marker == ?M_SOF7; 106 | Marker == ?M_SOF9; 107 | Marker == ?M_SOF10; 108 | Marker == ?M_SOF11; 109 | Marker == ?M_SOF13; 110 | Marker == ?M_SOF14; 111 | Marker == ?M_SOF15 -> 112 | case file:read(Fd, Len) of 113 | {ok,Bin} -> 114 | read_sections(Fd, process_sofn(Bin,IMG)); 115 | Error -> 116 | Error 117 | end; 118 | true -> 119 | file:position(Fd, {cur,Len}), 120 | read_sections(Fd, IMG) 121 | end. 122 | 123 | process_sofn(<>, IMG) -> 124 | IMG#erl_image { depth = Depth, 125 | height = Height, 126 | width = Width }. 127 | 128 | collect_maker(_Fd, _T, St) -> 129 | {ok, St}. 130 | 131 | collect_exif(Fd, T, St) -> 132 | _Key = exif:decode_tag(T#tiff_entry.tag), 133 | ?dbg("EXIF(~s) ~p ~p ~p\n", 134 | [T#tiff_entry.ifd,_Key,T#tiff_entry.type, T#tiff_entry.value]), 135 | case T#tiff_entry.tag of 136 | ?ExifInteroperabilityOffset -> 137 | [Offset] = T#tiff_entry.value, 138 | %% could be handle by a collect_interop? 139 | case image_tiff:scan_ifd(Fd, [$0,$.|T#tiff_entry.ifd], 140 | Offset, T#tiff_entry.endian, 141 | fun collect_exif/3, St) of 142 | {ok, St1} -> 143 | St1; 144 | _Error -> 145 | St 146 | end; 147 | ?MakerNote -> 148 | case collect_maker(Fd, T, St) of 149 | {ok,St1} -> 150 | St1; 151 | _Error -> 152 | St 153 | end; 154 | _ -> 155 | St 156 | end. 157 | 158 | 159 | %% Image info collector functions 160 | collect_tiff(Fd, T, St) -> 161 | Key = image_tiff:decode_tag(T#tiff_entry.tag), 162 | ?dbg("TIFF(~s) ~p ~p ~p\n", 163 | [T#tiff_entry.ifd,Key,T#tiff_entry.type, T#tiff_entry.value]), 164 | case T#tiff_entry.tag of 165 | ?ImageWidth -> 166 | [Width] = T#tiff_entry.value, 167 | St#erl_image { width = Width }; 168 | ?ImageLength -> 169 | [Length] = T#tiff_entry.value, 170 | St#erl_image { height = Length }; 171 | ?BitsPerSample -> 172 | Bs = T#tiff_entry.value, 173 | St#erl_image { depth = lists:sum(Bs) }; 174 | ?ImageDescription -> 175 | [Value] = T#tiff_entry.value, 176 | St#erl_image { comment = Value }; 177 | ?DateTime -> 178 | [Value] = T#tiff_entry.value, 179 | case string:tokens(Value, ": ") of 180 | [YYYY,MM,DD,H,M,S] -> 181 | DateTime = {{list_to_integer(YYYY), 182 | list_to_integer(MM), 183 | list_to_integer(DD)}, 184 | {list_to_integer(H), 185 | list_to_integer(M), 186 | list_to_integer(S)}}, 187 | St#erl_image { itime = DateTime}; 188 | _ -> 189 | St 190 | end; 191 | ?ExifOffset -> 192 | [Offset] = T#tiff_entry.value, 193 | case image_tiff:scan_ifd(Fd, [$0,$.|T#tiff_entry.ifd], 194 | Offset, T#tiff_entry.endian, 195 | fun collect_exif/3, St) of 196 | {ok, St1} -> 197 | St1; 198 | _Error -> 199 | St 200 | end; 201 | _ -> 202 | Value = T#tiff_entry.value, 203 | As = St#erl_image.attributes, 204 | St#erl_image { attributes = [{Key,Value}|As]} 205 | end. 206 | 207 | process_exif(Bin, IMG) -> 208 | case image_tiff:scan_binary(Bin, fun collect_tiff/3, IMG) of 209 | {ok, IMG1} -> 210 | IMG1; 211 | _Error -> 212 | IMG 213 | end. 214 | -------------------------------------------------------------------------------- /src/image_x_xpixmap.erl: -------------------------------------------------------------------------------- 1 | %%% File : image_x_xpixmap.erl 2 | %%% Author : Tony Rogvall 3 | %%% Description : XPM image processing 4 | %%% Created : 5 Mar 2003 by Tony Rogvall 5 | 6 | -module(image_x_xpixmap). 7 | 8 | -include_lib("erl_img.hrl"). 9 | -include("api.hrl"). 10 | -include("dbg.hrl"). 11 | 12 | -export([scan_xpm/1]). 13 | 14 | -import(lists,[reverse/1]). 15 | -define(upper(C), (C)>=$A, (C)=<$Z). 16 | -define(lower(C), (C)>=$a, (C)=<$z). 17 | -define(digit(C), (C)>=$0, (C)=<$9). 18 | 19 | -define(ID1(C), ?upper(C); ?lower(C); C==$_). 20 | -define(ID(C), ?upper(C); ?lower(C); ?digit(C); C==$_). 21 | 22 | %% Read magic info check MAGIC type and width and height (depth) 23 | %% of image 24 | 25 | magic(<<"/* XPM */\n", _Data/binary>>) -> true; 26 | magic(_) -> false. 27 | 28 | mime_type() -> "image/x-xpixmap". 29 | 30 | extensions() -> [".xpm"]. 31 | 32 | 33 | read_info(Fd) -> 34 | case file:read(Fd, 10) of 35 | {ok,Bin} -> 36 | case magic(Bin) of 37 | true -> 38 | case scan_xpm(Fd,fun({string,_}) -> false; 39 | (_) -> true 40 | end) of 41 | {ok,Ts} -> 42 | [{string,Fmt}|_] = reverse(Ts), 43 | {ok,[Width,Height,_NColors,_CharPerPixel],_Rest} = 44 | io_lib:fread("~d ~d ~d ~d",Fmt), 45 | {ok,#erl_image { 46 | type = ?MODULE, 47 | width = Width, 48 | height = Height, 49 | format = palette8, 50 | order = left_to_right, 51 | depth = 8 }}; 52 | Error -> 53 | Error 54 | end; 55 | false -> 56 | {error, bad_magic} 57 | end; 58 | Error -> 59 | Error 60 | end. 61 | 62 | 63 | write_info(_Fd, _IMG) -> 64 | ok. 65 | 66 | 67 | read(Fd, IMG, RowFun, St0) -> 68 | case scan_xpm(Fd) of 69 | {ok,Ts} -> 70 | case parse_xpm(Ts) of 71 | {ok,Strings} -> 72 | tr_rows(IMG,Strings,RowFun, St0); 73 | Error -> Error 74 | end; 75 | Error -> Error 76 | end. 77 | 78 | read(Fd,IMG) -> 79 | read(Fd, IMG, 80 | fun(_, Row, Ri, St) -> 81 | ?dbg("xpm: read row ~p\n", [Ri]), 82 | [{Ri,Row}|St] end, 83 | []). 84 | 85 | write(_Fd,_IMG) -> 86 | ok. 87 | 88 | tr_rows(IMG,[Fmt|Data],RowFun,St) -> 89 | {ok,[Width,Height,NColors,CharPerPixel],_Rest} = 90 | io_lib:fread("~d ~d ~d ~d",Fmt), 91 | {Colors,Data1} = tr_colors(Data,NColors,[],CharPerPixel), 92 | ColorMap = color_tab(Colors), 93 | IMG1 = IMG#erl_image { palette = ColorMap }, 94 | PIX0 = #erl_pixmap { height = Height, 95 | width = Width, 96 | format = IMG1#erl_image.format, 97 | palette = ColorMap }, 98 | case tr_pixels(Data1,Colors,PIX0,0,Height,RowFun,St) of 99 | {ok, PIX1} -> 100 | {ok, IMG1#erl_image { pixmaps = [ PIX1]}}; 101 | Error -> 102 | Error 103 | end. 104 | 105 | tr_pixels([Line|Ls],Colors,PIX,Ri,NRows,RowFun,St0) when Ri =/= NRows -> 106 | Row = tr_line(Line, [], Colors), 107 | St1 = RowFun(PIX,Row,Ri,St0), 108 | tr_pixels(Ls, Colors, PIX, Ri+1, NRows, RowFun, St1); 109 | tr_pixels([],_Colors,PIX,NRows,NRows,_RowFun,St0) -> 110 | {ok, PIX#erl_pixmap { pixels = St0 }}; 111 | tr_pixels(_, _Colors, _IMG, _, _, _RowFun, _St0) -> 112 | {error, bad_data}. 113 | 114 | 115 | color_tab([{_, " c #"++Hex} | Cs]) -> 116 | Color = hex_to_integer(Hex), 117 | R = (Color bsr 16) band 16#ff, 118 | G = (Color bsr 8) band 16#ff, 119 | B = (Color bsr 0) band 16#ff, 120 | [ {R,G,B} | color_tab(Cs)]; 121 | color_tab([{_, " c "++Name} | Cs]) -> 122 | [ Name | color_tab(Cs)]; 123 | color_tab([]) -> []. 124 | 125 | hex_to_integer(Cs) -> 126 | hex_to_integer(Cs, 0). 127 | 128 | hex_to_integer([C|Cs], N) when C >= $0, C =< $9 -> 129 | hex_to_integer(Cs, (N bsl 4) + (C - $0)); 130 | hex_to_integer([C|Cs], N) when C >= $a, C =< $f -> 131 | hex_to_integer(Cs, (N bsl 4) + ((C - $a)+10)); 132 | hex_to_integer([C|Cs], N) when C >= $A, C =< $F -> 133 | hex_to_integer(Cs, (N bsl 4) + ((C - $A)+10)); 134 | hex_to_integer([], N) -> N. 135 | 136 | 137 | %% 138 | %% Translate a line into color index binary line 139 | %% 140 | 141 | tr_line([], Acc, _Colors) -> 142 | list_to_binary(reverse(Acc)); 143 | tr_line(Line, Acc, Colors) -> 144 | {Line1,Index} = tr_pixel(Colors, Line, 1), 145 | tr_line(Line1, [Index|Acc], Colors). 146 | 147 | tr_pixel([{Chars,_}|Cs], Line, I) -> 148 | case lists:prefix(Chars,Line) of 149 | true -> {lists:nthtail(length(Chars),Line), I}; 150 | false -> tr_pixel(Cs, Line, I+1) 151 | end. 152 | 153 | tr_colors(Data, 0, Acc, _Cpp) -> 154 | {reverse(Acc), Data}; 155 | tr_colors([Color|Cs], N, Acc, Cpp) -> 156 | {Chars,Spec} = get_color(Color,[],Cpp), 157 | tr_colors(Cs, N-1, [{Chars,Spec}|Acc], Cpp). 158 | 159 | get_color(Cs, Acc, 0) -> {reverse(Acc), Cs}; 160 | get_color([C|Cs],Acc,N) -> get_color(Cs,[C|Acc],N-1). 161 | 162 | 163 | %% parse the xpm file 164 | parse_xpm([{id,"static"},{id,"char"},'*',{id,_Name},'[', ']','=', '{' | Ts]) -> 165 | parse_xpm(Ts, []); 166 | parse_xpm(Ts) -> 167 | {error,{bad_format,Ts}}. 168 | 169 | parse_xpm([{string,Str},',' | Ts], Acc) -> 170 | parse_xpm(Ts, [Str|Acc]); 171 | parse_xpm([{string,Str},'}',';'], Acc) -> 172 | {ok, reverse([Str|Acc])}; 173 | parse_xpm(['}', ';'], Acc) -> 174 | {ok, reverse(Acc)}; 175 | parse_xpm(Ts,_) -> 176 | {error, {bad_format,Ts}}. 177 | 178 | scan_xpm(Fd) -> 179 | scan_xpm(Fd, fun(_) -> true end). 180 | 181 | scan_xpm(Fd, While) -> 182 | more(Fd, fun(Cs) -> scan_xpm(Cs,Fd,[],While) end, fun() -> {ok,[]} end). 183 | 184 | %% scan xpm (C subset) tokens 185 | scan_xpm([C|Cs], Fd, Ts, W) -> 186 | if C == $\s; C == $\t; C == $\n; C == $\r -> 187 | scan_xpm(Cs,Fd,Ts,W); 188 | C == $" -> 189 | scan_string(Cs,Fd,Ts,W); 190 | ?ID1(C) -> 191 | scan_id(Cs,Fd,[C],Ts,W); 192 | C == $/ -> 193 | scan_comment(Cs,Fd,Ts,W); 194 | C == $*; C == $[; C == $]; C == ${; C == $}; 195 | C == $,; C == $=; C == $; -> 196 | next(Cs,Fd,list_to_atom([C]),Ts,W); 197 | true -> 198 | {error,{bad_format,[C|Cs]}} 199 | end; 200 | scan_xpm([],Fd,Ts,W) -> 201 | more(Fd, fun(Cs) -> scan_xpm(Cs,Fd,Ts,W) end, 202 | fun() -> {ok,reverse(Ts)} end). 203 | 204 | 205 | %% seen / 206 | scan_comment([$*|Cs],Fd,Ts,W) -> scan_c1(Cs,Fd,Ts,W); 207 | scan_comment([C|Cs],Fd,Ts,W) -> scan_xpm([C|Cs],Fd,['/'|Ts],W); 208 | scan_comment([],Fd,Ts,W) -> 209 | more(Fd, fun(Cs) -> scan_comment(Cs,Fd,Ts,W) end, 210 | fun() -> {error,bad_format} end). 211 | %% seen /* 212 | scan_c1([$*|Cs],Fd,Ts,W) -> scan_c2(Cs,Fd,Ts,W); 213 | scan_c1([_|Cs],Fd,Ts,W) -> scan_c1(Cs,Fd,Ts,W); 214 | scan_c1([],Fd,Ts,W) -> 215 | more(Fd, fun(Cs) -> scan_c1(Cs,Fd,Ts,W) end,fun() -> {error,comment} end). 216 | 217 | %% seen /*... * 218 | scan_c2([$/|Cs],Fd,Ts,W) -> scan_xpm(Cs,Fd,Ts,W); 219 | scan_c2([C|Cs],Fd,Ts,W) -> scan_c1([C|Cs],Fd,Ts,W); 220 | scan_c2([],Fd,Ts,W) -> 221 | more(Fd, fun(Cs) -> scan_c2(Cs,Fd,Ts,W) end,fun() -> {error,comment} end). 222 | 223 | scan_string(Cs,Fd,Ts,W) -> 224 | scan_string(Cs,Fd,[],Ts,W). 225 | 226 | scan_string([$\\,C | Cs],Fd,Str,Ts,W) -> 227 | scan_string(Cs,Fd,[C,$\\|Str],Ts,W); 228 | scan_string([$" | Cs],Fd,Str,Ts,W) -> 229 | next(Cs,Fd,{string,reverse(Str)},Ts,W); 230 | scan_string([C|Cs],Fd, Str, Ts,W) -> 231 | scan_string(Cs,Fd,[C|Str],Ts,W); 232 | scan_string(Cs1,Fd,Str,Ts,W) -> 233 | more(Fd, fun(Cs) -> scan_string(Cs1++Cs,Fd,Str,Ts,W) end, 234 | fun() -> {error,string} end). 235 | 236 | scan_id([C|Cs],Fd,ID,Ts,W) when ?ID(C) -> 237 | scan_id(Cs,Fd,[C|ID],Ts,W); 238 | scan_id([],Fd,ID,Ts,W) -> 239 | more(Fd, fun(Cs) -> scan_id(Cs,Fd,ID,Ts,W) end, 240 | fun() -> next([],Fd,{id,reverse(ID)},Ts,W) end); 241 | scan_id(Cs,Fd,ID,Ts,W) -> 242 | next(Cs,Fd,{id,reverse(ID)},Ts,W). 243 | 244 | 245 | next(Cs,Fd,Token,Ts,While) -> 246 | case While(Token) of 247 | true -> scan_xpm(Cs,Fd,[Token|Ts],While); 248 | false -> {ok,reverse([Token|Ts])} 249 | end. 250 | 251 | 252 | more(Fd,Fun,Eof) -> 253 | case file:read(Fd,64) of 254 | {ok,Data} -> 255 | Fun(binary_to_list(Data)); 256 | eof -> 257 | Eof(); 258 | Error -> 259 | Error 260 | end. 261 | -------------------------------------------------------------------------------- /src/image_gif.erl: -------------------------------------------------------------------------------- 1 | %%% File : image_gif.erl 2 | %%% Author : Tony Rogvall 3 | %%% Description : GIF image processing 4 | %%% Created : 5 Mar 2003 by Tony Rogvall 5 | 6 | -module(image_gif). 7 | 8 | -include("erl_img.hrl"). 9 | 10 | -include("api.hrl"). 11 | 12 | %% -define(debug, true). 13 | -include("dbg.hrl"). 14 | 15 | -import(lists, [reverse/1]). 16 | 17 | 18 | -define(APP_EXTENSION, 16#ff). 19 | -define(COM_EXTENSION, 16#fe). 20 | -define(CTL_EXTENSION, 16#f9). 21 | -define(TXT_EXTENSION, 16#01). 22 | 23 | -define(EXTENSION, 16#21). %% $! 24 | -define(IMAGE, 16#2c). %% $, 25 | -define(TRAILER, 16#3b). %% $; 26 | 27 | %% Read magic info check MAGIC type and width and height (depth) 28 | %% of image 29 | -define(MAGIC87, $G,$I,$F,$8,$7,$a). 30 | -define(MAGIC89, $G,$I,$F,$8,$9,$a). 31 | 32 | magic(<>) -> true; 33 | magic(<>) -> true; 34 | magic(_) -> false. 35 | 36 | mime_type() -> "image/gif". 37 | 38 | extensions() -> [ ".gif" ]. 39 | 40 | 41 | read_info(Fd) -> 42 | case file:read(Fd, 10) of 43 | {ok, <>} -> 46 | {ok,#erl_image { type = ?MODULE, 47 | width = Width, 48 | height = Height, 49 | format = palette8, 50 | order = left_to_right, 51 | depth = 8 }}; 52 | {ok, <>} -> 55 | {ok,#erl_image { type = ?MODULE, 56 | width = Width, 57 | height = Height, 58 | format = palette8, 59 | order = left_to_right, 60 | depth = 8 }}; 61 | {ok, _} -> 62 | {error, bad_magic}; 63 | Error -> 64 | Error 65 | end. 66 | 67 | write_info(Fd, IMG) -> 68 | %% Should version be configurable? 69 | file:write(Fd, <>). 72 | 73 | 74 | %% The Grammar. 75 | %% ::= Header * Trailer 76 | %% 77 | %% ::= Logical Screen Descriptor [Global Color Table] 78 | %% 79 | %% ::= | 80 | %% 81 | %% 82 | %% ::= [Graphic Control Extension] 83 | %% 84 | %% ::= | 85 | %% Plain Text Extension 86 | %% 87 | %% ::= Image Descriptor [Local Color Table] Image Data 88 | %% 89 | %% ::= Application Extension | 90 | %% Comment Extension 91 | 92 | read(Fd,IMG,RowFun,St0) -> 93 | file:position(Fd, 6), 94 | case file:read(Fd, 7) of 95 | {ok, <<_Width:16/little, _Hight:16/little, 96 | Map:1, _Cr:3, Sort:1, Pix:3, 97 | Background:8, 98 | AspectRatio:8>>} -> 99 | Palette = read_palette(Fd, Map, Pix+1), 100 | ?dbg("sizeof(palette)=~p Map=~w, Cr=~w, Sort=~w, Pix=~w\n", 101 | [length(Palette),Map,_Cr,Sort,Pix]), 102 | ?dbg("Background=~w, AspectRatio=~w\n", 103 | [Background, AspectRatio]), 104 | As = [{'Background',Background}, 105 | {'AspectRatio',AspectRatio}, 106 | {'Sort',Sort} | IMG#erl_image.attributes], 107 | IMG1 = IMG#erl_image { palette = Palette, attributes = As}, 108 | read_data(Fd, IMG1, RowFun, St0, []); 109 | Error -> 110 | Error 111 | end. 112 | 113 | 114 | read(Fd, IMG) -> 115 | read(Fd, IMG, 116 | fun(_, Row, Ri, St) -> 117 | ?dbg("gif: load row ~p\n", [Ri]), 118 | [{Ri,Row}|St] end, 119 | []). 120 | 121 | 122 | read_data(Fd, IMG, RowFun, St0, As) -> 123 | case file:read(Fd, 1) of 124 | {ok, <>} -> 125 | ?dbg("Extension\n",[]), 126 | read_extension(Fd, IMG, RowFun, St0, As); 127 | {ok, <>} -> 128 | ?dbg("Image\n",[]), 129 | case file:read(Fd, 9) of 130 | {ok, <>} -> 133 | Palette = read_palette(Fd, Map, Pix+1), 134 | As1 = [{'Interlaced', Interlaced}, 135 | {'Sort', Sort} | As], 136 | Pixmap0 = 137 | #erl_pixmap { top = Top, 138 | left = Left, 139 | width = Width, 140 | height = Height, 141 | format = IMG#erl_image.format, 142 | palette = Palette, 143 | attributes = As1 144 | }, 145 | ?dbg("pix=~w, sizeof(palette)=~p\n", [Pix,Palette]), 146 | case read_pixels(Fd,Pixmap0,RowFun,St0, 147 | Width,Height,Interlaced) of 148 | {ok, Pixmap1} -> 149 | %% ?dbg("Pixmap = ~p\n", [Pixmap2]), 150 | Ps = IMG#erl_image.pixmaps ++ [Pixmap1], 151 | read_data(Fd, IMG#erl_image { pixmaps = Ps }, 152 | RowFun, St0, []); 153 | Error -> Error 154 | end; 155 | Error -> Error 156 | end; 157 | {ok, <>} -> 158 | ?dbg("Trailer\n",[]), 159 | {ok, IMG}; 160 | Error -> 161 | Error 162 | end. 163 | 164 | 165 | read_extension(Fd, IMG,RowFun,St0,As) -> 166 | case file:read(Fd, 1) of 167 | {ok,<>} -> 168 | read_comment(Fd, IMG,RowFun,St0,As); 169 | {ok,<>} -> 170 | read_app(Fd, IMG,RowFun,St0,As); 171 | {ok,<>} -> 172 | read_ctl(Fd, IMG,RowFun,St0,As); 173 | {ok,<>} -> 174 | read_txt(Fd, IMG,RowFun,St0,As); 175 | {ok, _} -> 176 | read_blocks(Fd), %% skip 177 | read_data(Fd, IMG,RowFun,St0,As) 178 | end. 179 | 180 | 181 | read_ctl(Fd, IMG,RowFun,St0,As) -> 182 | ?dbg("Control block\n",[]), 183 | case read_block(Fd) of 184 | {ok, <<_:3, DisposalMethod:3, UserInput:1, Transparent:1, 185 | DelayTime:16/unsigned-little, 186 | TransparentColor:8>>} -> 187 | case read_block(Fd) of 188 | terminator -> 189 | As1 = [{'TransparentColor', TransparentColor}, 190 | {'DelayTime', DelayTime}, 191 | {'UserInput', UserInput}, 192 | {'Transparent', Transparent}, 193 | {'DisposalMethod', DisposalMethod} | As], 194 | read_data(Fd,IMG,RowFun,St0,As1); 195 | {ok,_} -> 196 | {error, bad_ctl_block}; 197 | Error -> Error 198 | end; 199 | Error -> Error 200 | end. 201 | 202 | 203 | read_comment(Fd, IMG,RowFun,St0,As) -> 204 | ?dbg("Comment block\n",[]), 205 | case read_blocks(Fd) of 206 | {ok, Comment} -> 207 | read_data(Fd, 208 | IMG#erl_image { comment = binary_to_list(Comment)} 209 | ,RowFun,St0,As); 210 | Error -> Error 211 | end. 212 | 213 | 214 | read_txt(Fd, IMG,RowFun,St0,As) -> 215 | ?dbg("Text block\n",[]), 216 | case read_block(Fd) of 217 | {ok, <>} -> 221 | case read_blocks(Fd) of 222 | {ok,Bin} -> 223 | As1 = 224 | [{'TextGridLeftPosition', GridLeft}, 225 | {'TextGridTopPosition', GridTop}, 226 | {'CharacterCellWidth', CellWidth}, 227 | {'CharacterCellHeight', CellHeight}, 228 | {'TextForegroundColorIndex', Foreground}, 229 | {'TextBackgroundColorIndex', Background}, 230 | {'Text', binary_to_list(Bin)} | As], 231 | read_data(Fd,IMG,RowFun,St0,As1); 232 | Error -> 233 | Error 234 | end; 235 | terminator -> 236 | {error, bad_txt_block}; 237 | Error -> Error 238 | end. 239 | 240 | read_app(Fd, IMG,RowFun,St0,As) -> 241 | ?dbg("Application block\n",[]), 242 | case read_block(Fd) of 243 | {ok, <>} -> 244 | case read_blocks(Fd) of 245 | {ok, AppData} -> 246 | As1 = 247 | [{'ApplicationIdentifier', binary_to_list(Ident)}, 248 | {'ApplicationAuthenticationCode', 249 | binary_to_list(AuthCode)}, 250 | {'ApplicationData', AppData} | 251 | IMG#erl_image.attributes], 252 | read_data(Fd,IMG#erl_image { attributes = As1}, 253 | RowFun,St0,As); 254 | Error -> 255 | Error 256 | end; 257 | terminator -> 258 | {error, bad_app_block}; 259 | Error -> 260 | Error 261 | end. 262 | 263 | %% 264 | %% Read One block 265 | %% return 266 | %% {ok, Block} 267 | %% terminator 268 | %% | {error,Reason} 269 | %% 270 | %% 271 | %% 272 | read_block(Fd) -> 273 | case file:read(Fd, 1) of 274 | {ok, <<0>>} -> 275 | terminator; 276 | {ok, <>} -> 277 | file:read(Fd, Size); 278 | Error -> 279 | Error 280 | end. 281 | 282 | 283 | %% 284 | %% Read a list of blocks 285 | %% 286 | read_blocks(Fd) -> 287 | read_blocks(Fd,<<>>). 288 | 289 | read_blocks(Fd,Acc) -> 290 | case read_block(Fd) of 291 | {ok,Bin} -> read_blocks(Fd,<>); 292 | terminator -> {ok, Acc}; 293 | Error -> Error 294 | end. 295 | 296 | 297 | 298 | read_palette(_Fd, 0, _Pixel) -> 299 | undefined; 300 | read_palette(Fd, 1, Pixel) -> 301 | Sz = (1 bsl Pixel), 302 | case file:read(Fd, Sz*3) of 303 | {ok, Bin} -> 304 | rd_palette(Bin, [], Sz) 305 | end. 306 | 307 | 308 | rd_palette(_Bin, Map, 0) -> 309 | reverse(Map); 310 | rd_palette(<>, Map, I) -> 311 | rd_palette(Bin, [{R,G,B} | Map], I-1). 312 | 313 | 314 | 315 | write(Fd, IMG) -> 316 | write_info(Fd, IMG), 317 | Palette = IMG#erl_image.palette, 318 | Background = attribute('Background',IMG#erl_image.attributes,0), 319 | AspectRatio = attribute('AspectRatio', IMG#erl_image.attributes,0), 320 | if is_list(Palette) -> 321 | PLen = length(Palette), 322 | ColorRes = if PLen > 0, PLen =< 256 -> 323 | trunc(math:log(PLen)/math:log(2))+1; 324 | PLen > 0 -> 325 | 8; 326 | true -> 327 | 1 328 | end, 329 | Map = 1, 330 | Cr = ColorRes - 1, 331 | Sort = 0, 332 | Pix = ColorRes - 1, 333 | file:write(Fd, <>), 334 | file:write(Fd, <>), 335 | write_palette(Fd, IMG#erl_image.palette, Pix+1); 336 | true -> 337 | Map = 0, 338 | Cr = 0, 339 | Sort = 0, 340 | Pix = 0, 341 | file:write(Fd, <>), 342 | file:write(Fd, <>) 343 | end, 344 | write_data(Fd, IMG), 345 | file:write(Fd, <>). 346 | 347 | 348 | write_palette(Fd, Map, Pixel) -> 349 | wr_palette(Fd, Map, (1 bsl Pixel)). 350 | 351 | wr_palette(_Fd, _, 0) -> ok; 352 | wr_palette(Fd, [{R,G,B}|Map], I) -> 353 | file:write(Fd, <>), 354 | wr_palette(Fd, Map, I-1); 355 | wr_palette(Fd, [], I) -> 356 | file:write(Fd, <<0:8, 0:8, 0:8>>), 357 | wr_palette(Fd, [], I-1). 358 | 359 | write_data(Fd, IMG) -> 360 | write_pixmaps(Fd, IMG, IMG#erl_image.pixmaps). 361 | 362 | write_pixmaps(Fd, IMG, [Pm|Pms]) -> 363 | DisposalMethod = attribute('DisposalMethod',Pm#erl_pixmap.attributes, 0), 364 | UserInput = attribute('UserInput', Pm#erl_pixmap.attributes, 0), 365 | DelayTime = attribute('DelayTime', Pm#erl_pixmap.attributes, 0), 366 | Transparent = attribute('Transparent', Pm#erl_pixmap.attributes, 0), 367 | TransparentColor = attribute('TransparentColor', 368 | Pm#erl_pixmap.attributes, 0), 369 | file:write(Fd, <>), 370 | write_blocks(Fd, <<0:3, DisposalMethod:3, 371 | UserInput:1, Transparent:1, 372 | DelayTime:16/unsigned-little, 373 | TransparentColor:8>>), 374 | write_image(Fd, Pm), 375 | write_pixmaps(Fd, IMG, Pms); 376 | write_pixmaps(_Fd, _IMG, []) -> 377 | ok. 378 | 379 | 380 | write_image(Fd, Pm) -> 381 | file:write(Fd, <>), 382 | file:write(Fd, 383 | <<(Pm#erl_pixmap.left):16/little, 384 | (Pm#erl_pixmap.top):16/little, 385 | (Pm#erl_pixmap.width):16/little, 386 | (Pm#erl_pixmap.height):16/little>>), 387 | Palette = Pm#erl_pixmap.palette, 388 | Interlaced = attribute('Interlaced', Pm#erl_pixmap.attributes, 0), 389 | %% Special code for none compressed data!!! 390 | Inline = attribute('Inline', Pm#erl_pixmap.attributes, 0), 391 | if is_list(Palette) -> 392 | PLen = length(Palette), 393 | ColorRes = if PLen > 0, PLen =< 256 -> 394 | trunc(math:log(PLen)/math:log(2))+1; 395 | PLen > 0 -> 396 | 8; 397 | true -> 398 | 1 399 | end, 400 | Sort = 0, 401 | Pix = ColorRes - 1, 402 | Map = 1, 403 | file:write(Fd, <>), 404 | write_palette(Fd, Palette, Pix+1); 405 | true -> 406 | Sort = 0, 407 | Pix = 0, 408 | Map = 0, 409 | file:write(Fd, <>) 410 | end, 411 | write_pixels(Fd, 412 | Pm#erl_pixmap.pixels, 413 | Pm#erl_pixmap.width, 414 | Pm#erl_pixmap.height, Interlaced, Inline). 415 | 416 | write_pixels(Fd, Pixels, Width, Height, Interlaced, Inline) -> 417 | Bin = collect_pixels(Pixels, Width, Height, Interlaced), 418 | {LZWCodeSize, Bin1} = 419 | if Inline == 1 -> 420 | %% FIXME: check that all pixels are 7 bit !!!!! 421 | {7,<<128, Bin/binary, 129>>}; 422 | true -> 423 | lzw:compress_gif(Bin) 424 | end, 425 | ?dbg("compress: orig_size=~w, size=~w codesize=~w\n", 426 | [size(Bin), size(Bin1), LZWCodeSize]), 427 | file:write(Fd, <>), 428 | write_blocks(Fd, Bin1). 429 | 430 | %% 431 | %% Fixme check that all rows are present and 432 | %% implement interlaced order 433 | %% 434 | collect_pixels(Rows, Width, Height, Interlaced) -> 435 | SortedRows = lists:sort(Rows), 436 | if Interlaced == 1 -> 437 | collect_interlaced(SortedRows,Width,Height,[],[],[],[]); 438 | true -> 439 | collect_raw(SortedRows,Width,Height,[]) 440 | end. 441 | 442 | collect_raw([{Ri,Row} | Rows], Width, Height,Acc) when Ri < Height -> 443 | Sz = size(Row), 444 | R = if Sz > Width -> 445 | %% remove pixels 446 | <> = Row, 447 | Bin; 448 | Sz < Width -> 449 | %% add pixels 450 | <>; 452 | true -> 453 | Row 454 | end, 455 | collect_raw(Rows, Width, Height, [R | Acc]); 456 | collect_raw([{_Ri,_Row} | Rows], Width, Height, Acc) -> 457 | %% ignore line out of range 458 | collect_raw(Rows, Width, Height, Acc); 459 | collect_raw([], _Width, _Height, Acc) -> 460 | list_to_binary(reverse(Acc)). 461 | 462 | collect_interlaced([{Ri,Row}|Rows],Width,Height,R1,R2,R3,R4) -> 463 | case Ri band 7 of 464 | 0 -> collect_interlaced(Rows,Width,Height,[Row|R1],R2,R3,R4); 465 | 1 -> collect_interlaced(Rows,Width,Height,R1,R2,R3,[Row|R4]); 466 | 2 -> collect_interlaced(Rows,Width,Height,R1,R2,[Row|R3],R4); 467 | 3 -> collect_interlaced(Rows,Width,Height,R1,R2,R3,[Row|R4]); 468 | 4 -> collect_interlaced(Rows,Width,Height,R1,[Row|R2],R3,R4); 469 | 5 -> collect_interlaced(Rows,Width,Height,R1,R2,R3,[Row|R4]); 470 | 6 -> collect_interlaced(Rows,Width,Height,R1,R2,[Row|R3],R4); 471 | 7 -> collect_interlaced(Rows,Width,Height,R1,R2,R3,[Row|R4]) 472 | end; 473 | collect_interlaced([],_Width,_Height,R1,R2,R3,R4) -> 474 | list_to_binary([reverse(R1),reverse(R2),reverse(R3),reverse(R4)]). 475 | 476 | 477 | 478 | write_blocks(Fd, Bin) -> 479 | write_blocks(Fd, Bin, 0, size(Bin)). 480 | 481 | write_blocks(Fd, Bin, Pos, Size) -> 482 | Sz = Size - Pos, 483 | if Sz > 255 -> 484 | <<_:Pos/binary, Block:255/binary, _/binary>> = Bin, 485 | file:write(Fd, <<255, Block/binary>>), 486 | write_blocks(Fd, Bin, Pos+255, Size); 487 | true -> 488 | <<_:Pos/binary, Block:Sz/binary, _/binary>> = Bin, 489 | file:write(Fd, <>), 490 | file:write(Fd, <<0>>) 491 | end. 492 | 493 | 494 | read_pixels(Fd,Pix0,RowFun,St0,Width,Height,Interlaced) -> 495 | case file:read(Fd, 1) of 496 | {ok, <>} -> 497 | case read_image(Fd,LZWCodeSize,Width,Height) of 498 | {ok,Data} -> 499 | case Interlaced of 500 | 1 -> 501 | interlaced_data(Data,Pix0,RowFun,St0,Width,Height); 502 | 0 -> 503 | raw_data(Data,Pix0,RowFun,St0,0,Width) 504 | end; 505 | Error -> 506 | Error 507 | end; 508 | Error -> 509 | Error 510 | end. 511 | 512 | read_image(Fd, LZWCodeSize, _Width, _Height) -> 513 | case read_blocks(Fd) of 514 | {ok,Bin} -> 515 | ?dbg("LZWCodeSize=~p compressed=~p\n", [LZWCodeSize, size(Bin)]), 516 | {ok,lzw:decompress_gif(Bin, LZWCodeSize)}; 517 | Error -> 518 | Error 519 | end. 520 | 521 | 522 | %% 523 | %% Read raw data 524 | %% 525 | raw_data(Bin,Pix,RowFun,St0,Ri,Width) -> 526 | case Bin of 527 | <> -> 528 | St1 = RowFun(Pix,Row,Ri,St0), 529 | raw_data(Bin1,Pix,RowFun,St1,Ri+1,Width); 530 | <<>> -> 531 | {ok, Pix#erl_pixmap { pixels = St0 }} 532 | end. 533 | 534 | %% Read interlaced data 535 | %% 536 | %% 0 R1a 537 | %% 1 R4a 538 | %% 2 R3a 539 | %% 3 R4b 540 | %% 4 R2a 541 | %% 5 R4c 542 | %% 6 R3b 543 | %% 7 R4d 544 | %% ... 545 | 546 | interlaced_data(Bin,Pix,RowFun,St0,Width,Height) -> 547 | {St1,Bin1} = raster_data(Bin, Pix,RowFun,St0, Height,0,8, Width), 548 | {St2,Bin2} = raster_data(Bin1,Pix,RowFun,St1, Height,4,8, Width), 549 | {St3,Bin3} = raster_data(Bin2,Pix,RowFun,St2, Height,2,4, Width), 550 | {St4,_Bin4} = raster_data(Bin3,Pix,RowFun,St3, Height,1,2, Width), 551 | {ok, Pix#erl_pixmap{ pixels = St4 }}. 552 | 553 | raster_data(Bin,_Pix,_RowFun,St,Height,Ri,_Rs,_Width) when Ri >= Height -> 554 | {St, Bin}; 555 | raster_data(Bin,Pix,RowFun,St0,Height,Ri,Rs,Width) -> 556 | <> = Bin, 557 | St1 = RowFun(Pix,Row,Ri,St0), 558 | raster_data(Bin1,Pix,RowFun,St1,Height,Ri+Rs,Rs,Width). 559 | 560 | 561 | attribute(Name, List, Default) -> 562 | case lists:keysearch(Name, 1, List) of 563 | false -> 564 | Default; 565 | {value,{_,Value}} -> 566 | Value 567 | end. 568 | -------------------------------------------------------------------------------- /src/lzw.erl: -------------------------------------------------------------------------------- 1 | %%% File : lzw.erl 2 | %%% Author : Dan Gudmundsson 3 | %%% Tony Rogvall 4 | %%% 5 | %%% Description : LZW compresion 6 | %%% Created : 26 Mar 2003 by Tony Rogvall 7 | %%% 8 | %%% lzw derived from wings e3d__tiff updated and fixed to 9 | %%% handle gif and tiff fillorder=2 10 | %%% 11 | -module(lzw). 12 | 13 | -export([decompress_tiff/1, 14 | decompress_tiff/3, 15 | decompress_gif/1, 16 | decompress_gif/2]). 17 | 18 | -export([compress/1, 19 | compress/2, 20 | compress/5, 21 | compress_gif/1, 22 | compress_gif/2]). 23 | 24 | -export([rbits8/1, rbits/2]). 25 | -compile(export_all). 26 | 27 | -import(lists, [reverse/1, foldl/3]). 28 | 29 | -include("erl_img.hrl"). 30 | 31 | -include("dbg.hrl"). 32 | 33 | -record(z, 34 | { 35 | clear, 36 | eoi, 37 | first, 38 | startlen, 39 | next, 40 | max, 41 | nextfn 42 | }). 43 | 44 | -define(get_lzw(Code), get(Code)). 45 | -define(add_lzw(Code, Str), put(Code, Str)). 46 | 47 | %% most significant bits first 48 | read_bits_msb(Bin, Offs, Len) -> 49 | Offs1 = Offs+Len, 50 | Pad = ?PAD_Len8(Offs1), 51 | <<_:Offs, Code:Len, _:Pad,_/binary>> = Bin, 52 | {Code,Offs1}. 53 | 54 | %% least significant bytes first 55 | read_bits_lsb(Bin, Offs, Len) -> 56 | Offs1 = Offs+Len, 57 | B0 = Offs div 8, 58 | B1 = (Offs1-1) div 8, 59 | R0 = Offs rem 8, 60 | BL = ((B1 - B0)+1), 61 | ?dbg("blen=~w,b0=~w,r0=~w b1=~w\n", [BL,B0,R0,B1]), 62 | <<_:B0/binary,BCode:BL/little-unsigned-integer-unit:8,_/binary>> = Bin, 63 | Code = (BCode bsr R0) band ((1 bsl Len)-1), 64 | {Code,Offs1}. 65 | 66 | %% least significant bits and bytes first 67 | read_bits_LSB(Bin, Offs, Len) -> 68 | {Code,Offs1} = read_bits_lsb(Bin,Offs,Len), 69 | {rbits(Code,Len),Offs1}. 70 | 71 | 72 | %% should finalize the dictionary (if not using special process) 73 | init_decomp(Lim) -> 74 | erase(), 75 | init_decomp(0,Lim). 76 | 77 | init_decomp(Lim,Lim) -> 78 | ok; 79 | init_decomp(I,Lim) -> 80 | ?add_lzw(I, [I]), 81 | init_decomp(I+1,Lim). 82 | 83 | init_comp(Lim) -> 84 | erase(), 85 | init_comp(0, Lim). 86 | 87 | init_comp(Lim,Lim) -> 88 | ok; 89 | init_comp(I,Lim) -> 90 | ?add_lzw([I], I), 91 | init_comp(I+1,Lim). 92 | 93 | 94 | %% reverse bytes and read the codes backwards 95 | %% needed for gif bit packing 96 | %% 97 | decompress_gif(Bin) -> 98 | decompress_gif(Bin, 8). 99 | 100 | decompress_gif(Bin, MinCodeSize) -> 101 | ReadFn = fun(Len,Offs) -> 102 | read_bits_lsb(Bin,Offs,Len) 103 | end, 104 | NextFn = fun(Len) -> (1 bsl Len) end, 105 | decomp(0,ReadFn,MinCodeSize,NextFn). 106 | 107 | 108 | 109 | decompress_tiff(Bin) -> 110 | decompress_tiff(Bin, 8, 1). 111 | 112 | decompress_tiff(Bin, MinCodeSize, 2) -> 113 | ReadFn = fun(Len,Offs) -> 114 | read_bits_LSB(Bin,Offs,Len) 115 | end, 116 | NextFn = fun(Len) -> (1 bsl Len)-1 end, 117 | decomp(0,ReadFn,MinCodeSize,NextFn); 118 | decompress_tiff(Bin, MinCodeSize, 1) -> 119 | ReadFn = fun(Len,Offs) -> 120 | read_bits_msb(Bin,Offs,Len) 121 | end, 122 | NextFn = fun(Len) -> (1 bsl Len)-1 end, 123 | decomp(0, ReadFn, MinCodeSize, NextFn). 124 | 125 | 126 | decomp(S, Read, MinCodeSize, NextFn) -> 127 | First = (1 bsl MinCodeSize)+2, 128 | StartLen = MinCodeSize+1, 129 | Z = #z { clear = (1 bsl MinCodeSize), 130 | eoi = (1 bsl MinCodeSize)+1, 131 | first = First, 132 | startlen = StartLen, 133 | next = NextFn(StartLen), 134 | max = 0, %% not used here 135 | nextfn = NextFn 136 | }, 137 | ?dbg("decomp: mincodesize=~p, z = ~p\n",[MinCodeSize, Z]), 138 | decomp(S,Read,0,First,StartLen,Z,[]). 139 | 140 | 141 | 142 | decomp(S,Read,PrevCode,Count,BitLen,Z,Acc) when BitLen<12,Count == Z#z.next -> 143 | NextBitLen = BitLen+1, 144 | ?dbg("NEXT BITLEN=~p\n", [NextBitLen]), 145 | NextCode = (Z#z.nextfn)(NextBitLen), 146 | decomp(S, Read, PrevCode, Count, NextBitLen, 147 | Z#z { next = NextCode }, Acc); 148 | decomp(S,Read,PrevCode,Count,BitLen,Z,Acc) -> 149 | {NewCode,NS} = Read(BitLen,S), 150 | ?dbg("read: ~w/~w count=~w\n", [NewCode, BitLen,Count]), 151 | if NewCode == Z#z.eoi -> 152 | ?dbg("EOI:~p\n",[Z#z.eoi]), 153 | list_to_binary(reverse(Acc)); 154 | NewCode == Z#z.clear -> 155 | ?dbg("CLEAR:~p\n",[Z#z.clear]), 156 | init_decomp(Z#z.first), 157 | StartLen = Z#z.startlen, 158 | {NewCode1,NS1} = Read(StartLen, NS), 159 | ?dbg("read: ~w/~w count=~w\n", [NewCode1,StartLen,Count]), 160 | if NewCode1 == Z#z.eoi -> 161 | list_to_binary(reverse(Acc)); 162 | true -> 163 | Str = ?get_lzw(NewCode1), 164 | NextCode = (Z#z.nextfn)(StartLen), 165 | decomp(NS1,Read,NewCode1, Z#z.first, StartLen, 166 | Z#z { next = NextCode }, [Str|Acc]) 167 | end; 168 | true -> 169 | ?dbg("CODE: prev=~p new=~p\n",[PrevCode,NewCode]), 170 | case ?get_lzw(NewCode) of 171 | undefined -> 172 | OldStr = [H|_] = ?get_lzw(PrevCode), 173 | NewStr = OldStr ++ [H], 174 | ?add_lzw(NewCode, NewStr), 175 | decomp(NS,Read,NewCode,Count+1,BitLen,Z,[NewStr|Acc]); 176 | Str = [H|_]-> 177 | ?add_lzw(Count, ?get_lzw(PrevCode) ++ [H]), 178 | decomp(NS,Read,NewCode,Count+1,BitLen,Z,[Str|Acc]) 179 | end 180 | end. 181 | 182 | %% reverse bits in a byte 183 | rbits8(Code) -> 184 | case Code of 185 | 2#00000000 -> 2#00000000; 186 | 2#00000001 -> 2#10000000; 187 | 2#00000010 -> 2#01000000; 188 | 2#00000011 -> 2#11000000; 189 | 2#00000100 -> 2#00100000; 190 | 2#00000101 -> 2#10100000; 191 | 2#00000110 -> 2#01100000; 192 | 2#00000111 -> 2#11100000; 193 | 2#00001000 -> 2#00010000; 194 | 2#00001001 -> 2#10010000; 195 | 2#00001010 -> 2#01010000; 196 | 2#00001011 -> 2#11010000; 197 | 2#00001100 -> 2#00110000; 198 | 2#00001101 -> 2#10110000; 199 | 2#00001110 -> 2#01110000; 200 | 2#00001111 -> 2#11110000; 201 | 2#00010000 -> 2#00001000; 202 | 2#00010001 -> 2#10001000; 203 | 2#00010010 -> 2#01001000; 204 | 2#00010011 -> 2#11001000; 205 | 2#00010100 -> 2#00101000; 206 | 2#00010101 -> 2#10101000; 207 | 2#00010110 -> 2#01101000; 208 | 2#00010111 -> 2#11101000; 209 | 2#00011000 -> 2#00011000; 210 | 2#00011001 -> 2#10011000; 211 | 2#00011010 -> 2#01011000; 212 | 2#00011011 -> 2#11011000; 213 | 2#00011100 -> 2#00111000; 214 | 2#00011101 -> 2#10111000; 215 | 2#00011110 -> 2#01111000; 216 | 2#00011111 -> 2#11111000; 217 | 2#00100000 -> 2#00000100; 218 | 2#00100001 -> 2#10000100; 219 | 2#00100010 -> 2#01000100; 220 | 2#00100011 -> 2#11000100; 221 | 2#00100100 -> 2#00100100; 222 | 2#00100101 -> 2#10100100; 223 | 2#00100110 -> 2#01100100; 224 | 2#00100111 -> 2#11100100; 225 | 2#00101000 -> 2#00010100; 226 | 2#00101001 -> 2#10010100; 227 | 2#00101010 -> 2#01010100; 228 | 2#00101011 -> 2#11010100; 229 | 2#00101100 -> 2#00110100; 230 | 2#00101101 -> 2#10110100; 231 | 2#00101110 -> 2#01110100; 232 | 2#00101111 -> 2#11110100; 233 | 2#00110000 -> 2#00001100; 234 | 2#00110001 -> 2#10001100; 235 | 2#00110010 -> 2#01001100; 236 | 2#00110011 -> 2#11001100; 237 | 2#00110100 -> 2#00101100; 238 | 2#00110101 -> 2#10101100; 239 | 2#00110110 -> 2#01101100; 240 | 2#00110111 -> 2#11101100; 241 | 2#00111000 -> 2#00011100; 242 | 2#00111001 -> 2#10011100; 243 | 2#00111010 -> 2#01011100; 244 | 2#00111011 -> 2#11011100; 245 | 2#00111100 -> 2#00111100; 246 | 2#00111101 -> 2#10111100; 247 | 2#00111110 -> 2#01111100; 248 | 2#00111111 -> 2#11111100; 249 | 2#01000000 -> 2#00000010; 250 | 2#01000001 -> 2#10000010; 251 | 2#01000010 -> 2#01000010; 252 | 2#01000011 -> 2#11000010; 253 | 2#01000100 -> 2#00100010; 254 | 2#01000101 -> 2#10100010; 255 | 2#01000110 -> 2#01100010; 256 | 2#01000111 -> 2#11100010; 257 | 2#01001000 -> 2#00010010; 258 | 2#01001001 -> 2#10010010; 259 | 2#01001010 -> 2#01010010; 260 | 2#01001011 -> 2#11010010; 261 | 2#01001100 -> 2#00110010; 262 | 2#01001101 -> 2#10110010; 263 | 2#01001110 -> 2#01110010; 264 | 2#01001111 -> 2#11110010; 265 | 2#01010000 -> 2#00001010; 266 | 2#01010001 -> 2#10001010; 267 | 2#01010010 -> 2#01001010; 268 | 2#01010011 -> 2#11001010; 269 | 2#01010100 -> 2#00101010; 270 | 2#01010101 -> 2#10101010; 271 | 2#01010110 -> 2#01101010; 272 | 2#01010111 -> 2#11101010; 273 | 2#01011000 -> 2#00011010; 274 | 2#01011001 -> 2#10011010; 275 | 2#01011010 -> 2#01011010; 276 | 2#01011011 -> 2#11011010; 277 | 2#01011100 -> 2#00111010; 278 | 2#01011101 -> 2#10111010; 279 | 2#01011110 -> 2#01111010; 280 | 2#01011111 -> 2#11111010; 281 | 2#01100000 -> 2#00000110; 282 | 2#01100001 -> 2#10000110; 283 | 2#01100010 -> 2#01000110; 284 | 2#01100011 -> 2#11000110; 285 | 2#01100100 -> 2#00100110; 286 | 2#01100101 -> 2#10100110; 287 | 2#01100110 -> 2#01100110; 288 | 2#01100111 -> 2#11100110; 289 | 2#01101000 -> 2#00010110; 290 | 2#01101001 -> 2#10010110; 291 | 2#01101010 -> 2#01010110; 292 | 2#01101011 -> 2#11010110; 293 | 2#01101100 -> 2#00110110; 294 | 2#01101101 -> 2#10110110; 295 | 2#01101110 -> 2#01110110; 296 | 2#01101111 -> 2#11110110; 297 | 2#01110000 -> 2#00001110; 298 | 2#01110001 -> 2#10001110; 299 | 2#01110010 -> 2#01001110; 300 | 2#01110011 -> 2#11001110; 301 | 2#01110100 -> 2#00101110; 302 | 2#01110101 -> 2#10101110; 303 | 2#01110110 -> 2#01101110; 304 | 2#01110111 -> 2#11101110; 305 | 2#01111000 -> 2#00011110; 306 | 2#01111001 -> 2#10011110; 307 | 2#01111010 -> 2#01011110; 308 | 2#01111011 -> 2#11011110; 309 | 2#01111100 -> 2#00111110; 310 | 2#01111101 -> 2#10111110; 311 | 2#01111110 -> 2#01111110; 312 | 2#01111111 -> 2#11111110; 313 | 2#10000000 -> 2#00000001; 314 | 2#10000001 -> 2#10000001; 315 | 2#10000010 -> 2#01000001; 316 | 2#10000011 -> 2#11000001; 317 | 2#10000100 -> 2#00100001; 318 | 2#10000101 -> 2#10100001; 319 | 2#10000110 -> 2#01100001; 320 | 2#10000111 -> 2#11100001; 321 | 2#10001000 -> 2#00010001; 322 | 2#10001001 -> 2#10010001; 323 | 2#10001010 -> 2#01010001; 324 | 2#10001011 -> 2#11010001; 325 | 2#10001100 -> 2#00110001; 326 | 2#10001101 -> 2#10110001; 327 | 2#10001110 -> 2#01110001; 328 | 2#10001111 -> 2#11110001; 329 | 2#10010000 -> 2#00001001; 330 | 2#10010001 -> 2#10001001; 331 | 2#10010010 -> 2#01001001; 332 | 2#10010011 -> 2#11001001; 333 | 2#10010100 -> 2#00101001; 334 | 2#10010101 -> 2#10101001; 335 | 2#10010110 -> 2#01101001; 336 | 2#10010111 -> 2#11101001; 337 | 2#10011000 -> 2#00011001; 338 | 2#10011001 -> 2#10011001; 339 | 2#10011010 -> 2#01011001; 340 | 2#10011011 -> 2#11011001; 341 | 2#10011100 -> 2#00111001; 342 | 2#10011101 -> 2#10111001; 343 | 2#10011110 -> 2#01111001; 344 | 2#10011111 -> 2#11111001; 345 | 2#10100000 -> 2#00000101; 346 | 2#10100001 -> 2#10000101; 347 | 2#10100010 -> 2#01000101; 348 | 2#10100011 -> 2#11000101; 349 | 2#10100100 -> 2#00100101; 350 | 2#10100101 -> 2#10100101; 351 | 2#10100110 -> 2#01100101; 352 | 2#10100111 -> 2#11100101; 353 | 2#10101000 -> 2#00010101; 354 | 2#10101001 -> 2#10010101; 355 | 2#10101010 -> 2#01010101; 356 | 2#10101011 -> 2#11010101; 357 | 2#10101100 -> 2#00110101; 358 | 2#10101101 -> 2#10110101; 359 | 2#10101110 -> 2#01110101; 360 | 2#10101111 -> 2#11110101; 361 | 2#10110000 -> 2#00001101; 362 | 2#10110001 -> 2#10001101; 363 | 2#10110010 -> 2#01001101; 364 | 2#10110011 -> 2#11001101; 365 | 2#10110100 -> 2#00101101; 366 | 2#10110101 -> 2#10101101; 367 | 2#10110110 -> 2#01101101; 368 | 2#10110111 -> 2#11101101; 369 | 2#10111000 -> 2#00011101; 370 | 2#10111001 -> 2#10011101; 371 | 2#10111010 -> 2#01011101; 372 | 2#10111011 -> 2#11011101; 373 | 2#10111100 -> 2#00111101; 374 | 2#10111101 -> 2#10111101; 375 | 2#10111110 -> 2#01111101; 376 | 2#10111111 -> 2#11111101; 377 | 2#11000000 -> 2#00000011; 378 | 2#11000001 -> 2#10000011; 379 | 2#11000010 -> 2#01000011; 380 | 2#11000011 -> 2#11000011; 381 | 2#11000100 -> 2#00100011; 382 | 2#11000101 -> 2#10100011; 383 | 2#11000110 -> 2#01100011; 384 | 2#11000111 -> 2#11100011; 385 | 2#11001000 -> 2#00010011; 386 | 2#11001001 -> 2#10010011; 387 | 2#11001010 -> 2#01010011; 388 | 2#11001011 -> 2#11010011; 389 | 2#11001100 -> 2#00110011; 390 | 2#11001101 -> 2#10110011; 391 | 2#11001110 -> 2#01110011; 392 | 2#11001111 -> 2#11110011; 393 | 2#11010000 -> 2#00001011; 394 | 2#11010001 -> 2#10001011; 395 | 2#11010010 -> 2#01001011; 396 | 2#11010011 -> 2#11001011; 397 | 2#11010100 -> 2#00101011; 398 | 2#11010101 -> 2#10101011; 399 | 2#11010110 -> 2#01101011; 400 | 2#11010111 -> 2#11101011; 401 | 2#11011000 -> 2#00011011; 402 | 2#11011001 -> 2#10011011; 403 | 2#11011010 -> 2#01011011; 404 | 2#11011011 -> 2#11011011; 405 | 2#11011100 -> 2#00111011; 406 | 2#11011101 -> 2#10111011; 407 | 2#11011110 -> 2#01111011; 408 | 2#11011111 -> 2#11111011; 409 | 2#11100000 -> 2#00000111; 410 | 2#11100001 -> 2#10000111; 411 | 2#11100010 -> 2#01000111; 412 | 2#11100011 -> 2#11000111; 413 | 2#11100100 -> 2#00100111; 414 | 2#11100101 -> 2#10100111; 415 | 2#11100110 -> 2#01100111; 416 | 2#11100111 -> 2#11100111; 417 | 2#11101000 -> 2#00010111; 418 | 2#11101001 -> 2#10010111; 419 | 2#11101010 -> 2#01010111; 420 | 2#11101011 -> 2#11010111; 421 | 2#11101100 -> 2#00110111; 422 | 2#11101101 -> 2#10110111; 423 | 2#11101110 -> 2#01110111; 424 | 2#11101111 -> 2#11110111; 425 | 2#11110000 -> 2#00001111; 426 | 2#11110001 -> 2#10001111; 427 | 2#11110010 -> 2#01001111; 428 | 2#11110011 -> 2#11001111; 429 | 2#11110100 -> 2#00101111; 430 | 2#11110101 -> 2#10101111; 431 | 2#11110110 -> 2#01101111; 432 | 2#11110111 -> 2#11101111; 433 | 2#11111000 -> 2#00011111; 434 | 2#11111001 -> 2#10011111; 435 | 2#11111010 -> 2#01011111; 436 | 2#11111011 -> 2#11011111; 437 | 2#11111100 -> 2#00111111; 438 | 2#11111101 -> 2#10111111; 439 | 2#11111110 -> 2#01111111; 440 | 2#11111111 -> 2#11111111 441 | end. 442 | 443 | rbits(Code, Len) -> 444 | rbits(Code, Len, 0). 445 | 446 | rbits(_Code, 0, Acc) -> 447 | Acc; 448 | rbits(Code, Len, Acc) when Len >= 8 -> 449 | rbits(Code bsr 8, Len - 8, 450 | (Acc bsl 8) bor rbits8(Code band 16#ff)); 451 | rbits(Code, Len, Acc) -> 452 | A8 = rbits8(Code band 16#ff), 453 | (Acc bsl Len) bor (A8 bsr (8 - Len)). 454 | 455 | 456 | compress(Bin) -> 457 | compress(Bin, size(Bin)). 458 | 459 | compress(Bin, Stripe) -> 460 | NextFn = fun(Len) -> (1 bsl Len)-1 end, 461 | compress(Bin, Stripe, 8, 12, NextFn). 462 | 463 | compress_gif(Bin) -> 464 | compress_gif(Bin, 8). 465 | 466 | compress_gif(Bin,MinCodeSize) -> 467 | NextFn = fun(Len) -> (1 bsl Len) end, 468 | compress(Bin,size(Bin), MinCodeSize, 14, NextFn). 469 | 470 | 471 | compress(Bin,Stripe,MinCodeSize,MaxCodeSize,NextFn) -> 472 | First = (1 bsl MinCodeSize)+2, 473 | StartLen = MinCodeSize+1, 474 | Z = #z { clear = (1 bsl MinCodeSize), 475 | eoi = (1 bsl MinCodeSize)+1, 476 | first = First, 477 | startlen = StartLen, 478 | next = NextFn(StartLen), 479 | max = NextFn(MaxCodeSize+1), 480 | nextfn = NextFn 481 | }, 482 | ?dbg("compress: mincodesize=~w, z=~w\n",[MinCodeSize,Z]), 483 | comp0(Bin,Stripe,StartLen,Z,{0,[],[]}). 484 | 485 | 486 | comp0(Bin,Stripe,BitLen,Z,Build) -> 487 | init_comp(Z#z.first-3), 488 | NBuild = write(Z#z.clear, BitLen, Build), 489 | comp1(Bin,0,Stripe,[],Z#z.startlen,Z#z.first,Z,NBuild). 490 | 491 | 492 | comp1(<<>>,_CC,_Stripe,Omega,BitLen,_Count,Z,Build) -> 493 | Code = ?get_lzw(Omega), 494 | NBuild = write(Code, BitLen, Build), 495 | {TotBitLen,Codes, Acc} = write(Z#z.eoi, BitLen, NBuild), 496 | PaddL = 8 - (TotBitLen rem 8), 497 | Bin = if PaddL == 8 -> 498 | buildbin(reverse(Codes)); 499 | true -> 500 | buildbin(reverse([{PaddL, 0}|Codes])) 501 | end, 502 | {Z#z.startlen-1, list_to_binary(reverse([Bin|Acc]))}; 503 | 504 | comp1(Bin,CC,Stripe,Omega,BitLen,Count,Z,Build) when CC == Stripe -> 505 | Code =?get_lzw(Omega), 506 | NBuild = write(Code, BitLen, Build), 507 | if Count+2 == Z#z.next -> 508 | BitLen1 = BitLen+1, 509 | NextCode = (Z#z.nextfn)(BitLen1), 510 | Z1 = Z#z { next = NextCode }, 511 | comp0(Bin,Stripe,BitLen1,Z1,NBuild); 512 | true -> 513 | comp0(Bin,Stripe,BitLen,Z,NBuild) 514 | end; 515 | 516 | comp1(<>,CC,Stripe,Omega,BitLen,Count,Z,Build) -> 517 | NewOmega = [Char|Omega], 518 | case ?get_lzw(NewOmega) of 519 | undefined -> 520 | Code = ?get_lzw(Omega), 521 | NBuild = write(Code,BitLen,Build), 522 | ?add_lzw(NewOmega, Count), 523 | if Count+2 == Z#z.next -> 524 | BitLen1 = BitLen+1, 525 | NextCode = (Z#z.nextfn)(BitLen1), 526 | Z1 = Z#z { next = NextCode }, 527 | if Z#z.next == Z#z.max -> 528 | Code2 =?get_lzw([Char]), 529 | NBuild2 = write(Code2,BitLen,NBuild), 530 | comp0(Bin,Stripe,BitLen1,Z1,NBuild2); 531 | true -> 532 | comp1(Bin,CC+1,Stripe,[Char],BitLen1, 533 | Count+1,Z1,NBuild) 534 | end; 535 | true -> 536 | comp1(Bin,CC+1,Stripe,[Char],BitLen, 537 | Count+1,Z,NBuild) 538 | end; 539 | _ -> 540 | comp1(Bin,CC+1,Stripe,NewOmega,BitLen,Count,Z,Build) 541 | end. 542 | 543 | %% FIXME: must be able to write lsb byte order and bit order! 544 | write(Code, CLen, {Totlen, List, Acc}) -> 545 | ?dbg("write: ~w/~w\n", [Code, CLen]), 546 | NewLen = CLen + Totlen, 547 | if 548 | NewLen rem 8 == 0 -> 549 | case buildbin(reverse([{CLen,Code}|List])) of 550 | Bin when is_binary(Bin) -> 551 | {0, [],[Bin|Acc]}; 552 | {Bin, NewList} when is_binary(Bin) -> 553 | Sum = foldl(fun({X,_}, Sum) -> X + Sum end, 0, NewList), 554 | {Sum,reverse(NewList),[Bin|Acc]} 555 | end; 556 | 557 | NewLen > 100 -> 558 | {Bin,NewList} = buildbin(reverse([{CLen,Code}|List])), 559 | Sum = foldl(fun({X,_}, Sum) -> X + Sum end, 0, NewList), 560 | {Sum,reverse(NewList),[Bin|Acc]}; 561 | 562 | true -> 563 | {Totlen+CLen,[{CLen,Code}|List], Acc} 564 | end. 565 | 566 | buildbin([{_L,C}]) -> 567 | <>; 568 | buildbin([{L1, C1},{L2,C2}]) -> 569 | <>; 570 | buildbin([{L1,C1},{L2,C2},{L3,C3}]) -> 571 | <>; 572 | buildbin([{L1,C1},{L2,C2},{L3,C3},{L4,C4}]) -> 573 | <>; 574 | buildbin([{L1,C1},{L2,C2},{L3,C3},{L4,C4},{L5,C5}]) -> 575 | <>; 576 | buildbin([{L1,C1},{L2,C2},{L3,C3},{L4,C4},{L5,C5},{L6,C6}]) -> 577 | <>; 578 | buildbin([{L1,C1},{L2,C2},{L3,C3},{L4,C4},{L5,C5},{L6,C6},{L7,C7}]) -> 579 | <>; 580 | buildbin([{L1,C1},{L2,C2},{L3,C3},{L4,C4},{L5,C5},{L6,C6},{L7,C7},{L8,C8}]) -> 581 | <>; 582 | buildbin([{L1,C1},{L2,C2},{L3,C3},{L4,C4},{L5,C5}, 583 | {L6,C6},{L7,C7},{L8,C8},{L9,C9} | Rest]) -> 584 | RemL = (L1+L2+L3+L4+L5+L6+L7+L8) rem 8, 585 | AddL = 8 - RemL, 586 | KeepL = L9 - AddL, 587 | SkipL = 16 - (AddL + KeepL), 588 | TempFill = 8 - (L9 rem 8), 589 | <> = <>, 590 | Bin = <>, 591 | NewList = [{KeepL, Keep9}|Rest], 592 | {Bin, NewList}; 593 | buildbin([]) -> 594 | <<>>. 595 | -------------------------------------------------------------------------------- /priv/pngsuite/pngsuite.doc: -------------------------------------------------------------------------------- 1 | PNGSUITE 2 | ---------------- 3 | 4 | testset for PNG-(de)coders 5 | created by Willem van Schaik 6 | ------------------------------------ 7 | 8 | This is a collection of graphics images created to test the png applications 9 | like viewers, converters and editors. All (as far as that is possible) 10 | formats supported by the PNG standard are represented. 11 | 12 | 13 | 1. INTRODUCTION 14 | -------------------- 15 | 16 | 1.1 PNG capabilities 17 | ------------------------ 18 | 19 | Supported color-types are: 20 | 21 | - grayscale 22 | - grayscale + alpha-channel 23 | - color palettes 24 | - rgb 25 | - rgb + alpha-channel 26 | 27 | Allowed bitdepths are depending on the color-type, but are in the range 28 | of 1-bit (grayscale, which is b&w) upto 16-bits. 29 | 30 | Special features are: 31 | 32 | - interlacing (Adam-7) 33 | - gamma-support 34 | - transparency (a poor-man's alpha solution) 35 | 36 | 37 | 1.2 File naming 38 | ------------------- 39 | 40 | Where possible, the testfiles are 32x32 bits icons. This results in a still 41 | reasonable size of the suite even with a large number of tests. The name 42 | of each test-file reflects thetype in the following way: 43 | 44 | g04i2c08.png 45 | || |||+---- bit-depth 46 | || ||+----- color-type (descriptive) 47 | || |+------ color-type (numerical) 48 | || +------- interlaced or non-interlaced 49 | |+--------- parameter of test (in this case gamma-value) 50 | +---------- test feature (in this case gamma) 51 | 52 | 53 | 1.3 PNG formats 54 | ------------------- 55 | 56 | color-type: 57 | 0g - grayscale 58 | 2c - rgb color 59 | 3p - paletted 60 | 4a - grayscale + alpha channel 61 | 6a - rgb color + alpha channel 62 | 63 | bit-depth: 64 | 01 - with color-type 0, 3 65 | 02 - with color-type 0, 3 66 | 04 - with color-type 0, 3 67 | 08 - with color-type 0, 2, 3, 4, 6 68 | 16 - with color-type 0, 2, 4, 6 69 | 70 | interlacing: 71 | n - non-interlaced 72 | i - interlaced 73 | 74 | 75 | 2. THE TESTS 76 | ----------------- 77 | 78 | 2.1 Sizes 79 | ------------- 80 | 81 | These tests are there to check if your software handles pictures well, with 82 | picture sizes that are not a multiple of 8. This is particularly important 83 | with Adam-7 type interlacing. In the same way these tests check if pictures 84 | size 1x1 and similar are ok. 85 | 86 | s01 - 1x1 pixel picture 87 | s02 - 2x2 pixel picture 88 | s03 - 3x3 pixel picture 89 | s04 - 4x4 pixel picture 90 | s05 - 5x5 pixel picture 91 | s06 - 6x6 pixel picture 92 | s07 - 7x7 pixel picture 93 | s08 - 8x8 pixel picture 94 | s09 - 9x9 pixel picture 95 | s32 - 32x32 pixel picture 96 | s33 - 33x33 pixel picture 97 | s34 - 34x34 pixel picture 98 | s35 - 35x35 pixel picture 99 | s36 - 36x36 pixel picture 100 | s37 - 37x37 pixel picture 101 | s38 - 38x38 pixel picture 102 | s39 - 39x39 pixel picture 103 | s40 - 40x40 pixel picture 104 | 105 | 106 | 2.2 Background 107 | ------------------ 108 | 109 | When the PNG file contains a background chunck, this should be used for 110 | pictures with alpha-channel or pictures with a transparency chunck. For 111 | pictures without this background-chunk, but with alpha, this testset 112 | assumes a black background. 113 | 114 | For the images in this test, the left-side should be 100% the background 115 | color, where moving to the right the color should gradually become the 116 | image pattern. 117 | 118 | bga - alpha + no background 119 | bgw - alpha + white background 120 | bgg - alpha + gray background 121 | bgb - alpha + black background 122 | bgy - alpha + yellow background 123 | 124 | 125 | 2.3 Transparency 126 | -------------------- 127 | 128 | Transparency should be used together with a background chunk. To test the 129 | combination of the two the latter 4 tests are there. How to handle pictures 130 | with transparancy, but without a background, opinions can differ. Here we 131 | use black, but especially in the case of paletted images, the normal color 132 | would maybe even be better. 133 | 134 | tp0 - not transparent for reference 135 | tp1 - transparent, but no background chunk 136 | tbw - transparent + white background 137 | tbg - transparent + gray background 138 | tbb - transparent + black background 139 | tby - transparent + yellow background 140 | 141 | 142 | 2.4 Gamma 143 | ------------- 144 | 145 | To test if your viewer handles gamma-correction, 6 testfiles are available. 146 | They contain corrected color-ramps and a corresponding gamma-chunk with the 147 | file-gamma value. These are created in such a way that when the viewer does 148 | the gamma correction right, all 6 should be displayed identical. 149 | 150 | If they are different, probably the gamma correction is omitted. In that 151 | case, have a look at the two right coloumns in the 6 pictures. The image 152 | where those two look the same (when looked from far) reflects the gamma of 153 | your system. However, because of the limited size of the image, you should 154 | do more elaborate tests to determine your display gamma. 155 | 156 | g03 - file-gamma = 0.35, for display with gamma = 2.8 157 | g04 - file-gamma = 0.45, for display with gamma = 2.2 (PC) 158 | g05 - file-gamma = 0.55, for display with gamma = 1.8 (Mac) 159 | g07 - file-gamma = 0.70, for display with gamma = 1.4 160 | g10 - file-gamma = 1.00, for display with gamma = 1.0 (NeXT) 161 | g25 - file-gamma = 2.50, for display with gamma = 0.4 162 | 163 | 164 | 2.5 Filtering 165 | ----------------- 166 | 167 | PNG uses file-filtering, for optimal compression. Normally the type is of 168 | filtering is adjusted to the contents of the picture, but here each file 169 | has the same picture, with a different filtering. 170 | 171 | f0 - no filtering 172 | f1 - sub filtering 173 | f2 - up filtering 174 | f3 - average filtering 175 | f4 - paeth filtering 176 | 177 | 178 | 2.6 Additional palettes 179 | --------------------------- 180 | 181 | Besides the normal use of paletted images, palette chunks can in combination 182 | with true-color (and other) images also be used to select color lookup-tables 183 | when the video system is of limited capabilities. The suggested palette chunk 184 | is specially created for this purpose. 185 | 186 | pp - normal palette chunk 187 | ps - suggested palette chunk 188 | 189 | 190 | 2.7 Ancillary chunks (under construction) 191 | ------------------------ 192 | 193 | To test the correct decoding of ancillary chunks, these test-files contain 194 | one or more examples of these chunkcs. Depending on the type of chunk, a 195 | number of typical values are selected to test. Unluckily, the testset can 196 | not contain all combinations, because that would be an endless set. 197 | 198 | The significant bits are used in files with the next higher bit-depth. They 199 | indicate howmany bits are valid. 200 | 201 | cs3 - 3 significant bits 202 | cs5 - 5 significant bits 203 | cs8 - 8 significant bits (reference) 204 | cs3 - 13 significant bits 205 | 206 | For the physical pixel dimensions, the result of each decoding should be 207 | a sqare picture. The first (cdf) image is an example of flat (horizontal) 208 | pixels, where the pHYS chunk (x is 1 per unit, y = 4 per unit) must take 209 | care of the correction. The second is just the other way round. The last 210 | example uses the unit specifier, for 1000 pixels per meter. This should 211 | result in a picture of 3.2 cm square. 212 | 213 | cdf - physical pixel dimensions, 8x32 flat pixels 214 | cdh - physical pixel dimensions, 32x8 high pixels 215 | cds - physical pixel dimensions, 8x8 square pixels 216 | cdu - physical pixel dimensions, with unit-specifier 217 | 218 | ccw - primary chromaticities and white point 219 | 220 | ch1 - histogram 15 colors 221 | ch2 - histogram 256 colors 222 | 223 | cm7 - modification time, 01-jan-1970 224 | cm9 - modification time, 31-dec-1999 225 | cm0 - modification time, 01-jan-2000 226 | 227 | In the textual chunk, a number of the standard, and some non-standard 228 | text items are included. 229 | 230 | ct0 - no textual data 231 | ct1 - with textual data 232 | ctz - with compressed textual data 233 | 234 | 235 | 2.8 Chunk ordering (still under construction) 236 | ---------------------- 237 | 238 | These testfiles will test the obligatory ordering relations between various 239 | chunk types (not yet) as well as the number of data chunks used for the image. 240 | 241 | oi1 - mother image with 1 idat-chunk 242 | oi2 - image with 2 idat-chunks 243 | oi4 - image with 4 unequal sized idat-chunks 244 | oi9 - all idat-chunks of length one 245 | 246 | 247 | 2.9 Compression level 248 | ------------------------- 249 | 250 | Here you will find a set of images compressed by zlib, ranging from level 0 251 | for no compression at maximum speed upto level 9 for maximum compression. 252 | 253 | z00 - zlib compression level 0 - none 254 | z03 - zlib compression level 3 255 | z06 - zlib compression level 6 - default 256 | z09 - zlib compression level 9 - maximum 257 | 258 | 259 | 2.10 Corrupted files (under construction) 260 | ----------------------- 261 | 262 | All these files are illegal. When decoding they should generate appropriate 263 | error-messages. 264 | 265 | x00 - empty IDAT chunk 266 | xcr - added cr bytes 267 | xlf - added lf bytes 268 | xc0 - color type 0 269 | xc9 - color type 9 270 | xd0 - bit-depth 0 271 | xd3 - bit-depth 3 272 | xd9 - bit-depth 99 273 | xcs - incorrect IDAT checksum 274 | 275 | 276 | 3. TEST FILES 277 | ------------------ 278 | 279 | For each of the tests listed above, one or more test-files are created. A 280 | selection is made (for each test) for the color-type and bitdepth to be used 281 | for the tests. Further for a number of tests, both a non-interlaced as well 282 | as an interlaced version is available. 283 | 284 | 285 | 3.1 Basic format test files (non-interlaced) 286 | ------------------------------------------------ 287 | 288 | basn0g01 - black & white 289 | basn0g02 - 2 bit (4 level) grayscale 290 | basn0g04 - 4 bit (16 level) grayscale 291 | basn0g08 - 8 bit (256 level) grayscale 292 | basn0g16 - 16 bit (64k level) grayscale 293 | basn2c08 - 3x8 bits rgb color 294 | basn2c16 - 3x16 bits rgb color 295 | basn3p01 - 1 bit (2 color) paletted 296 | basn3p02 - 2 bit (4 color) paletted 297 | basn3p04 - 4 bit (16 color) paletted 298 | basn3p08 - 8 bit (256 color) paletted 299 | basn4a08 - 8 bit grayscale + 8 bit alpha-channel 300 | basn4a16 - 16 bit grayscale + 16 bit alpha-channel 301 | basn6a08 - 3x8 bits rgb color + 8 bit alpha-channel 302 | basn6a16 - 3x16 bits rgb color + 16 bit alpha-channel 303 | 304 | 305 | 3.2 Basic format test files (Adam-7 interlaced) 306 | --------------------------------------------------- 307 | 308 | basi0g01 - black & white 309 | basi0g02 - 2 bit (4 level) grayscale 310 | basi0g04 - 4 bit (16 level) grayscale 311 | basi0g08 - 8 bit (256 level) grayscale 312 | basi0g16 - 16 bit (64k level) grayscale 313 | basi2c08 - 3x8 bits rgb color 314 | basi2c16 - 3x16 bits rgb color 315 | basi3p01 - 1 bit (2 color) paletted 316 | basi3p02 - 2 bit (4 color) paletted 317 | basi3p04 - 4 bit (16 color) paletted 318 | basi3p08 - 8 bit (256 color) paletted 319 | basi4a08 - 8 bit grayscale + 8 bit alpha-channel 320 | basi4a16 - 16 bit grayscale + 16 bit alpha-channel 321 | basi6a08 - 3x8 bits rgb color + 8 bit alpha-channel 322 | basi6a16 - 3x16 bits rgb color + 16 bit alpha-channel 323 | 324 | 325 | 3.3 Sizes test files 326 | ----------------------- 327 | 328 | s01n3p01 - 1x1 paletted file, no interlacing 329 | s02n3p01 - 2x2 paletted file, no interlacing 330 | s03n3p01 - 3x3 paletted file, no interlacing 331 | s04n3p01 - 4x4 paletted file, no interlacing 332 | s05n3p02 - 5x5 paletted file, no interlacing 333 | s06n3p02 - 6x6 paletted file, no interlacing 334 | s07n3p02 - 7x7 paletted file, no interlacing 335 | s08n3p02 - 8x8 paletted file, no interlacing 336 | s09n3p02 - 9x9 paletted file, no interlacing 337 | s32n3p04 - 32x32 paletted file, no interlacing 338 | s33n3p04 - 33x33 paletted file, no interlacing 339 | s34n3p04 - 34x34 paletted file, no interlacing 340 | s35n3p04 - 35x35 paletted file, no interlacing 341 | s36n3p04 - 36x36 paletted file, no interlacing 342 | s37n3p04 - 37x37 paletted file, no interlacing 343 | s38n3p04 - 38x38 paletted file, no interlacing 344 | s39n3p04 - 39x39 paletted file, no interlacing 345 | s40n3p04 - 40x40 paletted file, no interlacing 346 | 347 | s01i3p01 - 1x1 paletted file, interlaced 348 | s02i3p01 - 2x2 paletted file, interlaced 349 | s03i3p01 - 3x3 paletted file, interlaced 350 | s04i3p01 - 4x4 paletted file, interlaced 351 | s05i3p02 - 5x5 paletted file, interlaced 352 | s06i3p02 - 6x6 paletted file, interlaced 353 | s07i3p02 - 7x7 paletted file, interlaced 354 | s08i3p02 - 8x8 paletted file, interlaced 355 | s09i3p02 - 9x9 paletted file, interlaced 356 | s32i3p04 - 32x32 paletted file, interlaced 357 | s33i3p04 - 33x33 paletted file, interlaced 358 | s34i3p04 - 34x34 paletted file, interlaced 359 | s35i3p04 - 35x35 paletted file, interlaced 360 | s36i3p04 - 36x36 paletted file, interlaced 361 | s37i3p04 - 37x37 paletted file, interlaced 362 | s38i3p04 - 38x38 paletted file, interlaced 363 | s39i3p04 - 39x39 paletted file, interlaced 364 | s40i3p04 - 40x40 paletted file, interlaced 365 | 366 | 367 | 3.4 Background test files (with alpha) 368 | ------------------------------------------ 369 | 370 | bgai4a08 - 8 bit grayscale, alpha, no background chunk, interlaced 371 | bgai4a16 - 16 bit grayscale, alpha, no background chunk, interlaced 372 | bgan6a08 - 3x8 bits rgb color, alpha, no background chunk 373 | bgan6a16 - 3x16 bits rgb color, alpha, no background chunk 374 | 375 | bgbn4a08 - 8 bit grayscale, alpha, black background chunk 376 | bggn4a16 - 16 bit grayscale, alpha, gray background chunk 377 | bgwn6a08 - 3x8 bits rgb color, alpha, white background chunk 378 | bgyn6a16 - 3x16 bits rgb color, alpha, yellow background chunk 379 | 380 | 381 | 3.5 Transparency (and background) test files 382 | ------------------------------------------------ 383 | 384 | tp0n1g08 - not transparent for reference (logo on gray) 385 | tbbn1g04 - transparent, black background chunk 386 | tbwn1g16 - transparent, white background chunk 387 | tp0n2c08 - not transparent for reference (logo on gray) 388 | tbrn2c08 - transparent, red background chunk 389 | tbgn2c16 - transparent, green background chunk 390 | tbbn2c16 - transparent, blue background chunk 391 | tp0n3p08 - not transparent for reference (logo on gray) 392 | tp1n3p08 - transparent, but no background chunk 393 | tbbn3p08 - transparent, black background chunk 394 | tbgn3p08 - transparent, light-gray background chunk 395 | tbwn3p08 - transparent, white background chunk 396 | tbyn3p08 - transparent, yellow background chunk 397 | 398 | 399 | 3.6 Gamma test files 400 | ------------------------ 401 | 402 | g03n0g16 - grayscale, file-gamma = 0.35 403 | g04n0g16 - grayscale, file-gamma = 0.45 404 | g05n0g16 - grayscale, file-gamma = 0.55 405 | g07n0g16 - grayscale, file-gamma = 0.70 406 | g10n0g16 - grayscale, file-gamma = 1.00 407 | g25n0g16 - grayscale, file-gamma = 2.50 408 | g03n2c08 - color, file-gamma = 0.35 409 | g04n2c08 - color, file-gamma = 0.45 410 | g05n2c08 - color, file-gamma = 0.55 411 | g07n2c08 - color, file-gamma = 0.70 412 | g10n2c08 - color, file-gamma = 1.00 413 | g25n2c08 - color, file-gamma = 2.50 414 | g03n3p04 - paletted, file-gamma = 0.35 415 | g04n3p04 - paletted, file-gamma = 0.45 416 | g05n3p04 - paletted, file-gamma = 0.55 417 | g07n3p04 - paletted, file-gamma = 0.70 418 | g10n3p04 - paletted, file-gamma = 1.00 419 | g25n3p04 - paletted, file-gamma = 2.50 420 | 421 | 422 | 3.7 Filtering test files 423 | ---------------------------- 424 | 425 | f00n0g08 - grayscale, no interlacing, filter-type 0 426 | f01n0g08 - grayscale, no interlacing, filter-type 1 427 | f02n0g08 - grayscale, no interlacing, filter-type 2 428 | f03n0g08 - grayscale, no interlacing, filter-type 3 429 | f04n0g08 - grayscale, no interlacing, filter-type 4 430 | f00n2c08 - color, no interlacing, filter-type 0 431 | f01n2c08 - color, no interlacing, filter-type 1 432 | f02n2c08 - color, no interlacing, filter-type 2 433 | f03n2c08 - color, no interlacing, filter-type 3 434 | f04n2c08 - color, no interlacing, filter-type 4 435 | 436 | 437 | 3.8 Additional palette chunk test files 438 | ------------------------------------------- 439 | 440 | pp0n2c16 - six-cube palette-chunk in true-color image 441 | pp0n6a08 - six-cube palette-chunk in true-color+alpha image 442 | ps1n0g08 - six-cube suggested palette (1 byte) in grayscale image 443 | ps1n2c16 - six-cube suggested palette (1 byte) in true-color image 444 | ps2n0g08 - six-cube suggested palette (2 bytes) in grayscale image 445 | ps2n2c16 - six-cube suggested palette (2 bytes) in true-color image 446 | 447 | 448 | 3.9 Ancillary chunks test files 449 | ----------------------------------- 450 | 451 | cs5n2c08 - color, 5 significant bits 452 | cs8n2c08 - color, 8 significant bits (reference) 453 | cs3n2c16 - color, 13 significant bits 454 | cs3n3p08 - paletted, 3 significant bits 455 | cs5n3p08 - paletted, 5 significant bits 456 | cs8n3p08 - paletted, 8 significant bits (reference) 457 | 458 | cdfn2c08 - physical pixel dimensions, 8x32 flat pixels 459 | cdhn2c08 - physical pixel dimensions, 32x8 high pixels 460 | cdsn2c08 - physical pixel dimensions, 8x8 square pixels 461 | cdun2c08 - physical pixel dimensions, 1000 pixels per 1 meter 462 | 463 | ccwn2c08 - chroma chunk w:0.3127,0.3290 r:0.64,0.33 g:0.30,0.60 b:0.15,0.06 464 | ccwn3p08 - chroma chunk w:0.3127,0.3290 r:0.64,0.33 g:0.30,0.60 b:0.15,0.06 465 | 466 | ch1n3p04 - histogram 15 colors 467 | ch2n3p08 - histogram 256 colors 468 | 469 | cm7n0g04 - modification time, 01-jan-1970 00:00:00 470 | cm9n0g04 - modification time, 31-dec-1999 23:59:59 471 | cm0n0g04 - modification time, 01-jan-2000 12:34:56 472 | 473 | ct0n0g04 - no textual data 474 | ct1n0g04 - with textual data 475 | ctzn0g04 - with compressed textual data 476 | 477 | 478 | 479 | 3.10 Chunk ordering 480 | ---------------------- 481 | 482 | oi1n0g16 - grayscale mother image with 1 idat-chunk 483 | oi2n0g16 - grayscale image with 2 idat-chunks 484 | oi4n0g16 - grayscale image with 4 unequal sized idat-chunks 485 | oi9n0g16 - grayscale image with all idat-chunks length one 486 | oi1n2c16 - color mother image with 1 idat-chunk 487 | oi2n2c16 - color image with 2 idat-chunks 488 | oi4n2c16 - color image with 4 unequal sized idat-chunks 489 | oi9n2c16 - color image with all idat-chunks length one 490 | 491 | 492 | 493 | 3.11 Compression level 494 | ------------------------- 495 | 496 | z00n2c08 - color, no interlacing, compression level 0 (none) 497 | z03n2c08 - color, no interlacing, compression level 3 498 | z06n2c08 - color, no interlacing, compression level 6 (default) 499 | z09n2c08 - color, no interlacing, compression level 9 (maximum) 500 | 501 | 502 | 503 | 3.12 Currupted files 504 | ----------------------- 505 | 506 | x00n0g01 - empty 0x0 grayscale file 507 | xcrn0g04 - added cr bytes 508 | xlfn0g04 - added lf bytes 509 | xc0n0c08 - color type 0 510 | xc9n0c08 - color type 9 511 | xd0n2c00 - bit-depth 0 512 | xd3n2c03 - bit-depth 3 513 | xd9n2c99 - bit-depth 99 514 | xcsn2c08 - incorrect IDAT checksum 515 | 516 | 517 | -------- 518 | (c) Willem van Schaik 519 | willem@schaik.com 520 | Singapore, October 1996 521 | -------------------------------------------------------------------------------- /src/image_tiff.erl: -------------------------------------------------------------------------------- 1 | %%% File : image_tif.erl 2 | %%% Author : Tony Rogvall 3 | %%% Description : TIFF image format 4 | %%% Created : 6 Mar 2003 by Tony Rogvall 5 | 6 | -module(image_tiff). 7 | 8 | -include_lib("erl_img.hrl"). 9 | -include("tiff.hrl"). 10 | 11 | -include("api.hrl"). 12 | -include("dbg.hrl"). 13 | 14 | 15 | -export([scan_ifd/6, scan_ifd_bin/6]). 16 | -export([decode_tag/1]). 17 | 18 | -export([scan_fd/3, scan_file/3, scan_binary/3]). 19 | -export([dump_file/1, dump_binary/1]). 20 | 21 | -import(lists, [map/2, reverse/1]). 22 | 23 | -define(II, 16#4949). %% little-endian 24 | -define(MM, 16#4D4D). %% big-endian 25 | -define(MAGIC, 42). 26 | 27 | magic(<>) -> true; 28 | magic(<>) -> true; 29 | magic(_) -> false. 30 | 31 | mime_type() -> "image/tiff". 32 | 33 | extensions() -> [".tiff", ".tif" ]. 34 | 35 | 36 | read_info(Fd) -> 37 | case scan_fd(Fd, fun collect_fun/3, #erl_image { type = ?MODULE }) of 38 | {ok, IMG} -> 39 | Bps = erl_img:attribute(IMG, 'BitsPerSample'), 40 | Xs = erl_img:attribute(IMG,'ExtraSamples',[]), 41 | Format = 42 | case erl_img:attribute(IMG, 'PhotoMetricInterpretation') of 43 | [0] -> 44 | case Bps of 45 | [4] -> gray4; 46 | [8] -> gray8 47 | end; 48 | [1] -> 49 | case Bps of 50 | [4] -> gray4; 51 | [8] -> gray8 52 | end; 53 | [2] -> 54 | case Bps of 55 | [8,8,8] -> 56 | case Xs of 57 | [] -> r8g8b8; 58 | [_] -> r8g8b8a8 59 | end; 60 | [8,8,8,8] -> 61 | r8g8b8a8 62 | end; 63 | [3] -> 64 | case Bps of 65 | [4] -> palette4; 66 | [8] -> palette8 67 | end 68 | end, 69 | {ok, IMG#erl_image { format = Format }}; 70 | Error -> 71 | Error 72 | end. 73 | 74 | 75 | write_info(_Fd, _IMG) -> 76 | ok. 77 | 78 | read(Fd, IMG) -> 79 | read(Fd, IMG, 80 | fun(_, Row, Ri, St) -> 81 | ?dbg("tiff: load row ~p\n", [Ri]), 82 | [{Ri,Row}|St] end, 83 | []). 84 | 85 | 86 | read(Fd,IMG, RowFun, St0) -> 87 | SOffset = erl_img:attribute(IMG, 'StripOffset'), 88 | SCount = erl_img:attribute(IMG, 'StripByteCounts'), 89 | [Compression] = erl_img:attribute(IMG, 'Compression',[1]), 90 | [Predict] = erl_img:attribute(IMG, 'Predictor', [0]), 91 | [FillOrder] = erl_img:attribute(IMG, 'FillOrder', [1]), 92 | {SampleOrder,Y0,Ys} = case erl_img:attribute(IMG, 'Orientation', [1]) of 93 | [1] -> {left_to_right, 0, 1}; 94 | [2] -> {right_to_left, 0, 1}; 95 | [3] -> {right_to_left, IMG#erl_image.height-1,-1}; 96 | [4] -> {left_to_right, IMG#erl_image.height-1,-1} 97 | end, 98 | BytesPerRow = (IMG#erl_image.depth div 8) * IMG#erl_image.width, 99 | ?dbg("BytesPerRow = ~p\n", [BytesPerRow]), 100 | IMG1 = IMG#erl_image { order = SampleOrder }, 101 | PIX = #erl_pixmap { width = IMG1#erl_image.width, 102 | height = IMG1#erl_image.height, 103 | format = IMG1#erl_image.format }, 104 | case read_strips(Fd,PIX,RowFun,St0,Y0,Ys,BytesPerRow, 105 | Compression, Predict, FillOrder,SOffset,SCount) of 106 | {ok, PIX1} -> 107 | {ok, IMG1#erl_image { pixmaps = [PIX1]}}; 108 | Error -> 109 | Error 110 | end. 111 | 112 | 113 | read_strips(_Fd,PIX,_RowFun,St0,_Ri,_Rs, 114 | _BytesPerRow,_Compression, _Predict, _Fill, [], []) -> 115 | {ok, PIX#erl_pixmap { pixels = St0 }}; 116 | read_strips(Fd,PIX,RowFun,St0,Ri,Rs, 117 | BytesPerRow,Compression, Predict, Fill, 118 | [Offs|SOffset], [Size|SCount]) -> 119 | case file:pread(Fd,Offs,Size) of 120 | {ok,Bin} -> 121 | case Compression of 122 | 1 -> %% no compression 123 | {St1,Rj} = split_strip(PIX,Bin,BytesPerRow, 124 | RowFun,St0,Ri,Rs), 125 | read_strips(Fd,PIX,RowFun,St1,Rj,Rs,BytesPerRow, 126 | Compression,Predict,Fill,SOffset,SCount); 127 | 128 | 5 -> %% lzw compression 129 | Bin1 = lzw:decompress_tiff(Bin, 8, Fill), 130 | Bin2 = undo_differencing(Bin1, Predict, 131 | PIX#erl_pixmap.format, 132 | PIX#erl_pixmap.width), 133 | {St1,Rj} = split_strip(PIX,Bin2,BytesPerRow, 134 | RowFun,St0,Ri,Rs), 135 | read_strips(Fd,PIX,RowFun,St1,Rj,Rs,BytesPerRow, 136 | Compression, Predict, Fill, SOffset, SCount); 137 | 138 | 32773 -> 139 | Bin1 = unpack_bits(Bin), 140 | {St1,Rj} = split_strip(PIX,Bin1,BytesPerRow, 141 | RowFun,St0,Ri,Rs), 142 | read_strips(Fd,PIX,RowFun,St1,Rj,Rs,BytesPerRow, 143 | Compression, Predict, Fill, SOffset, SCount); 144 | _ -> 145 | {error, {unknown_compression,Compression}} 146 | end; 147 | Error -> 148 | Error 149 | end. 150 | 151 | 152 | split_strip(PIX,Strip,RowWidth,RowFun,St0,Ri,Rs) -> 153 | case Strip of 154 | <> -> 155 | St1 = RowFun(PIX,Row,Ri,St0), 156 | split_strip(PIX,Tail,RowWidth,RowFun,St1,Ri+Rs,Rs); 157 | _ -> 158 | {St0,Ri} 159 | end. 160 | 161 | 162 | write(_Fd,_IMG) -> 163 | ok. 164 | 165 | %% Image info collector functions 166 | collect_fun(_Fd, T, St) -> 167 | Key = decode_tag(T#tiff_entry.tag), 168 | Value = T#tiff_entry.value, 169 | As = [{Key,Value} | St#erl_image.attributes], 170 | case Key of 171 | 'ImageWidth' -> 172 | [Width] = Value, 173 | St#erl_image { width = Width, attributes = As }; 174 | 'ImageLength' -> 175 | [Length] = Value, 176 | St#erl_image { height = Length, attributes = As }; 177 | 'BitsPerSample' -> 178 | St#erl_image { depth = lists:sum(Value), attributes = As }; 179 | 'ImageDescription' -> 180 | St#erl_image { comment = Value, attributes = As }; 181 | 'DateTime' -> 182 | [V] = Value, 183 | case string:tokens(V, ": ") of 184 | [YYYY,MM,DD,H,M,S] -> 185 | DateTime = {{list_to_integer(YYYY), 186 | list_to_integer(MM), 187 | list_to_integer(DD)}, 188 | {list_to_integer(H), 189 | list_to_integer(M), 190 | list_to_integer(S)}}, 191 | St#erl_image { itime = DateTime, attributes = As }; 192 | _ -> 193 | St#erl_image { attributes = As } 194 | end; 195 | 196 | _ -> 197 | St#erl_image { attributes = As } 198 | end. 199 | 200 | 201 | dump_fun(_Fd, T, St) -> 202 | Key = decode_tag(T#tiff_entry.tag), 203 | io:format("~s ~s ~w\n", [Key,T#tiff_entry.type,T#tiff_entry.value]), 204 | St. 205 | 206 | dump_binary(Bin) when is_binary(Bin) -> 207 | scan_binary(Bin, fun dump_fun/3, ok). 208 | 209 | dump_file(File) -> 210 | scan_file(File, fun dump_fun/3, ok). 211 | 212 | 213 | scan_file(File, Callback, St) -> 214 | case file:open(File, [raw, binary, read]) of 215 | {ok,Fd} -> 216 | Res = scan_fd(Fd, Callback, St), 217 | file:close(Fd), 218 | Res; 219 | Error -> 220 | Error 221 | end. 222 | 223 | scan_binary(Bin, Callback, St) -> 224 | case file:open(Bin, [ram, binary, read]) of 225 | {ok,Fd} -> 226 | Res = scan_fd(Fd, Callback, St), 227 | file:close(Fd), 228 | Res; 229 | Error -> 230 | Error 231 | end. 232 | 233 | 234 | scan_fd(Fd, Callback, St) -> 235 | case file:read(Fd, 8) of 236 | {ok, <>} -> 237 | %% io:format("TIFF: LITTLE endian\n"), 238 | scan_ifd(Fd, [$0], Offset, little, Callback, St); 239 | {ok, <>} -> 240 | %% io:format("TIFF: BIG endian\n"), 241 | scan_ifd(Fd, [$0], Offset, big, Callback, St); 242 | {ok,_} -> 243 | {error, bad_magic}; 244 | Error -> 245 | Error 246 | end. 247 | 248 | %% Scan entry point for special Exif/MakerNote 249 | scan_ifd_bin(Bin, IFD, Offset, Endian, Callback, St) -> 250 | case file:open(Bin, [ram, binary, read]) of 251 | {ok,Fd} -> 252 | Res = scan_ifd(Fd, IFD, Offset, Endian, Callback, St), 253 | file:close(Fd), 254 | Res; 255 | Error -> 256 | Error 257 | end. 258 | 259 | scan_ifd(_Fd, _IFD, 0, _Endian, _Callback, St) -> 260 | {ok,St}; 261 | scan_ifd(Fd, IFD, Offset, Endian, Callback,St) -> 262 | file:position(Fd, Offset), 263 | case read_u16(Fd,Endian) of 264 | {ok,N} -> 265 | scan_entries(Fd, IFD, Endian, N, Callback, St); 266 | Error -> Error 267 | end. 268 | 269 | scan_entries(Fd, [I|IFD], Endian, 0, Callback, St) -> 270 | case read_u32(Fd,Endian) of 271 | {ok,Offset} -> 272 | scan_ifd(Fd,[I+1|IFD],Offset,Endian,Callback,St); 273 | Error -> Error 274 | end; 275 | scan_entries(Fd, IFD, Endian, I, Callback,St) -> 276 | case read_entry(Fd,Endian) of 277 | {ok,{Tag,Type,N,Data}} -> 278 | TiffTag = 279 | case is_value(Type, N) of 280 | true -> 281 | Value = decode_value(Type,Endian,N,Data), 282 | #tiff_entry { ifd = IFD, 283 | tag = Tag, 284 | endian = Endian, 285 | type = Type, 286 | value = Value }; 287 | false -> 288 | Offset = if Endian == little -> 289 | <> = Data, Offs; 290 | true -> 291 | <> = Data, Offs 292 | end, 293 | Value = decode_offs_value(Fd,Offset,Type,Endian,N), 294 | #tiff_entry { ifd = IFD, 295 | tag = Tag, 296 | endian = Endian, 297 | type = Type, 298 | offs = Offset, 299 | value = Value } 300 | end, 301 | %% Save file position inorder to allwo callback to parse Sub ifd's 302 | {ok,Save} = file:position(Fd, cur), 303 | St1 = Callback(Fd, TiffTag, St), 304 | file:position(Fd, Save), 305 | scan_entries(Fd, IFD, Endian, I-1, Callback, St1); 306 | Error -> Error 307 | end. 308 | 309 | read_u16(Fd, Endian) -> 310 | case file:read(Fd, 2) of 311 | {ok, <>} when Endian == little -> {ok, N}; 312 | {ok, <>} when Endian == big -> {ok, N}; 313 | {ok, _} -> {error, truncated}; 314 | Error -> Error 315 | end. 316 | 317 | read_u32(Fd, Endian) -> 318 | case file:read(Fd, 4) of 319 | {ok, <>} when Endian == little -> {ok, N}; 320 | {ok, <>} when Endian == big -> {ok, N}; 321 | {ok, _} -> {error, truncated}; 322 | Error -> Error 323 | end. 324 | 325 | read_entry(Fd,Endian) -> 326 | case file:read(Fd,12) of 327 | {ok,<>} when 328 | Endian == little -> 329 | {ok,{Tag,decode_type(T),N,V}}; 330 | {ok,<>} when 331 | Endian == big -> 332 | {ok,{Tag,decode_type(T),N,V}}; 333 | {ok,_} -> {error, truncated}; 334 | Error -> Error 335 | end. 336 | 337 | 338 | %% Decode TIFF types 339 | decode_type(?BYTE) -> byte; 340 | decode_type(?ASCII) -> ascii; 341 | decode_type(?SHORT) -> short; 342 | decode_type(?LONG) -> long; 343 | decode_type(?RATIONAL) -> rational; 344 | decode_type(?SBYTE) -> sbyte; 345 | decode_type(?UNDEFINED) -> undefined; 346 | decode_type(?SSHORT) -> sshort; 347 | decode_type(?SLONG) -> slong; 348 | decode_type(?SRATIONAL) -> srational; 349 | decode_type(?FLOAT) -> float; 350 | decode_type(?DOUBLE) -> double; 351 | decode_type(_) -> unknown. 352 | 353 | decode_tag(Tag) -> 354 | case Tag of 355 | ?NewSubfileType -> 'NewSubfileType'; 356 | ?SubfileType -> 'SubfileType'; 357 | ?ImageWidth -> 'ImageWidth'; 358 | ?ImageLength -> 'ImageLength'; 359 | ?BitsPerSample -> 'BitsPerSample'; 360 | ?Compression -> 'Compression'; 361 | ?PhotoMetricInterpretation -> 'PhotoMetricInterpretation'; 362 | ?Threshholding -> 'Threshholding'; 363 | ?CellWidth -> 'CellWidth'; 364 | ?CellLength -> 'CellLength'; 365 | ?FillOrder -> 'FillOrder'; 366 | ?DocumentName -> 'DocumentName'; 367 | ?ImageDescription -> 'ImageDescription'; 368 | ?Make -> 'Make'; 369 | ?Model -> 'Model'; 370 | ?StripOffset -> 'StripOffset'; 371 | ?Orientation -> 'Orientation'; 372 | ?SamplesPerPixel -> 'SamplesPerPixel'; 373 | ?RowsPerStrip -> 'RowsPerStrip'; 374 | ?StripByteCounts -> 'StripByteCounts'; 375 | ?MinSampleValue -> 'MinSampleValue'; 376 | ?MaxSampleValue -> 'MaxSampleValue'; 377 | ?XResolution -> 'XResolution'; 378 | ?YResolution -> 'YResolution'; 379 | ?PlanarConfiguration -> 'PlanarConfiguration'; 380 | ?PageName -> 'PageName'; 381 | ?XPosition -> 'XPosition'; 382 | ?YPosition -> 'YPosition'; 383 | ?FreeOffsets -> 'FreeOffsets'; 384 | ?FreeByteCounts -> 'FreeByteCounts'; 385 | ?GrayResponseUnit -> 'GrayResponseUnit'; 386 | ?GrayResponseCurve -> 'GrayResponseCurve'; 387 | ?T4Options -> 'T4Options'; 388 | ?T6Options -> 'T6Options'; 389 | ?ResolutionUnit -> 'ResolutionUnit'; 390 | ?PageNumber -> 'PageNumber'; 391 | ?TransferFunction -> 'TransferFunction'; 392 | ?Software -> 'Software'; 393 | ?DateTime -> 'DateTime'; 394 | ?Artist -> 'Artist'; 395 | ?HostComputer -> 'HostComputer'; 396 | ?Predictor -> 'Predictor'; 397 | ?WhitePoint -> 'WhitePoint'; 398 | ?PrimaryChromaticities -> 'PrimaryChromaticities'; 399 | ?ColorMap -> 'ColorMap'; 400 | ?HalftoneHints -> 'HalftoneHints'; 401 | ?TileWidth -> 'TileWidth'; 402 | ?TileLength -> 'TileLength'; 403 | ?TileOffset -> 'TileOffset'; 404 | ?TileByteCounts -> 'TileByteCounts'; 405 | ?InkSet -> 'InkSet'; 406 | ?InkNames -> 'InkNames'; 407 | ?NumberOfInks -> 'NumberOfInks'; 408 | ?DotRange -> 'DotRange'; 409 | ?TargetPrinter -> 'TargetPrinter'; 410 | ?ExtraSamples -> 'ExtraSamples'; 411 | ?SampleFormat -> 'SampleFormat'; 412 | ?SMinSampleValue -> 'SMinSampleValue'; 413 | ?SMaxSampleValue -> 'SMaxSampleValue'; 414 | ?TransferRange -> 'TransferRange'; 415 | ?JPEGProc -> 'JPEGProc'; 416 | ?JPEGInterchangeFormat -> 'JPEGInterchangeFormat'; 417 | ?JPEGInterchangeFormatLength -> 'JPEGInterchangeFormatLength'; 418 | ?JPEGRestartInterval -> 'JPEGRestartInterval'; 419 | ?JPEGLosslessPredictors -> 'JPEGLosslessPredictors'; 420 | ?JPEGPointTransforms -> 'JPEGPointTransforms'; 421 | ?JPEGQTables -> 'JPEGQTables'; 422 | ?JPEGDCTables -> 'JPEGDCTables'; 423 | ?JPEGACTables -> 'JPEGACTables'; 424 | ?YCbCrCoefficients -> 'YCbCrCoefficients'; 425 | ?YCbCrSampling -> 'YCbCrSampling'; 426 | ?YCbCrPositioning -> 'YCbCrPositioning'; 427 | ?ReferenceBlackWhite -> 'ReferenceBlackWhite'; 428 | ?Copyright -> 'Copyright'; 429 | _ -> Tag 430 | end. 431 | 432 | 433 | is_value(byte,N) when N =< 4 -> true; 434 | is_value(sbyte,N) when N =< 4 -> true; 435 | is_value(undefined,N) when N =< 4 -> true; 436 | is_value(ascii,N) when N =< 4 -> true; 437 | is_value(short,N) when N =< 2 -> true; 438 | is_value(sshort,N) when N =< 2 -> true; 439 | is_value(long,1) -> true; 440 | is_value(slong,1) -> true; 441 | is_value(float,1) -> true; 442 | is_value(_,_) -> false. 443 | 444 | %% calculate the size of the type 445 | sizeof(byte) -> 1; 446 | sizeof(ascii) -> 1; 447 | sizeof(short) -> 2; 448 | sizeof(long) -> 4; 449 | sizeof(rational) -> 8; 450 | sizeof(sbyte) -> 1; 451 | sizeof(undefined) -> 1; 452 | sizeof(sshort) -> 2; 453 | sizeof(slong) -> 4; 454 | sizeof(srational) -> 8; 455 | sizeof(float) -> 4; 456 | sizeof(double) -> 8; 457 | sizeof(_) -> 0. 458 | 459 | %% decode an offseted value 460 | decode_offs_value(Fd,Offset,Type,Endian,N) -> 461 | Sz = sizeof(Type)*N, 462 | case file:pread(Fd, Offset, Sz) of 463 | {ok,Bin} when size(Bin) == Sz -> 464 | decode_value(Type,Endian,N,Bin); 465 | {ok,_} -> 466 | {error, truncated}; 467 | Error -> 468 | Error 469 | end. 470 | 471 | decode_value(_,_,0,_) -> []; 472 | 473 | %% little 474 | decode_value(short,little,I,<>) -> 475 | [V|decode_value(short,little,I-1,VT)]; 476 | 477 | decode_value(long,little,I,<>) -> 478 | [V|decode_value(long, little,I-1,VT)]; 479 | 480 | decode_value(rational,little,I, <>) -> 481 | [{N,D}|decode_value(rational,little,I-1,VT)]; 482 | 483 | decode_value(sshort,little,I,<>) -> 484 | [V|decode_value(sshort,little,I-1,VT)]; 485 | 486 | decode_value(slong,little,I,<>) -> 487 | [V|decode_value(slong, little,I-1,VT)]; 488 | 489 | decode_value(srational,little,I, 490 | <>) -> 491 | [{N,D}|decode_value(srational,little,I-1,VT)]; 492 | 493 | decode_value(float,little,I,<>) -> 494 | [V|decode_value(float, little,I-1,VT)]; 495 | 496 | decode_value(double,little,I,<>) -> 497 | [V|decode_value(double,little,I-1,VT)]; 498 | 499 | %% big 500 | decode_value(short,big,I,<>) -> 501 | [V|decode_value(short,big,I-1,VT)]; 502 | 503 | decode_value(long,big,I,<>) -> 504 | [V|decode_value(long,big,I-1,VT)]; 505 | 506 | decode_value(rational,big,I,<>) -> 507 | [{N,D}|decode_value(rational,big,I-1,VT)]; 508 | 509 | decode_value(sshort,big,I,<>) -> 510 | [V|decode_value(sshort,big,I-1,VT)]; 511 | 512 | decode_value(slong,big,I,<>) -> 513 | [V|decode_value(slong,big,I-1,VT)]; 514 | 515 | decode_value(srational,big,I,<>) -> 516 | [{N,D}|decode_value(srational,big,I-1,VT)]; 517 | 518 | decode_value(float,big,I,<>) -> 519 | [V|decode_value(float,big,I-1,VT)]; 520 | 521 | decode_value(double,big,I,<>) -> 522 | [V|decode_value(double,big,I-1,VT)]; 523 | 524 | %% Any endian single fields 525 | decode_value(sbyte,_Endian,N,Bin) -> 526 | <> = Bin, 527 | map(fun(I) when I >= 16#80 -> I - 16#100; 528 | (I) -> I 529 | end, binary_to_list(V)); 530 | 531 | 532 | decode_value(byte,_Endian,N,Bin) -> 533 | <> = Bin, 534 | binary_to_list(V); 535 | decode_value(ascii,_Endian,N,Bin) -> 536 | <> = Bin, 537 | decode_strings(binary_to_list(V)); 538 | decode_value(undefined,_Endian,N,Bin) -> 539 | <> = Bin, 540 | V; 541 | decode_value(unknown,_Endian,_N,Bin) -> 542 | Bin. 543 | 544 | 545 | %% decode a sequence of strings 546 | decode_strings(Cs) -> 547 | decode_strings(Cs,[],[]). 548 | 549 | decode_strings([0|Cs], String, Acc) -> 550 | decode_strings(Cs, [], [reverse(String)|Acc]); 551 | decode_strings([C|Cs], String, Acc) -> 552 | decode_strings(Cs, [C|String], Acc); 553 | decode_strings([], [], Acc) -> 554 | reverse(Acc); 555 | decode_strings([], String, Acc) -> 556 | reverse([reverse(String)|Acc]). 557 | 558 | 559 | 560 | undo_differencing(Data,2,r8g8b8a8,Width) -> 561 | undo_differencing4(Data, Width); 562 | undo_differencing(Data,2,r8g8b8,Width) -> 563 | undo_differencing3(Data, Width); 564 | undo_differencing(Data,_,_,_) -> 565 | Data. 566 | 567 | undo_differencing4(Data, Width) -> 568 | if is_binary(Data) -> 569 | undo_differencing4(0, Width, binary_to_list(Data),0,0,0,0, []); 570 | is_list(Data) -> 571 | undo_differencing4(0, Width, Data, 0,0,0,0, []) 572 | end. 573 | 574 | 575 | undo_differencing4(W, W, Rest, _,_,_,_,Ack) -> 576 | undo_differencing4(0,W, Rest, 0,0,0,0,Ack); 577 | undo_differencing4(C, W, [R,G,B,A|Rest], AR,AG,AB,AA, Ack) -> 578 | %% io:format("undo ~p ~n", [[{R,G,B,A}, {AR,AG,AB,AA}]]), 579 | RR = (R + AR) rem 256, 580 | RG = (G + AG) rem 256, 581 | RB = (B + AB) rem 256, 582 | RA = (A + AA) rem 256, 583 | undo_differencing4(C+1, W, Rest, RR,RG,RB,RA, [RA,RB,RG,RR|Ack]); 584 | undo_differencing4(_, _, [], _,_,_,_, Ack) -> 585 | list_to_binary(reverse(Ack)). 586 | 587 | 588 | undo_differencing3(Data, Width) -> 589 | if is_binary(Data) -> 590 | undo_differencing3(0, Width, binary_to_list(Data),0,0,0, []); 591 | is_list(Data) -> 592 | undo_differencing3(0, Width, Data, 0, 0, 0, []) 593 | end. 594 | 595 | undo_differencing3(W, W, Rest, _,_,_, Ack) -> 596 | undo_differencing3(0,W, Rest, 0,0,0, Ack); 597 | undo_differencing3(C, W, [R,G,B|Rest], AR,AG,AB, Ack) -> 598 | RR = (R + AR) rem 256, 599 | RG = (G + AG) rem 256, 600 | RB = (B + AB) rem 256, 601 | undo_differencing3(C+1, W, Rest, RR,RG,RB, [RB,RG,RR|Ack]); 602 | undo_differencing3(_, _, [], _,_,_, Ack) -> 603 | list_to_binary(reverse(Ack)). 604 | 605 | 606 | unpack_bits(Bin) -> 607 | unpack_bits(Bin, []). 608 | 609 | unpack_bits(<<>>, Acc) -> 610 | list_to_binary(lists:reverse(Acc)); 611 | unpack_bits(<<128:8/signed,Tail/binary>>, Acc) -> 612 | unpack_bits(Tail, Acc); 613 | unpack_bits(<>, Acc) when Code >= 0 -> 614 | Count = Code + 1, 615 | <> = Tail, 616 | unpack_bits(Tail1, [Bin1|Acc]); 617 | unpack_bits(<>, Acc) -> 618 | Count = -Code + 1, 619 | <> = Tail, 620 | unpack_bits(Tail1, [list_to_binary(lists:duplicate(Count, Re))|Acc]). 621 | -------------------------------------------------------------------------------- /src/image_png.erl: -------------------------------------------------------------------------------- 1 | %%% File : image_png.erl 2 | %%% Author : Tony Rogvall 3 | %%% Description : PNG Files 4 | %%% Created : 5 Mar 2003 by Tony Rogvall 5 | 6 | -module(image_png). 7 | 8 | -include_lib("erl_img.hrl"). 9 | -include("api.hrl"). 10 | 11 | -include("dbg.hrl"). 12 | 13 | -import(lists, [reverse/1]). 14 | -import(erl_img, [attribute/3, set_attribute/3]). 15 | -export([filter/4]). 16 | 17 | -define(MAGIC, 137,$P,$N,$G,$\r,$\n,26,$\n). 18 | 19 | -define(IHDR, "IHDR"). %% image header 20 | -define(PLTE, "PLTE"). %% palette 21 | -define(IDAT, "IDAT"). %% image data 22 | -define(IEND, "IEND"). %% image trailer 23 | 24 | -define(bKGD, "bKGD"). %% background color 25 | -define(cHRM, "cHRM"). %% primary chromaticites and white point 26 | -define(gAMA, "gAMA"). %% Image gamma 27 | -define(hIST, "hIST"). %% Image histogram 28 | -define(pHYs, "pHYs"). %% Physical pixel dimensions 29 | -define(sBIT, "sBIT"). %% Significant bits 30 | -define(tEXt, "tEXt"). %% Textual data 31 | -define(tIME, "tIME"). %% Image last modification time 32 | -define(tRNS, "tRNS"). %% Transparency 33 | -define(zTXt, "zTXt"). %% Compressed textual data 34 | 35 | magic(<>) -> true; 36 | magic(_) -> false. 37 | 38 | mime_type() -> "image/png". 39 | 40 | extensions() -> [ ".png" ]. 41 | 42 | read_info(Fd) -> 43 | case file:read(Fd, 8) of 44 | {ok, << ?MAGIC >> } -> 45 | scan_info(Fd, #erl_image { type = ?MODULE }, true); 46 | {ok, _} -> 47 | {error, bad_magic}; 48 | Error -> 49 | Error 50 | end. 51 | 52 | scan_info(Fd, IMG, First) -> 53 | case read_chunk_hdr(Fd) of 54 | {ok, Length, Type} -> 55 | scan_info(Fd, IMG, First, Type, Length); 56 | Error -> 57 | Error 58 | end. 59 | 60 | scan_info(Fd, IMG, true, ?IHDR, Length) -> 61 | case read_chunk_crc(Fd,Length) of 62 | {ok, <>} -> 65 | scan_info(Fd, IMG#erl_image { 66 | width = Width, 67 | height = Height, 68 | depth = BitDepth, 69 | format = format(ColorType,BitDepth), 70 | order = left_to_right, 71 | attributes = 72 | [ {'ColorType', ColorType}, 73 | {'Compression', CompressionMethod}, 74 | {'Filter', FilterMethod }, 75 | {'Interlace', InterlaceMethod }]}, false); 76 | Error -> Error 77 | end; 78 | scan_info(Fd, IMG, false, ?tEXt, Length) -> 79 | case read_chunk_crc(Fd, Length) of 80 | {ok, Bin} -> 81 | scan_info(Fd, update_txt(IMG, Bin), false); 82 | Error -> Error 83 | end; 84 | scan_info(Fd, IMG, false, ?zTXt, Length) -> 85 | case read_chunk_crc(Fd, Length) of 86 | {ok, CBin} -> 87 | Bin = zlib:uncompress(CBin), 88 | scan_info(Fd, update_txt(IMG, Bin), false); 89 | Error -> Error 90 | end; 91 | scan_info(Fd, IMG, false, ?bKGD, Length) -> 92 | CT = attribute(IMG, 'ColorType', undefined), 93 | case read_chunk_crc(Fd, Length) of 94 | {ok, <>} when CT==3 -> 95 | scan_info(Fd, set_attribute(IMG, 'Background', Index), false); 96 | {ok, <>} when CT==0; CT==4 -> 97 | scan_info(Fd, set_attribute(IMG, 'Background', Gray), false); 98 | {ok, <>} when CT==2; CT==6 -> 99 | scan_info(Fd, set_attribute(IMG, 'Background', {R,G,B}), false); 100 | {ok, _Data} -> 101 | ?dbg("bKGD other=~p\n", [_Data]), 102 | scan_info(Fd, IMG, false); 103 | Error -> Error 104 | end; 105 | scan_info(Fd, IMG, false, ?tIME, Length) -> 106 | case read_chunk_crc(Fd, Length) of 107 | {ok, <>} -> 108 | scan_info(Fd, IMG#erl_image { mtime = {{Year,Mon,Day}, 109 | {H,M,S}} }, false); 110 | {ok, _Data} -> 111 | ?dbg("tIME other=~p\n", [_Data]), 112 | scan_info(Fd, IMG, false); 113 | Error -> Error 114 | end; 115 | scan_info(Fd, IMG, false, ?pHYs, Length) -> 116 | case read_chunk_crc(Fd, Length) of 117 | {ok, <>} -> 118 | scan_info(Fd, set_attribute(IMG,'Physical',{X,Y,meter}),false); 119 | {ok, _Data} -> 120 | ?dbg("pHYs other=~p\n", [_Data]), 121 | scan_info(Fd, IMG, false); 122 | Error -> Error 123 | end; 124 | scan_info(_Fd, IMG, false, ?IEND, 0) -> 125 | {ok, IMG}; 126 | scan_info(Fd, IMG, false, _Type, Length) -> 127 | ?dbg("~s skipped=~p\n", [_Type,Length]), 128 | skip_chunk(Fd, Length), 129 | scan_info(Fd, IMG, false). 130 | 131 | %% Update txt attributes 132 | update_txt(IMG, Txt) -> 133 | case txt(binary_to_list(Txt), []) of 134 | {value,{Key,Value}} -> 135 | case Key of 136 | 'Comment' -> 137 | IMG#erl_image { comment = Value }; 138 | _ -> 139 | As = [{Key,Value} | IMG#erl_image.attributes], 140 | IMG#erl_image { attributes = As } 141 | end; 142 | false -> 143 | IMG 144 | end. 145 | 146 | 147 | %% determine the erl_image format 148 | bytes_per_row(gray1,W) -> W div 8; 149 | bytes_per_row(gray2,W) -> W div 4; 150 | bytes_per_row(gray4,W) -> W div 2; 151 | bytes_per_row(gray8,W) -> W; 152 | bytes_per_row(gray16,W) -> W*2; 153 | bytes_per_row(r8g8b8,W) -> W*3; 154 | bytes_per_row(r16g16b16,W) -> W*6; 155 | bytes_per_row(palette1,W) -> W div 8; 156 | bytes_per_row(palette2,W) -> W div 4; 157 | bytes_per_row(palette4,W) -> W div 2; 158 | bytes_per_row(palette8,W) -> W; 159 | bytes_per_row(gray8a8,W) -> W*2; 160 | bytes_per_row(gray16a16,W) -> W*4; 161 | bytes_per_row(r8g8b8a8,W) -> W*4; 162 | bytes_per_row(r16g16b16a16,W) -> W*8. 163 | 164 | 165 | bpp(gray1) -> 1; 166 | bpp(gray2) -> 1; 167 | bpp(gray4) -> 1; 168 | bpp(gray8) -> 1; 169 | bpp(gray16) -> 2; 170 | bpp(r8g8b8) -> 3; 171 | bpp(r16g16b16) -> 6; 172 | bpp(palette1) -> 1; 173 | bpp(palette2) -> 1; 174 | bpp(palette4) -> 1; 175 | bpp(palette8) -> 1; 176 | bpp(gray8a8) -> 2; 177 | bpp(gray16a16) -> 4; 178 | bpp(r8g8b8a8) -> 4; 179 | bpp(r16g16b16a16) -> 8. 180 | 181 | 182 | format(0, 1) -> gray1; 183 | format(0, 2) -> gray2; 184 | format(0, 4) -> gray4; 185 | format(0, 8) -> gray8; 186 | format(0, 16) -> gray16; 187 | format(2, 8) -> r8g8b8; 188 | format(2, 16) -> r16g16b16; 189 | format(3, 1) -> palette1; 190 | format(3, 2) -> palette2; 191 | format(3, 4) -> palette4; 192 | format(3, 8) -> palette8; 193 | format(4, 8) -> gray8a8; 194 | format(4, 16) -> gray16a16; 195 | format(6, 8) -> r8g8b8a8; 196 | format(6, 16) -> r16g16b16a16. 197 | 198 | %% process text chunk 199 | txt([0|Value], RKey) -> 200 | {value, {list_to_atom(reverse(RKey)), Value}}; 201 | txt([C|Cs], RKey) -> 202 | txt(Cs,[C|RKey]); 203 | txt([], _) -> 204 | false. 205 | 206 | %% read palette 207 | plte(<>) -> 208 | [{R,G,B} | plte(Data)]; 209 | plte(<<>>) -> []. 210 | 211 | %% IMPLEMENT This: 212 | write_info(_Fd, _IMG) -> 213 | ok. 214 | 215 | 216 | read(Fd, IMG) -> 217 | read(Fd, IMG, 218 | fun(_, Row, Ri, St) -> 219 | ?dbg("png: load row ~p\n", [Ri]), 220 | [{Ri,Row}|St] end, 221 | []). 222 | 223 | 224 | read(Fd, IMG, RowFun, St0) -> 225 | file:position(Fd, 8), %% skip magic 226 | Z = zlib:open(), 227 | zlib:inflateInit(Z), 228 | Resp = read_image(Fd, [], undefined, Z), 229 | zlib:close(Z), 230 | case Resp of 231 | {ok, Binary, Palette} -> 232 | {ok,Pixmap} = create_pixmap(IMG, Binary, Palette, RowFun, St0), 233 | {ok, IMG#erl_image { pixmaps = [Pixmap], 234 | palette = Palette }}; 235 | Error -> Error 236 | end. 237 | 238 | create_pixmap(IMG, Bin, Palette, RowFun, St0) -> 239 | Interlace = attribute(IMG, 'Interlace', 0), 240 | Pix0 = #erl_pixmap { width = IMG#erl_image.width, 241 | height = IMG#erl_image.height, 242 | palette = Palette, 243 | format = IMG#erl_image.format }, 244 | Bpp = bpp(IMG#erl_image.format), 245 | BytesPerRow = bytes_per_row( 246 | IMG#erl_image.format,IMG#erl_image.width), 247 | case Interlace of 248 | 0 -> 249 | raw_data(Bin,Pix0,RowFun,St0,0,Bpp,BytesPerRow); 250 | 1 -> 251 | interlaced_data(Bin, Pix0, RowFun, St0, Bpp) 252 | end. 253 | 254 | 255 | raw_data(Bin,Pix,RowFun,St0,Ri,Bpp,Width) -> 256 | case Bin of 257 | <> -> 258 | Prior = case St0 of 259 | [] -> <<>>; 260 | [{_,Row0}|_] -> Row0 261 | end, 262 | Row1 = filter(Filter,Bpp,Row,Prior), %% Filter method=0 assumed 263 | St1 = RowFun(Pix,Row1,Ri,St0), 264 | raw_data(Bin1,Pix,RowFun,St1,Ri+1,Bpp,Width); 265 | _ -> 266 | {ok, Pix#erl_pixmap { pixels = St0 }} 267 | end. 268 | 269 | interlaced_data(Bin, Pix0=#erl_pixmap{width=Width, height=Height}, 270 | RowFun, St0, Bpp) -> 271 | [P1, P2, P3, P4, P5, P6, P7] = interlaced_pass(Bin, Width, Height, Bpp, 1), 272 | Passes = {P1, P2, P3, P4, P5, P6, P7}, 273 | Cols = lists:seq(0, Width - 1), 274 | merge_adam7(Passes, Pix0, RowFun, St0, 0, Bpp, Cols, Height). 275 | 276 | merge_adam7(_P, Pix, _RowFun, St0, Height, _Bpp, _Cols, Height) -> 277 | {ok, Pix#erl_pixmap{pixels=St0}}; 278 | merge_adam7(P, Pix, RowFun, St0, Ri, Bpp, Cols, Height) -> 279 | St1 = RowFun(Pix, merge_adam7_row(P, Ri, Bpp, Cols), Ri, St0), 280 | merge_adam7(P, Pix, RowFun, St1, 1 + Ri, Bpp, Cols, Height). 281 | 282 | %% 1 6 4 6 2 6 4 6 283 | %% 7 7 7 7 7 7 7 7 284 | %% 5 6 5 6 5 6 5 6 285 | %% 7 7 7 7 7 7 7 7 286 | %% 3 6 4 6 3 6 4 6 287 | %% 7 7 7 7 7 7 7 7 288 | %% 5 6 5 6 5 6 5 6 289 | %% 7 7 7 7 7 7 7 7 290 | merge_adam7_row(P, Ri, _Bpp, _Cols) when Ri band 1 =:= 1 -> 291 | %% 7 7 7 7 7 7 7 7 292 | array:get(Ri, element(7, P)); 293 | merge_adam7_row(P, Ri, Bpp, Cols) -> 294 | << <<(adam7_pixel(P, Ri, Bpp, Col))/binary>> || Col <- Cols >>. 295 | 296 | adam7_pixel(P, Ri, Bpp, Col) when Ri band 1 =:= 1 -> 297 | %% [7 7 7 7 7 7 7 7] 298 | binary_part(array:get(Ri, element(7, P)), Col * Bpp, Bpp); 299 | adam7_pixel(P, Ri, Bpp, Col) when Ri band 7 =:= 0 andalso Col band 7 =:= 0 -> 300 | %% [1] 6 4 6 2 6 4 6 301 | binary_part(array:get(Ri, element(1, P)), (Col div 8) * Bpp, Bpp); 302 | adam7_pixel(P, Ri, Bpp, Col) when Ri band 7 =:= 0 andalso Col band 1 =:= 1 -> 303 | %% 1 [6] 4 [6] 2 [6] 4 [6] 304 | binary_part(array:get(Ri, element(6, P)), (Col div 2) * Bpp, Bpp); 305 | adam7_pixel(P, Ri, Bpp, Col) when Ri band 7 =:= 0 andalso 306 | (Col band 7 =:= 2 orelse Col band 7 =:= 6) -> 307 | %% 1 6 [4] 6 2 6 [4] 6 308 | binary_part(array:get(Ri, element(4, P)), (Col div 4) * Bpp, Bpp); 309 | adam7_pixel(P, Ri, Bpp, Col) when Ri band 7 =:= 0 andalso 310 | (Col band 7 =:= 2 orelse Col band 7 =:= 4) -> 311 | %% 1 6 4 6 [2] 6 4 6 312 | binary_part(array:get(Ri, element(2, P)), (Col div 8) * Bpp, Bpp); 313 | adam7_pixel(P, Ri, Bpp, Col) when Ri band 7 =:= 2 orelse Ri band 7 =:= 6 -> 314 | %% [5 6 5 6 5 6 5 6] 315 | binary_part(array:get(Ri, element(5 + (Col band 1), P)), 316 | (Col div 2) * Bpp, Bpp); 317 | adam7_pixel(P, Ri, Bpp, Col) when Ri band 7 =:= 4 andalso Col band 3 =:= 0 -> 318 | %% [3] 6 4 6 [3] 6 4 6 319 | binary_part(array:get(Ri, element(3, P)), (Col div 4) * Bpp, Bpp); 320 | adam7_pixel(P, Ri, Bpp, Col) when Ri band 7 =:= 4 andalso Col band 1 =:= 1 -> 321 | %% 3 [6] 4 [6] 3 [6] 4 [6] 322 | binary_part(array:get(Ri, element(6, P)), (Col div 2) * Bpp, Bpp); 323 | adam7_pixel(P, Ri, Bpp, Col) when Ri band 7 =:= 4 andalso 324 | (Col band 7 =:= 2 orelse Col band 7 =:= 6) -> 325 | %% 3 6 [4] 6 3 6 [4] 6 326 | binary_part(array:get(Ri, element(4, P)), (Col div 4) * Bpp, Bpp). 327 | 328 | 329 | 330 | 331 | interlaced_pass(_Bin, _Width, _Height, _Bpp, 8) -> 332 | []; 333 | interlaced_pass(Bin, Width, Height, Bpp, N) -> 334 | {RowLen, RowInc, RowStart} = get_row_info(Bpp, Width, N), 335 | {Data, Bin1} = read_interlaced_data(Bin, Width, Height, RowStart, Bpp, 336 | RowLen, RowInc, []), 337 | [array:from_orddict(lists:reverse(Data)) 338 | | interlaced_pass(Bin1, Width, Height, Bpp, 1 + N)]. 339 | 340 | read_interlaced_data(Bin, _Width, Height, RowNum, _Bpp, RowLen, _RowInc, St0) 341 | when RowNum >= Height orelse RowLen =:= 0 -> 342 | {St0, Bin}; 343 | read_interlaced_data(Bin, Width, Height, RowNum, Bpp, RowLen, RowInc, St0) -> 344 | case Bin of 345 | <> -> 346 | Prior = case St0 of 347 | [] -> <<>>; 348 | [{_,Row0}|_] -> Row0 349 | end, 350 | Row1 = filter(Filter,Bpp,Row,Prior), %% Filter method=0 assumed 351 | read_interlaced_data(Bin1, Width, Height, RowInc + RowNum, Bpp, 352 | RowLen, RowInc, 353 | [{RowNum, Row1} | St0]); 354 | _ -> 355 | {St0, Bin} 356 | end. 357 | 358 | get_row_info(BPP, Width, Pass) -> 359 | RowLen = (Width + adam7(offset, Pass)) div adam7(col_increment, Pass), 360 | {RowLen * BPP, adam7(row_increment, Pass), adam7(starting_row, Pass)}. 361 | 362 | adam7(offset, N) -> element(N, { 7, 3, 3, 1, 1, 0, 0 }); 363 | %% adam7(starting_col, N) -> element(N, { 0, 4, 0, 2, 0, 1, 0 }); 364 | adam7(starting_row, N) -> element(N, { 0, 0, 4, 0, 2, 0, 1 }); 365 | adam7(col_increment, N) -> element(N, { 8, 8, 4, 4, 2, 2, 1 }); 366 | adam7(row_increment, N) -> element(N, { 8, 8, 8, 4, 4, 2, 2 }). 367 | 368 | 369 | filter(0,_,Row,_Prior) -> Row; 370 | filter(1, Bpp, Row, Prior) -> filter_sub(Row,Prior,Bpp); 371 | filter(2, Bpp, Row,Prior) -> filter_up(Row,Prior,Bpp); 372 | filter(3, Bpp, Row,Prior) -> filter_avg(Row,Prior,Bpp); 373 | filter(4, Bpp, Row,Prior) -> filter_paeth(Row,Prior,Bpp). 374 | 375 | %% 376 | %% Raw(x) = Sub(x) + Raw(x-bpp) [ Sub(x) = Raw(x) - Raw(x-bpp) ] 377 | %% 378 | filter_sub(Sub,_Prior,Bpp) -> 379 | Rn = lists:duplicate(Bpp, 0), 380 | Rm = [], 381 | filter_sub(Sub, 0, size(Sub), [], Rn, Rm). 382 | 383 | filter_sub(_Sub, X, X, Acc, _, _) -> 384 | list_to_binary(reverse(Acc)); 385 | filter_sub(Sub, X, N, Acc, [Rxb|Rn], Rm) -> 386 | <<_:X/binary, Sx:8, _/binary>> = Sub, 387 | Rx = (Sx + Rxb) band 16#ff, 388 | if Rn == [] -> 389 | filter_sub(Sub,X+1,N,[Rx|Acc],reverse([Rx|Rm]),[]); 390 | true -> 391 | filter_sub(Sub,X+1,N,[Rx|Acc],Rn,[Rx|Rm]) 392 | end. 393 | %% 394 | %% Raw(x) = Up(x) + Prior(x) [ Up(x) = Raw(x) - Prior(x) ] 395 | %% 396 | filter_up(Up, Prior, _Bpp) -> 397 | filter_up(Up, Prior, 0, size(Up), []). 398 | 399 | filter_up(_Up, _Prior, X, X, Acc) -> 400 | list_to_binary(reverse(Acc)); 401 | filter_up(Up, Prior, X, N, Acc) -> 402 | <<_:X/binary,Ux:8,_/binary>> = Up, 403 | Px = case Prior of 404 | <<_:X/binary,Pi,_/binary>> -> Pi; 405 | _ -> 0 406 | end, 407 | Rx = (Ux + Px) band 16#ff, 408 | filter_up(Up,Prior,X+1,N,[Rx|Acc]). 409 | 410 | %% 411 | %% Raw(x) = Avarage(x) + floor((Raw(x-bpp)+Prior(x))/2) 412 | %% [ Avarage(x) = Raw(x) - floor((Raw(x-bpp)+Prior(x))/2) ] 413 | %% 414 | 415 | filter_avg(Avg, Prior,Bpp) -> 416 | Rn = lists:duplicate(Bpp, 0), 417 | Rm = [], 418 | filter_avg(Avg, Prior, 0, size(Avg), [], Rn, Rm). 419 | 420 | filter_avg(_Avg,_Prior, X, X, Acc, _, _) -> 421 | list_to_binary(reverse(Acc)); 422 | filter_avg(Avg, Prior, X, N, Acc, [Rxb|Rn], Rm) -> 423 | <<_:X/binary, Ax:8, _/binary>> = Avg, 424 | Px = case Prior of 425 | <<_:X/binary,Pi,_/binary>> -> Pi; 426 | _ -> 0 427 | end, 428 | Rx = (Ax + ((Rxb+Px) div 2)) band 16#ff, 429 | if Rn == [] -> 430 | filter_avg(Avg,Prior,X+1,N,[Rx|Acc],reverse([Rx|Rm]),[]); 431 | true -> 432 | filter_avg(Avg,Prior,X+1,N,[Rx|Acc],Rn,[Rx|Rm]) 433 | end. 434 | 435 | %% 436 | %% Paeth(x) = Raw(x) - 437 | %% PaethPredictor(Raw(x-bpp),Prior(x),Prior(x-bpp)) 438 | %% 439 | %% Raw(x) = Paeth(x) + PaethPredictor(Raw(x-bpp),Prior(x),Prior(x-bpp)) 440 | %% 441 | filter_paeth(Pae,Prior,Bpp) -> 442 | Pn = Rn = lists:duplicate(Bpp, 0), 443 | Pm = Rm = [], 444 | filter_pae(Pae, Prior, 0, size(Pae), [], Rn, Rm, Pn, Pm). 445 | 446 | 447 | filter_pae(_Pae, _Prior, X, X, Acc, _Rn, _Rm, _Pn, _Pm) -> 448 | list_to_binary(reverse(Acc)); 449 | filter_pae(Pae, Prior, X, N, Acc, [Rxb|Rn], Rm, [Pxb|Pn], Pm) -> 450 | <<_:X/binary, PAx:8, _/binary>> = Pae, 451 | Px = case Prior of 452 | <<_:X/binary,Pi,_/binary>> -> Pi; 453 | _ -> 0 454 | end, 455 | Rx = (PAx + paethPredictor(Rxb, Px, Pxb)) band 16#ff, 456 | if Rn == [] -> 457 | filter_pae(Pae,Prior,X+1,N,[Rx|Acc], 458 | reverse([Rx|Rm]),[], 459 | reverse([Px|Pm]),[]); 460 | true -> 461 | filter_pae(Pae,Prior,X+1,N,[Rx|Acc], 462 | Rn,[Rx|Rm], 463 | Pn,[Px|Pm]) 464 | end. 465 | 466 | -define(dabs(X,Y), 467 | if (X) > (Y) -> (X) - (Y); 468 | true -> (Y) - (X) 469 | end). 470 | 471 | paethPredictor(A,B,C) -> 472 | P = A + B - C, 473 | PA = ?dabs(P,A), 474 | PB = ?dabs(P,B), 475 | PC = ?dabs(P,C), 476 | if PA =< PB, PA =< PC -> A; 477 | PB =< PC -> B; 478 | true -> C 479 | end. 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | write(_Fd, _IMG) -> 489 | ok. 490 | 491 | read_image(Fd, Acc, Palette, Z) -> 492 | case read_chunk_hdr(Fd) of 493 | {ok, Length, ?IDAT} -> 494 | case read_chunk_crc(Fd, Length) of 495 | {ok, CBin} -> 496 | Blocks = zlib:inflate(Z, CBin), 497 | read_image(Fd, [Blocks|Acc], Palette, Z); 498 | Error -> Error 499 | end; 500 | {ok, _Length, ?IEND} -> 501 | zlib:inflateEnd(Z), 502 | {ok, list_to_binary(reverse(Acc)), Palette}; 503 | 504 | {ok, Length, ?PLTE} -> 505 | case read_chunk_crc(Fd, Length) of 506 | {ok, Chunk} -> 507 | read_image(Fd, Acc, plte(Chunk), Z); 508 | Error -> 509 | Error 510 | end; 511 | {ok, Length, _} -> 512 | skip_chunk(Fd, Length), 513 | read_image(Fd, Acc, Palette, Z) 514 | end. 515 | 516 | %% 517 | %% Given chunk header read chunk and check crc 518 | %% 519 | read_chunk_crc(Fd, Length) -> 520 | file:position(Fd, {cur,-4}), 521 | LengthWithType = Length+4, 522 | case file:read(Fd, LengthWithType+4) of 523 | {ok,<>} -> 524 | case valid_crc32(TypeChunk, CRC) of 525 | true -> 526 | <<_:32, Chunk/binary>> = TypeChunk, 527 | {ok, Chunk}; 528 | false -> 529 | {error, bad_crc} 530 | end; 531 | {ok,_} -> 532 | {error, bad_chunk}; 533 | Error -> 534 | Error 535 | end. 536 | 537 | %% 538 | %% Read the chunk header 539 | %% 540 | 541 | read_chunk_hdr(Fd) -> 542 | case file:read(Fd, 8) of 543 | {ok, <>} -> 544 | Tag = binary_to_list(Type), 545 | ?dbg("chunk: type = ~p, length=~p\n", [Tag,Length]), 546 | {ok, Length, Tag}; 547 | Error -> 548 | Error 549 | end. 550 | 551 | 552 | skip_chunk(Fd, Length) -> 553 | file:position(Fd, {cur,Length+4}). 554 | 555 | valid_crc32(Binary, CRC32) -> 556 | Z = zlib:open(), 557 | Value = zlib:crc32(Z, Binary), 558 | zlib:close(Z), 559 | ?dbg("crc check: ~p == ~p\n", [CRC32, Value]), 560 | CRC32 == Value. 561 | 562 | -ifdef(TEST). 563 | -include_lib("eunit/include/eunit.hrl"). 564 | %% 565 | %% These tests use the pngsuite of test pngs from the libpng 566 | %% website to verify all sorts of things. The intentionally corrupt 567 | %% pngs live in priv/pngsuite/corrupted because I didn't want 568 | %% to deal with them right now, see should probably write 569 | %% some more acceptance tests that ensures those fail for 570 | %% specific reasons. 571 | %% 572 | png_suite_files() -> 573 | application:start(erl_img), 574 | PrivDir = code:priv_dir(erl_img), 575 | %% Skip tests for unsupported 1-bit, 2-bit, 4-bit color 576 | {ok, SkipRegex} = re:compile("0[124]\\.png"), 577 | lists:filter( 578 | fun (Fn) -> nomatch =:= re:run(Fn, SkipRegex) end, 579 | filelib:wildcard(PrivDir ++ "/pngsuite/*.png")). 580 | 581 | %% Use a macro here so png_suite_*_test_ shows up in the eunit output. 582 | -define(PNG_SUITE_TEST(TestFun), 583 | [{filename:basename(FName), 584 | fun() -> 585 | TestFun(FName) 586 | end} 587 | || FName <- png_suite_files()]). 588 | 589 | png_suite_basic_interlace_test() -> 590 | application:start(erl_img), 591 | PrivDir = code:priv_dir(erl_img), 592 | [F0, F1] = lists:sort( 593 | filelib:wildcard(PrivDir ++ "/pngsuite/bas[in]2c08.png")), 594 | Pm = fun (F) -> 595 | {ok, _Img=#erl_image{pixmaps=[PM]}} = erl_img:load(F), 596 | %% erl_img:save("/tmp/" ++ filename:basename(F) ++ ".tga", 597 | %% _Img#erl_image{type=image_tga}), 598 | #erl_pixmap{pixels=Rows} = PM, 599 | lists:sort(Rows) 600 | end, 601 | ?assertEqual( 602 | Pm(F0), 603 | Pm(F1)), 604 | ok. 605 | 606 | merge_adam7_row_test_() -> 607 | %% 1 6 4 6 2 6 4 6 608 | %% 7 7 7 7 7 7 7 7 609 | %% 5 6 5 6 5 6 5 6 610 | %% 7 7 7 7 7 7 7 7 611 | %% 3 6 4 6 3 6 4 6 612 | %% 7 7 7 7 7 7 7 7 613 | %% 5 6 5 6 5 6 5 6 614 | %% 7 7 7 7 7 7 7 7 615 | P = {array:from_orddict([{0, <<1>>}]), 616 | array:from_orddict([{0, <<2>>}]), 617 | array:from_orddict([{4, <<3, 3>>}]), 618 | array:from_orddict([{0, <<4, 4>>}, 619 | {4, <<4, 4>>}]), 620 | array:from_orddict([{2, <<5, 5, 5, 5>>}, 621 | {6, <<5, 5, 5, 5>>}]), 622 | array:from_orddict([{0, <<6, 6, 6, 6>>}, 623 | {2, <<6, 6, 6, 6>>}, 624 | {4, <<6, 6, 6, 6>>}, 625 | {6, <<6, 6, 6, 6>>}]), 626 | array:from_orddict([{1, <<7, 7, 7, 7, 7, 7, 7, 7>>}, 627 | {3, <<7, 7, 7, 7, 7, 7, 7, 7>>}, 628 | {5, <<7, 7, 7, 7, 7, 7, 7, 7>>}, 629 | {7, <<7, 7, 7, 7, 7, 7, 7, 7>>}])}, 630 | ExpectList = [{0, <<1, 6, 4, 6, 2, 6, 4, 6>>}, 631 | {1, <<7, 7, 7, 7, 7, 7, 7, 7>>}, 632 | {2, <<5, 6, 5, 6, 5, 6, 5, 6>>}, 633 | {3, <<7, 7, 7, 7, 7, 7, 7, 7>>}, 634 | {4, <<3, 6, 4, 6, 3, 6, 4, 6>>}, 635 | {5, <<7, 7, 7, 7, 7, 7, 7, 7>>}, 636 | {6, <<5, 6, 5, 6, 5, 6, 5, 6>>}, 637 | {7, <<7, 7, 7, 7, 7, 7, 7, 7>>}], 638 | [(fun ({N, Expect}) -> 639 | {"row_" ++ integer_to_list(N), 640 | fun () -> 641 | ?assertEqual( 642 | Expect, 643 | merge_adam7_row(P, N, 1, lists:seq(0, 7))) 644 | end} 645 | end)(X) || X <- ExpectList]. 646 | 647 | 648 | png_suite_read_test_() -> 649 | ?PNG_SUITE_TEST( 650 | fun(FileName) -> 651 | {ok, Fd} = file:open(FileName, [read, binary]), 652 | {ok, Info} = read_info(Fd), 653 | {ok, 0} = file:position(Fd, 0), 654 | ?assertMatch({ok, _}, read(Fd, Info)) 655 | end). 656 | 657 | png_suite_magic_test_() -> 658 | ?PNG_SUITE_TEST( 659 | fun(FileName) -> 660 | {ok, Bin} = file:read_file(FileName), 661 | ?assertEqual(true, magic(Bin)) 662 | end). 663 | 664 | non_png_magic_test() -> 665 | ?assertEqual(false, magic(<<"notapng">>)). 666 | 667 | mime_type_test() -> 668 | ?assertEqual("image/png", mime_type()). 669 | 670 | extensions_test() -> 671 | ?assertEqual([".png"], extensions()). 672 | 673 | -endif. 674 | --------------------------------------------------------------------------------