├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── daffodils.jpg ├── docs ├── _static │ └── favicon.ico ├── about.rst ├── conf.py ├── daffodil │ ├── colorspace.rst │ ├── filter │ │ ├── convolve.rst │ │ ├── gaussian.rst │ │ └── index.rst │ ├── image.rst │ ├── index.rst │ ├── meta.rst │ ├── pixel.rst │ └── transform │ │ ├── flip.rst │ │ └── index.rst └── index.rst ├── dub.sdl ├── dub.selections.json ├── requirements.txt ├── source └── daffodil │ ├── bmp │ ├── headers.d │ ├── meta.d │ └── package.d │ ├── colorspace.d │ ├── filter │ ├── convolve.d │ ├── gaussian.d │ └── package.d │ ├── image.d │ ├── meta.d │ ├── package.d │ ├── pixel.d │ ├── transform │ ├── flip.d │ └── package.d │ └── util │ ├── data.d │ ├── errors.d │ ├── headers.d │ └── range.d └── test ├── bmp_suite ├── README.md ├── b │ ├── badbitcount.bmp │ ├── badbitssize.bmp │ ├── baddens1.bmp │ ├── baddens2.bmp │ ├── badfilesize.bmp │ ├── badheadersize.bmp │ ├── badpalettesize.bmp │ ├── badplanes.bmp │ ├── badrle.bmp │ ├── badrle4.bmp │ ├── badrle4bis.bmp │ ├── badrle4ter.bmp │ ├── badrlebis.bmp │ ├── badrleter.bmp │ ├── badwidth.bmp │ ├── pal8badindex.bmp │ ├── reallybig.bmp │ ├── rgb16-880.bmp │ ├── rletopdown.bmp │ └── shortfile.bmp ├── g │ ├── pal1.bmp │ ├── pal1bg.bmp │ ├── pal1wb.bmp │ ├── pal4.bmp │ ├── pal4gs.bmp │ ├── pal4rle.bmp │ ├── pal8-0.bmp │ ├── pal8.bmp │ ├── pal8gs.bmp │ ├── pal8nonsquare.bmp │ ├── pal8os2.bmp │ ├── pal8rle.bmp │ ├── pal8topdown.bmp │ ├── pal8v4.bmp │ ├── pal8v5.bmp │ ├── pal8w124.bmp │ ├── pal8w125.bmp │ ├── pal8w126.bmp │ ├── rgb16-565.bmp │ ├── rgb16-565pal.bmp │ ├── rgb16.bmp │ ├── rgb24.bmp │ ├── rgb24pal.bmp │ ├── rgb32.bmp │ └── rgb32bf.bmp ├── q │ ├── pal1p1.bmp │ ├── pal2.bmp │ ├── pal2color.bmp │ ├── pal4rlecut.bmp │ ├── pal4rletrns.bmp │ ├── pal8offs.bmp │ ├── pal8os2-sz.bmp │ ├── pal8os2sp.bmp │ ├── pal8os2v2-16.bmp │ ├── pal8os2v2-40sz.bmp │ ├── pal8os2v2-sz.bmp │ ├── pal8os2v2.bmp │ ├── pal8oversizepal.bmp │ ├── pal8rlecut.bmp │ ├── pal8rletrns.bmp │ ├── rgb16-231.bmp │ ├── rgb16-3103.bmp │ ├── rgb24jpeg.bmp │ ├── rgb24largepal.bmp │ ├── rgb24lprof.bmp │ ├── rgb24png.bmp │ ├── rgb24prof.bmp │ ├── rgb32-111110.bmp │ ├── rgb32-7187.bmp │ ├── rgb32fakealpha.bmp │ ├── rgb32h52.bmp │ ├── rgba16-1924.bmp │ ├── rgba16-4444.bmp │ ├── rgba32-61754.bmp │ ├── rgba32-81284.bmp │ ├── rgba32.bmp │ ├── rgba32abf.bmp │ └── rgba32h56.bmp └── testbmp_suite.d ├── images ├── bmp_small-24bpp.bmp └── empty.file ├── testall.d └── testbmp.d /.gitignore: -------------------------------------------------------------------------------- 1 | # D/DUB 2 | .dub 3 | docs.json 4 | __test__* 5 | __dummy.html 6 | *.o 7 | *.a 8 | *.obj 9 | 10 | # Sphinx/Python 11 | build/ 12 | __pycache__/ 13 | *.py[cod] 14 | *$py.class 15 | 16 | # unit-threaded 17 | bin/ 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: d 2 | d: 3 | - dmd 4 | # - gdc 5 | # - ldc 6 | 7 | install: 8 | - dub fetch doveralls 9 | 10 | script: 11 | - dub test -b unittest-cov 12 | - rm .*.lst # hack for test coverage 13 | - dub run doveralls 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Cameron Lonsdale, Benjamin Schaaf 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | # You can set these variables from the command line. 4 | SPHINXOPTS = 5 | SPHINXBUILD = sphinx-build 6 | PAPER = 7 | BUILDDIR = build 8 | # Internal variables. 9 | PAPEROPT_a4 = -D latex_paper_size=a4 10 | PAPEROPT_letter = -D latex_paper_size=letter 11 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) \ 12 | $(SPHINXOPTS) docs 13 | # the i18n builder cannot share the environment and doctrees with the others 14 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) docs 15 | .PHONY: help 16 | help: 17 | @echo "Please use 'make ' where is one of" 18 | @echo " html to make standalone HTML files" 19 | @echo " dirhtml to make HTML files named index.html in directories" 20 | @echo " singlehtml to make a single large HTML file" 21 | @echo " pickle to make pickle files" 22 | @echo " json to make JSON files" 23 | @echo " htmlhelp to make HTML files and a HTML help project" 24 | @echo " qthelp to make HTML files and a qthelp project" 25 | @echo " applehelp to make an Apple Help Book" 26 | @echo " devhelp to make HTML files and a Devhelp project" 27 | @echo " epub to make an epub" 28 | @echo " epub3 to make an epub3" 29 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 30 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 31 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 32 | @echo " lualatexpdf to make LaTeX files and run them through lualatex" 33 | @echo " xelatexpdf to make LaTeX files and run them through xelatex" 34 | @echo " text to make text files" 35 | @echo " man to make manual pages" 36 | @echo " texinfo to make Texinfo files" 37 | @echo " info to make Texinfo files and run them through makeinfo" 38 | @echo " gettext to make PO message catalogs" 39 | @echo " changes to make an overview of all changed/added/deprecated items" 40 | @echo " xml to make Docutils-native XML files" 41 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 42 | @echo " linkcheck to check all external links for integrity" 43 | @echo " doctest to run all doctests embedded in the documentation \ 44 | (if enabled)" 45 | @echo " coverage to run coverage check of the documentation (if enabled)" 46 | @echo " dummy to check syntax errors of document sources" 47 | .PHONY: clean 48 | clean: 49 | rm -rf $(BUILDDIR)/* 50 | .PHONY: html 51 | html: 52 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 53 | @echo 54 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 55 | .PHONY: dirhtml 56 | dirhtml: 57 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 58 | @echo 59 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 60 | .PHONY: singlehtml 61 | singlehtml: 62 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 63 | @echo 64 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 65 | .PHONY: pickle 66 | pickle: 67 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 68 | @echo 69 | @echo "Build finished; now you can process the pickle files." 70 | .PHONY: json 71 | json: 72 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 73 | @echo 74 | @echo "Build finished; now you can process the JSON files." 75 | .PHONY: htmlhelp 76 | htmlhelp: 77 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 78 | @echo 79 | @echo "Build finished; now you can run HTML Help Workshop with the" \\ 80 | ".hhp project file in $(BUILDDIR)/htmlhelp." 81 | .PHONY: qthelp 82 | qthelp: 83 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 84 | @echo 85 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \\ 86 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 87 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/%(project_fn)s.qhcp" 88 | @echo "To view the help file:" 89 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/%(project_fn)s.qhc" 90 | .PHONY: applehelp 91 | applehelp: 92 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 93 | @echo 94 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 95 | @echo "N.B. You won't be able to view it unless you put it in" \\ 96 | "~/Library/Documentation/Help or install it in your application" \\ 97 | "bundle." 98 | .PHONY: devhelp 99 | devhelp: 100 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 101 | @echo 102 | @echo "Build finished." 103 | @echo "To view the help file:" 104 | @echo "# mkdir -p $$HOME/.local/share/devhelp/%(project_fn)s" 105 | @echo "# ln -s $(BUILDDIR)/devhelp\ 106 | $$HOME/.local/share/devhelp/%(project_fn)s" 107 | @echo "# devhelp" 108 | .PHONY: epub 109 | epub: 110 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 111 | @echo 112 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 113 | .PHONY: epub3 114 | epub3: 115 | $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 116 | @echo 117 | @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." 118 | .PHONY: latex 119 | latex: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo 122 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 123 | @echo "Run \\`make' in that directory to run these through (pdf)latex" \\ 124 | "(use \\`make latexpdf' here to do that automatically)." 125 | .PHONY: latexpdf 126 | latexpdf: 127 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 128 | @echo "Running LaTeX files through pdflatex..." 129 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 130 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 131 | .PHONY: latexpdfja 132 | latexpdfja: 133 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 134 | @echo "Running LaTeX files through platex and dvipdfmx..." 135 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 136 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 137 | .PHONY: lualatexpdf 138 | lualatexpdf: 139 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 140 | @echo "Running LaTeX files through lualatex..." 141 | $(MAKE) PDFLATEX=lualatex -C $(BUILDDIR)/latex all-pdf 142 | @echo "lualatex finished; the PDF files are in $(BUILDDIR)/latex." 143 | .PHONY: xelatexpdf 144 | xelatexpdf: 145 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 146 | @echo "Running LaTeX files through xelatex..." 147 | $(MAKE) PDFLATEX=xelatex -C $(BUILDDIR)/latex all-pdf 148 | @echo "xelatex finished; the PDF files are in $(BUILDDIR)/latex." 149 | .PHONY: text 150 | text: 151 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 152 | @echo 153 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 154 | .PHONY: man 155 | man: 156 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 157 | @echo 158 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 159 | .PHONY: texinfo 160 | texinfo: 161 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 162 | @echo 163 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 164 | @echo "Run \\`make' in that directory to run these through makeinfo" \\ 165 | "(use \\`make info' here to do that automatically)." 166 | .PHONY: info 167 | info: 168 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 169 | @echo "Running Texinfo files through makeinfo..." 170 | make -C $(BUILDDIR)/texinfo info 171 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 172 | .PHONY: gettext 173 | gettext: 174 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 175 | @echo 176 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 177 | .PHONY: changes 178 | changes: 179 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 180 | @echo 181 | @echo "The overview file is in $(BUILDDIR)/changes." 182 | .PHONY: linkcheck 183 | linkcheck: 184 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 185 | @echo 186 | @echo "Link check complete; look for any errors in the above output " \\ 187 | "or in $(BUILDDIR)/linkcheck/output.txt." 188 | .PHONY: doctest 189 | doctest: 190 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 191 | @echo "Testing of doctests in the sources finished, look at the " \\ 192 | "results in $(BUILDDIR)/doctest/output.txt." 193 | .PHONY: coverage 194 | coverage: 195 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 196 | @echo "Testing of coverage in the sources finished, look at the " \\ 197 | "results in $(BUILDDIR)/coverage/python.txt." 198 | .PHONY: xml 199 | xml: 200 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 201 | @echo 202 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 203 | .PHONY: pseudoxml 204 | pseudoxml: 205 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 206 | @echo 207 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 208 | .PHONY: dummy 209 | dummy: 210 | $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy 211 | @echo 212 | @echo "Build finished. Dummy builder generates no files." 213 | .PHONY: deploy 214 | deploy: html 215 | $(eval tmp = mktemp -d) 216 | cp $(BUILDDIR)/html $tmp -r 217 | git checkout gh-pages 218 | git clean -e $tmp -xdf 219 | cp $tmp/* . -r 220 | rm -rf $tmp 221 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Daffodil 2 | 3 | [![Build Status](https://travis-ci.org/BenjaminSchaaf/daffodil.svg?branch=master)](https://travis-ci.org/BenjaminSchaaf/daffodil) 4 | [![Coverage Status](https://coveralls.io/repos/github/BenjaminSchaaf/daffodil/badge.svg?branch=master)](https://coveralls.io/github/BenjaminSchaaf/daffodil?branch=master) 5 | [![DUB Listing](https://img.shields.io/dub/dt/daffodil.svg)](http://code.dlang.org/packages/daffodil) 6 | 7 | A image processing library for D, inspired by 8 | [Pillow](https://python-pillow.github.io/). 9 | 10 | Read the documentation [here](https://benjaminschaaf.github.io/daffodil/). 11 | 12 | ## Goals 13 | 14 | - Simple, Extensible API 15 | - Controllable internals with suitable defaults 16 | - Wide format support with extensive testing 17 | - High performance 18 | - Support a variety of filters and transformations 19 | - Thread Safety (pending) 20 | 21 | ## Example 22 | 23 | ```D 24 | import daffodil; 25 | import daffodil.filter; 26 | import daffodil.transform; 27 | 28 | void main() { 29 | // daffodil allows you to choose what format pixels are stored with 30 | // defaults to `real` for when you don't care about memory usage. 31 | auto image = load!uint("daffodil.bmp"); 32 | 33 | // concise filter usage, with a simple saving API 34 | image.gaussianBlurred(1.4) 35 | .save("blurry_daffodil.bmp"); 36 | 37 | // easy transformations 38 | image.flip!"y"; 39 | image.save("upside_down_daffodil.bmp"); 40 | } 41 | ``` 42 | 43 | ## Installing 44 | 45 | Add daffodil as a dependency to your 46 | [dub.json](https://code.dlang.org/package-format?lang=json): 47 | 48 | ```json 49 | "dependencies": { 50 | "daffodil": "~>0.1.1" 51 | } 52 | ``` 53 | 54 | Or [fetch](https://code.dlang.org/docs/commandline) the package directly: 55 | 56 | ```bash 57 | dub fetch daffodil 58 | ``` 59 | 60 | ## Development 61 | 62 | ### Testing 63 | 64 | Tests use the [unit-threaded](https://github.com/atilaneves/unit-threaded) 65 | framework and can be run using: 66 | 67 | ```bash 68 | dub test 69 | ``` 70 | 71 | ### Documentation 72 | 73 | Documentation is written using the [sphinx 74 | framework](http://www.sphinx-doc.org/en/stable/) and a custom D domain/autodoc 75 | for sphinx ([sphinxddoc](https://github.com/BenjaminSchaaf/sphinxddoc)). 76 | 77 | To build the documentation, simply run: 78 | 79 | ```bash 80 | make html 81 | ``` 82 | -------------------------------------------------------------------------------- /daffodils.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/daffodils.jpg -------------------------------------------------------------------------------- /docs/_static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/docs/_static/favicon.ico -------------------------------------------------------------------------------- /docs/about.rst: -------------------------------------------------------------------------------- 1 | About 2 | ##### 3 | 4 | Goals 5 | ===== 6 | 7 | - Simple, Extensible API 8 | - Controllable internals with suitable defaults 9 | - Wide format support with extensive testing 10 | - High performance 11 | - Support a variety of filters and transformations 12 | - Thread Safety (pending) 13 | 14 | License 15 | ======= 16 | 17 | Daffodil is licensed under the open source MIT license: 18 | 19 | .. include:: ../LICENSE 20 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Daffodil documentation build configuration file, created by 5 | # sphinx-quickstart on Sat Dec 19 02:58:25 2015. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | import sys 17 | import os 18 | 19 | # -- General configuration ------------------------------------------------ 20 | 21 | # If your documentation needs a minimal Sphinx version, state it here. 22 | #needs_sphinx = '1.0' 23 | 24 | # Add any Sphinx extension module names here, as strings. They can be 25 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 26 | # ones. 27 | extensions = [ 28 | 'sphinx.ext.todo', 29 | 'ddoc.d', 30 | 'ddoc.autodoc', 31 | ] 32 | 33 | autodoc_lookup_path = os.path.join(os.path.split(__file__)[0], "..", "source") 34 | 35 | highlight_language = 'd' 36 | todo_include_todos = True 37 | 38 | # Add any paths that contain templates here, relative to this directory. 39 | templates_path = ['_templates'] 40 | 41 | # The suffix of source filenames. 42 | source_suffix = '.rst' 43 | 44 | # The encoding of source files. 45 | #source_encoding = 'utf-8-sig' 46 | 47 | # The master toctree document. 48 | master_doc = 'index' 49 | 50 | # General information about the project. 51 | project = 'daffodil' 52 | copyright = '2015, Cameron Lonsdale, Benjamin Schaaf' 53 | 54 | # The version info for the project you're documenting, acts as replacement for 55 | # |version| and |release|, also used in various other places throughout the 56 | # built documents. 57 | # 58 | # The short X.Y version. 59 | version = '1.0' 60 | # The full version, including alpha/beta/rc tags. 61 | release = '1.0' 62 | 63 | # The language for content autogenerated by Sphinx. Refer to documentation 64 | # for a list of supported languages. 65 | #language = None 66 | 67 | # There are two options for replacing |today|: either, you set today to some 68 | # non-false value, then it is used: 69 | #today = '' 70 | # Else, today_fmt is used as the format for a strftime call. 71 | #today_fmt = '%B %d, %Y' 72 | 73 | # List of patterns, relative to source directory, that match files and 74 | # directories to ignore when looking for source files. 75 | exclude_patterns = ['_build'] 76 | 77 | # The reST default role (used for this markup: `text`) to use for all 78 | # documents. 79 | #default_role = None 80 | 81 | # If true, '()' will be appended to :func: etc. cross-reference text. 82 | #add_function_parentheses = True 83 | 84 | # If true, the current module name will be prepended to all description 85 | # unit titles (such as .. function::). 86 | #add_module_names = True 87 | 88 | # If true, sectionauthor and moduleauthor directives will be shown in the 89 | # output. They are ignored by default. 90 | #show_authors = False 91 | 92 | # The name of the Pygments (syntax highlighting) style to use. 93 | pygments_style = 'sphinx' 94 | 95 | # A list of ignored prefixes for module index sorting. 96 | #modindex_common_prefix = [] 97 | 98 | # If true, keep warnings as "system message" paragraphs in the built documents. 99 | #keep_warnings = False 100 | 101 | 102 | # -- Options for HTML output ---------------------------------------------- 103 | 104 | # The theme to use for HTML and HTML Help pages. See the documentation for 105 | # a list of builtin themes. 106 | html_theme = 'alabaster' 107 | 108 | # Theme options are theme-specific and customize the look and feel of a theme 109 | # further. For a list of options available for each theme, see the 110 | # documentation. 111 | html_theme_options = { 112 | 'logo': None, 113 | 'logo_name': True, 114 | 'description': 'Image Processing Library for D', 115 | 'github_user': 'BenjaminSchaaf', 116 | 'github_repo': 'daffodil', 117 | 'github_button': False, 118 | 'github_banner': True, 119 | 'travis_button': True, 120 | 'page_width': '1420px', 121 | } 122 | 123 | # Add any paths that contain custom themes here, relative to this directory. 124 | #html_theme_path = [] 125 | 126 | # The name for this set of Sphinx documents. If None, it defaults to 127 | # " v documentation". 128 | #html_title = None 129 | 130 | # A shorter title for the navigation bar. Default is the same as html_title. 131 | #html_short_title = None 132 | 133 | # The name of an image file (relative to this directory) to place at the top 134 | # of the sidebar. 135 | #html_logo = None 136 | 137 | # The name of an image file (within the static path) to use as favicon of the 138 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 139 | # pixels large. 140 | html_favicon = 'favicon.ico' 141 | 142 | # Add any paths that contain custom static files (such as style sheets) here, 143 | # relative to this directory. They are copied after the builtin static files, 144 | # so a file named "default.css" will overwrite the builtin "default.css". 145 | html_static_path = ['_static'] 146 | 147 | # Add any extra paths that contain custom files (such as robots.txt or 148 | # .htaccess) here, relative to this directory. These files are copied 149 | # directly to the root of the documentation. 150 | #html_extra_path = [] 151 | 152 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 153 | # using the given strftime format. 154 | #html_last_updated_fmt = '%b %d, %Y' 155 | 156 | # If true, SmartyPants will be used to convert quotes and dashes to 157 | # typographically correct entities. 158 | #html_use_smartypants = True 159 | 160 | # Custom sidebar templates, maps document names to template names. 161 | html_sidebars = { 162 | '**': [ 163 | 'about.html', 164 | 'navigation.html', 165 | 'relations.html', 166 | 'searchbox.html', 167 | 'donate.html', 168 | ] 169 | } 170 | 171 | # Additional templates that should be rendered to pages, maps page names to 172 | # template names. 173 | #html_additional_pages = {} 174 | 175 | # If false, no module index is generated. 176 | #html_domain_indices = True 177 | 178 | # If false, no index is generated. 179 | #html_use_index = True 180 | 181 | # If true, the index is split into individual pages for each letter. 182 | #html_split_index = False 183 | 184 | # If true, links to the reST sources are added to the pages. 185 | #html_show_sourcelink = True 186 | 187 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 188 | #html_show_sphinx = True 189 | 190 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 191 | #html_show_copyright = True 192 | 193 | # If true, an OpenSearch description file will be output, and all pages will 194 | # contain a tag referring to it. The value of this option must be the 195 | # base URL from which the finished HTML is served. 196 | #html_use_opensearch = '' 197 | 198 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 199 | #html_file_suffix = None 200 | 201 | # Output file base name for HTML help builder. 202 | htmlhelp_basename = 'Daffodildoc' 203 | -------------------------------------------------------------------------------- /docs/daffodil/colorspace.rst: -------------------------------------------------------------------------------- 1 | colorspace 2 | ########## 3 | 4 | .. automodule:: daffodil.colorspace 5 | -------------------------------------------------------------------------------- /docs/daffodil/filter/convolve.rst: -------------------------------------------------------------------------------- 1 | convolve 2 | ######## 3 | 4 | .. automodule:: daffodil.filter.convolve 5 | -------------------------------------------------------------------------------- /docs/daffodil/filter/gaussian.rst: -------------------------------------------------------------------------------- 1 | gaussian 2 | ######## 3 | 4 | .. automodule:: daffodil.filter.gaussian 5 | -------------------------------------------------------------------------------- /docs/daffodil/filter/index.rst: -------------------------------------------------------------------------------- 1 | filter 2 | ###### 3 | 4 | .. toctree:: 5 | :hidden: 6 | :maxdepth: 2 7 | 8 | convolve 9 | gaussian 10 | 11 | .. automodule:: daffodil.filter 12 | -------------------------------------------------------------------------------- /docs/daffodil/image.rst: -------------------------------------------------------------------------------- 1 | image 2 | ##### 3 | 4 | .. automodule:: daffodil.image 5 | -------------------------------------------------------------------------------- /docs/daffodil/index.rst: -------------------------------------------------------------------------------- 1 | daffodil 2 | ######## 3 | 4 | .. toctree:: 5 | :titlesonly: 6 | :includehidden: 7 | 8 | image 9 | meta 10 | pixel 11 | colorspace 12 | filter/index 13 | transform/index 14 | 15 | .. automodule:: daffodil 16 | -------------------------------------------------------------------------------- /docs/daffodil/meta.rst: -------------------------------------------------------------------------------- 1 | meta 2 | #### 3 | 4 | The :d:mod:`daffodil.meta` module exposes the :d:struct:`MetaData` class. 5 | 6 | .. automodule:: daffodil.meta 7 | -------------------------------------------------------------------------------- /docs/daffodil/pixel.rst: -------------------------------------------------------------------------------- 1 | pixel 2 | ##### 3 | 4 | .. automodule:: daffodil.pixel 5 | -------------------------------------------------------------------------------- /docs/daffodil/transform/flip.rst: -------------------------------------------------------------------------------- 1 | flip 2 | #### 3 | 4 | .. automodule:: daffodil.transform.flip 5 | -------------------------------------------------------------------------------- /docs/daffodil/transform/index.rst: -------------------------------------------------------------------------------- 1 | transform 2 | ######### 3 | 4 | .. toctree:: 5 | :hidden: 6 | :maxdepth: 2 7 | 8 | flip 9 | 10 | .. automodule:: daffodil.transform 11 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Daffodil 2 | ######## 3 | 4 | Daffodil is a D image processing Library, inspired by Pillow_ (The Python 5 | Imaging Library) 6 | 7 | .. toctree:: 8 | :titlesonly: 9 | :includehidden: 10 | 11 | about 12 | daffodil/index 13 | 14 | .. 15 | Once indexing works: 16 | * :ref:`genindex` 17 | 18 | .. _Pillow: https://python-pillow.github.io/ 19 | -------------------------------------------------------------------------------- /dub.sdl: -------------------------------------------------------------------------------- 1 | name "daffodil" 2 | description "D Imaging Library" 3 | authors "Cameron Lonsdale" "Benjamin Schaaf" 4 | copyright "Copyright © 2015, Cameron Lonsdale, Benjamin Schaaf" 5 | license "GPL-2.0" 6 | configuration "library" { 7 | targetType "library" 8 | } 9 | configuration "unittest" { 10 | targetType "executable" 11 | targetName "__test__unittest__" 12 | mainSourceFile "build/test/ut.d" 13 | sourcePaths "source" "test" 14 | importPaths "source" "test" 15 | preBuildCommands "dub run unit-threaded -c gen_ut_main -- -f build/test/ut.d" 16 | dependency "unit-threaded" version="~>0.6.6" 17 | } 18 | -------------------------------------------------------------------------------- /dub.selections.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileVersion": 1, 3 | "versions": { 4 | "unit-threaded": "0.6.36" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | -e git+https://github.com/BenjaminSchaaf/sphinxddoc#egg=ddoc 2 | -------------------------------------------------------------------------------- /source/daffodil/bmp/headers.d: -------------------------------------------------------------------------------- 1 | module daffodil.bmp.headers; 2 | 3 | import daffodil.bmp; 4 | import daffodil.util.headers; 5 | 6 | const BMP_FILE_HEADER = [0x42, 0x4D]; 7 | 8 | struct BmpHeader { 9 | @(Endianess.little): 10 | uint size; 11 | ushort reserved1; 12 | ushort reserved2; 13 | uint contentOffset; 14 | } 15 | 16 | enum DibVersion { 17 | CORE = 12, 18 | INFO = 40, 19 | V2INFO = 52, 20 | V3INFO = 56, 21 | V4 = 108, 22 | V5 = 124, 23 | } 24 | 25 | enum CompressionMethod { 26 | RGB = 0, 27 | RLE8 = 1, 28 | RLE4 = 2, 29 | BITFIELDS = 3, 30 | JPEG = 4, 31 | PNG = 5, 32 | ALPHABITFIELDS = 6, 33 | CMYK = 11, 34 | CMYKRLE8 = 12, 35 | CMYKRLE4 = 13, 36 | } 37 | 38 | /// Matches the typedefs found here: https://forums.adobe.com/servlet/JiveServlet/showImage/2-3273299-47801/BMP_Headers.png 39 | struct DibHeader(DibVersion version_ = DibVersion.V5) { 40 | @(Endianess.little): 41 | // All versions 42 | static if (version_ <= DibVersion.CORE) { 43 | ushort width; 44 | ushort height; 45 | } else { 46 | int width; 47 | int height; 48 | } 49 | ushort planes; 50 | ushort bitCount; 51 | 52 | static if (version_ >= DibVersion.INFO) { 53 | uint compression; 54 | uint dataSize; 55 | int xPixelsPerMeter; 56 | int yPixelsPerMeter; 57 | uint colorsUsed; 58 | uint colorsImportant; 59 | } 60 | 61 | static if (version_ >= DibVersion.V2INFO) { 62 | uint redMask; 63 | uint greenMask; 64 | uint blueMask; 65 | } 66 | 67 | static if (version_ >= DibVersion.V3INFO) { 68 | uint alphaMask; 69 | } 70 | 71 | static if (version_ >= DibVersion.V4) { 72 | uint csType; 73 | // No idea what this is, but its 32 bits long 74 | static struct Endpoints { long a; long b; long c; long d; int e; }; 75 | Endpoints endpoints; 76 | uint gammaRed; 77 | uint gammaGreen; 78 | uint gammaBlue; 79 | } 80 | 81 | static if (version_ >= DibVersion.V5) { 82 | uint intent; 83 | uint profileData; 84 | uint profileSize; 85 | uint reserved; 86 | } 87 | 88 | mixin Upcast; 89 | } 90 | 91 | struct DibColorMask(bool alpha = true) { 92 | @(Endianess.little): 93 | uint redMask; 94 | uint greenMask; 95 | uint blueMask; 96 | 97 | static if (alpha) { 98 | uint alphaMask; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /source/daffodil/bmp/meta.d: -------------------------------------------------------------------------------- 1 | module daffodil.bmp.meta; 2 | 3 | import daffodil.meta; 4 | import daffodil.bmp.headers; 5 | 6 | class BmpMetaData : MetaData { 7 | BmpHeader bmpHeader; 8 | DibVersion dibVersion; 9 | DibHeader!() dibHeader; 10 | 11 | this(size_t width, size_t height, BmpHeader bmpHeader, 12 | DibVersion dibVersion, DibHeader!() dibHeader) { 13 | this.width = width; 14 | this.height = height; 15 | this.bmpHeader = bmpHeader; 16 | this.dibVersion = dibVersion; 17 | this.dibHeader = dibHeader; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /source/daffodil/bmp/package.d: -------------------------------------------------------------------------------- 1 | module daffodil.bmp; 2 | 3 | public { 4 | import daffodil.bmp.meta; 5 | 6 | static { 7 | import headers = daffodil.bmp.headers; 8 | } 9 | } 10 | 11 | import std.math; 12 | import std.traits; 13 | import std.bitmanip; 14 | import std.typecons; 15 | import std.algorithm; 16 | import core.bitop; 17 | 18 | import daffodil; 19 | import daffodil.util.data; 20 | import daffodil.util.range; 21 | import daffodil.util.errors; 22 | import daffodil.util.headers; 23 | 24 | import daffodil.bmp.headers; 25 | 26 | /// Register this file format with the common api 27 | static this() { 28 | registerFormat(Format( 29 | "BMP", 30 | &check!DataRange, 31 | &loadMeta!DataRange, 32 | (d, m) => loadImage!DataRange(d, cast(BmpMetaData)m).imageRangeObject, 33 | (d, i, m) => saveImage!(OutputRange!ubyte, RandomAccessImageRange!(real[]))(d, i, cast(BmpMetaData)m), 34 | [".bmp", ".dib"], 35 | typeid(BmpMetaData), 36 | )); 37 | } 38 | 39 | // Magic Number "BM" 40 | const ubyte[] MAGIC_NUMBER = [0x42, 0x4D]; 41 | 42 | /** 43 | * Documentation 44 | */ 45 | bool check(R)(R data) if (isInputRange!R && 46 | is(ElementType!R == ubyte)) { 47 | auto taken = data.take(2).array; 48 | enforce!UnexpectedEndOfData(taken.length == 2); 49 | // Make sure data starts with the magic number 50 | return taken == MAGIC_NUMBER; 51 | } 52 | /// Ditto 53 | bool check(T)(T loadeable) if (isLoadeable!T) { 54 | return check(dataLoad(loadeable)); 55 | } 56 | 57 | @("BMP file format check") 58 | unittest { 59 | assert( check(cast(ubyte[])[0x42, 0x4D, 0x32, 0x7D, 0xFA, 0x9E])); 60 | assert(!check(cast(ubyte[])[0x43, 0x4D, 0x32, 0x7D, 0xFA, 0x9E])); 61 | assert(!check(cast(ubyte[])[0x42, 0x4C, 0x32, 0x7D, 0xFA, 0x9E])); 62 | } 63 | 64 | /** 65 | * Documentation 66 | */ 67 | auto load(V = real, T : DataRange)(T data, BmpMetaData meta = null) { 68 | enforce!InvalidImageType(check(data), "Data does not contain a bmp image."); 69 | 70 | if (meta is null) meta = loadMeta(data); 71 | return new Image!V(loadImage(data, meta), meta); 72 | } 73 | /// Ditto 74 | auto load(V = real, T)(T loadeable) if (isLoadeable!T) { 75 | return load!V(dataLoad(loadeable)); 76 | } 77 | 78 | // The default rgba masks for common formats 79 | private enum DEFAULT_MASKS = [ 80 | 16 : tuple(0x0F_00_00_00u, 0x00_F0_00_00u, 0x00_0F_00_00u, 0xF0_00_00_00u), 81 | 24 : tuple(0x00_00_FF_00u, 0x00_FF_00_00u, 0xFF_00_00_00u, 0x00_00_00_00u), 82 | 32 : tuple(0x00_FF_00_00u, 0x00_00_FF_00u, 0x00_00_00_FFu, 0xFF_00_00_00u), 83 | ]; 84 | 85 | /** 86 | * Documentation 87 | */ 88 | BmpMetaData loadMeta(R)(R data) if (isInputRange!R && 89 | is(ElementType!R == ubyte)) { 90 | auto bmpHeader = parseHeader!BmpHeader(data); 91 | auto dibVersion = cast(DibVersion)parseHeader!uint(data); 92 | 93 | // Parse dib header according to version, but store in most complex version 94 | DibHeader!() dibHeader; 95 | foreach (ver; EnumMembers!DibVersion) { 96 | if (dibVersion == ver) { 97 | dibHeader = cast(DibHeader!())parseHeader!(DibHeader!ver)(data); 98 | } 99 | } 100 | 101 | // Validation 102 | alias checkValid = enforce!(InvalidHeader, bool); 103 | 104 | checkValid(dibVersion > DibVersion.CORE || dibHeader.bitCount < 16, 105 | "Old BMP image header does not support bpp > 8"); 106 | checkValid(dibHeader.planes == 1, "BMP image can only have one color plane."); 107 | checkValid(dibHeader.compression == CompressionMethod.RGB || dibHeader.dataSize > 0, 108 | "Invalid data size for compression method"); 109 | checkValid(dibHeader.width > 0, "BMP image width must be positive"); 110 | checkValid(dibHeader.height != 0, "BMP image height must not be 0"); 111 | 112 | checkValid(dibHeader.dataSize == bmpHeader.size - bmpHeader.contentOffset, 113 | "BMP header's image size does not match DIB header's"); 114 | 115 | // Calculate raster data sizes 116 | uint rowSize = (dibHeader.bitCount * dibHeader.width + 31)/32 * 4; 117 | uint columnSize = rowSize * dibHeader.height; 118 | checkValid(dibHeader.dataSize == columnSize, 119 | "BMP data size does not match image dimensions"); 120 | 121 | // Compressions methods are not yet supported 122 | enforce!NotSupported(dibHeader.compression == CompressionMethod.RGB || 123 | dibHeader.compression == CompressionMethod.BITFIELDS); 124 | 125 | // V5 has a ICC color profile, not yet supported 126 | enforce!NotSupported(dibVersion != DibVersion.V5); 127 | 128 | // Color tables are not yet supported 129 | enforce!NotSupported(dibHeader.colorsUsed == 0); 130 | enforce!NotSupported(dibHeader.bitCount > 8); 131 | 132 | // Use special color mask for special compression method 133 | if (dibHeader.compression == CompressionMethod.BITFIELDS) { 134 | auto mask = parseHeader!(DibColorMask!false)(data); 135 | dibHeader.redMask = mask.redMask; 136 | dibHeader.greenMask = mask.greenMask; 137 | dibHeader.blueMask = mask.blueMask; 138 | } 139 | // Default RGB masks, as early versions didn't have them 140 | else if (dibVersion <= DibVersion.INFO) { 141 | checkValid((dibHeader.bitCount in DEFAULT_MASKS) != null, 142 | "BMP header uses non-standard bpp without color masks"); 143 | auto mask = DEFAULT_MASKS[dibHeader.bitCount]; 144 | dibHeader.redMask = mask[0]; 145 | dibHeader.greenMask = mask[1]; 146 | dibHeader.blueMask = mask[2]; 147 | dibHeader.alphaMask = mask[3]; 148 | } 149 | 150 | uint[] masks = [dibHeader.redMask, dibHeader.greenMask, dibHeader.blueMask]; 151 | if (dibHeader.alphaMask != 0) { 152 | masks ~= dibHeader.alphaMask; 153 | } 154 | 155 | // Validate color masks 156 | foreach (mask; masks) { 157 | checkValid(mask != 0, "Color mask is 0"); 158 | } 159 | 160 | return new BmpMetaData(dibHeader.width, abs(dibHeader.height), 161 | bmpHeader, dibVersion, dibHeader); 162 | } 163 | /// Ditto 164 | auto loadMeta(T)(T loadeable) if (isLoadeable!T) { 165 | return loadMeta(dataLoad(loadeable)); 166 | } 167 | 168 | /** 169 | * Documentation 170 | */ 171 | auto loadImage(R)(R data, BmpMetaData meta) if (isInputRange!R && 172 | is(ElementType!R == ubyte)) { 173 | enforce!ImageException(meta !is null, "Cannot load bmp Image without bmp Meta Data"); 174 | 175 | auto dib = meta.dibHeader; 176 | uint[] masks = [dib.redMask, dib.greenMask, dib.blueMask]; 177 | if (dib.alphaMask != 0) { 178 | masks ~= dib.alphaMask; 179 | } 180 | 181 | return maskedRasterLoad(data, masks, dib.bitCount, 182 | dib.width, -dib.height, RGB, 4); 183 | } 184 | /// Ditto 185 | auto loadImage(T)(T loadeable, BmpMetaData meta) if (isLoadeable!T) { 186 | return loadImage(dataLoad(loadeable), meta); 187 | } 188 | 189 | /** 190 | * Documentation 191 | */ 192 | void save(V = real, R)(Image!V image, R output) if (isOutputRange!(R, ubyte)) { 193 | saveImage(output, image.range, cast(BmpMetaData)image.meta); 194 | } 195 | /// Ditto 196 | void save(V = real, T)(Image!V image, T saveable) if (isSaveable!T) { 197 | save(image, dataSave(saveable)); 198 | } 199 | 200 | /** 201 | * Documentation 202 | */ 203 | void saveImage(R, I)(R output, I image, BmpMetaData meta) if (isOutputRange!(R, ubyte) && 204 | isRandomAccessImageRange!I) { 205 | if (meta is null) { 206 | // TODO: Default 207 | assert(false); 208 | } 209 | 210 | put(output, MAGIC_NUMBER[]); 211 | // TODO: Validation 212 | writeHeader(meta.bmpHeader, output); 213 | writeHeader(meta.dibVersion, output); 214 | 215 | foreach (ver; EnumMembers!DibVersion) { 216 | if (meta.dibVersion == ver) { 217 | writeHeader(cast(DibHeader!ver)meta.dibHeader, output); 218 | } 219 | } 220 | 221 | auto dib = meta.dibHeader; 222 | uint[] masks = [dib.redMask, dib.greenMask, dib.blueMask]; 223 | if (dib.alphaMask != 0) { 224 | masks ~= dib.alphaMask; 225 | } 226 | 227 | maskedRasterSave(image, output, masks, dib.bitCount, dib.width, -dib.height, 4); 228 | } 229 | /// Ditto 230 | void saveImage(T, I)(T saveable, I image, BmpMetaData meta) if (isSaveable!T && 231 | isRandomAccessImageRange!I) { 232 | return saveImage(dataSave(saveable), image, meta); 233 | } 234 | -------------------------------------------------------------------------------- /source/daffodil/colorspace.d: -------------------------------------------------------------------------------- 1 | module daffodil.colorspace; 2 | 3 | import std.conv; 4 | import std.string; 5 | import std.algorithm; 6 | 7 | /** 8 | * A group of functions describing the operations for a :d:struct:`Pixel`. 9 | */ 10 | struct ColorSpace { 11 | /// 12 | void function(const real[], const real, real[]) opScalarMul; 13 | /// 14 | void function(const real[], const real[], real[]) opColorAdd; 15 | /// 16 | string function(const real[]) toString; 17 | } 18 | 19 | /// Standard RGB color space 20 | @property const(ColorSpace*) RGB() { return &RGBColorSpace; } 21 | 22 | private const RGBColorSpace = ColorSpace( 23 | (const real[] self, const real other, real[] target) { 24 | assert(self.length == target.length); 25 | 26 | foreach (index; 0..self.length) { 27 | target[index] = cast(real)(self[index] * other); 28 | } 29 | }, 30 | (const real[] self, const real[] other, real[] target) { 31 | assert(self.length == target.length); 32 | assert(self.length == other.length); 33 | foreach (index; 0..self.length) { 34 | target[index] = cast(real)min(self[index] + other[index], real.max); 35 | } 36 | }, 37 | (const real[] self) { 38 | auto values = self.map!(to!string); 39 | return "(" ~ values.join(", ") ~ ")"; 40 | } 41 | ); 42 | -------------------------------------------------------------------------------- /source/daffodil/filter/convolve.d: -------------------------------------------------------------------------------- 1 | module daffodil.filter.convolve; 2 | 3 | import std.range; 4 | import std.algorithm; 5 | 6 | import daffodil.image; 7 | 8 | /** 9 | * Convolve a flat 2D matrix with a given center over an image and return the result. 10 | * 11 | * With a matrix [0.5, 0.5] with width 2 and center [0, 0] convolved over an image 12 | * each resulting pixel will be a 50:50 mix of itself and its right neighbour. 13 | * 14 | * TODO: Abstract away matrix, width and center into a kernel 15 | */ 16 | auto convolved(V)(const Image!V image, const real[] matrix, int width, int[2] center) { 17 | auto height = matrix.length / width; 18 | auto ret = image.dup; 19 | 20 | foreach (imageY; 0..image.height) { 21 | foreach (imageX; 0..image.width) { 22 | // Make sure weighting always adds up to 1. Fixes corners and incorrect/incomplete matrices. 23 | real accum = 0; 24 | 25 | // Accumulate color by weighing adjacent pixels according to given matrix. 26 | auto color = ret.newColor(); 27 | 28 | foreach (indexY; 0..height) { 29 | auto matrixY = indexY - center[1]; 30 | if (matrixY + imageY < 0 || matrixY + imageY >= image.height) continue; 31 | 32 | foreach (indexX; 0..width) { 33 | auto matrixX = indexX - center[0]; 34 | if (matrixX + imageX < 0 || matrixX + imageX >= image.width) continue; 35 | 36 | auto matrixValue = matrix[indexX + indexY * width]; 37 | accum += matrixValue; 38 | 39 | //TODO: Shorthand once color operation is implemented 40 | color = color + image[imageX + matrixX, imageY + matrixY] * matrixValue; 41 | } 42 | } 43 | 44 | ret[imageX, imageY] = color * (1/accum); 45 | } 46 | } 47 | 48 | return ret; 49 | } 50 | /// Ditto 51 | auto convolved(string axis, V)(const Image!V image, const real[] matrix, int center) { 52 | auto ret = image.dup; 53 | 54 | static if (canFind(axis, 'x')) { 55 | // Apply matrix horizontally 56 | ret = ret.convolved(matrix, cast(int)matrix.length, [center, 0]); 57 | } 58 | 59 | static if (canFind(axis, 'y')) { 60 | // Apply matrix vertically 61 | ret = ret.convolved(matrix, 1, [0, center]); 62 | } 63 | 64 | return ret; 65 | } 66 | /// Ditto 67 | auto convolved(string axis, V)(const Image!V image, const real[] matrix) { 68 | return image.convolved!axis(matrix, cast(int)(matrix.length / 2)); 69 | } 70 | 71 | //TODO: Add tests 72 | -------------------------------------------------------------------------------- /source/daffodil/filter/gaussian.d: -------------------------------------------------------------------------------- 1 | /** 2 | * A gaussian filter (aka gaussian blur) is a convolution 3 | * (:d:mod:`daffodil.filter.convolve`) using a matrix created from a gaussian 4 | * distribution. 5 | */ 6 | module daffodil.filter.gaussian; 7 | 8 | import std.math; 9 | 10 | import daffodil.image; 11 | import daffodil.filter; 12 | 13 | /** 14 | * Evaluate the gaussian/normal distribution for a given ``x``, ``stDev`` and 15 | * ``mean``. 16 | */ 17 | real gaussianDistribution(real x, real stDev = 1, real mean = 0) { 18 | return 1/(stDev * sqrt(2 * PI))* E.pow(-pow(x - mean, 2)/(2 * pow(stDev, 2))); 19 | } 20 | 21 | @("gaussian distribution") 22 | unittest { 23 | static void assertEq(real a, real b) { 24 | return assert(approxEqual(a, b)); 25 | } 26 | 27 | // Standard Normal Distribution 28 | assertEq(gaussianDistribution(0), 0.398942); 29 | assertEq(gaussianDistribution(1), 0.241971); 30 | assertEq(gaussianDistribution(1), gaussianDistribution(-1)); 31 | assertEq(gaussianDistribution(2), 0.053991); 32 | assertEq(gaussianDistribution(2), gaussianDistribution(-2)); 33 | assertEq(gaussianDistribution(3), 0.004432); 34 | assertEq(gaussianDistribution(3), gaussianDistribution(-3)); 35 | 36 | // Changed standard deviation 37 | assertEq(gaussianDistribution(0, 2), 0.199471); 38 | assertEq(gaussianDistribution(2, 2), 0.120985); 39 | 40 | // Changed mean 41 | assertEq(gaussianDistribution(0, 2, 2), 0.120985); 42 | assertEq(gaussianDistribution(2, 2, 2), 0.199471); 43 | assertEq(gaussianDistribution(4, 2, 2), 0.120985); 44 | } 45 | 46 | /** 47 | * Create a 1D matrix of a discrete gaussian distribution with a given standard 48 | * deviation and the number of standard deviations to stop generating at. The 49 | * result is mirrored with guaranteed odd length. 50 | * 51 | * The result can be used to convolve a image. 52 | */ 53 | real[] gaussianMatrix(real stDev = 1, real maxDev = 3) { 54 | auto range = cast(uint)ceil(stDev * maxDev); 55 | auto ret = new real[1 + 2*range]; 56 | 57 | ret[range] = gaussianDistribution(0, stDev); 58 | foreach (i; 1..range + 1) { 59 | ret[range + i] = ret[range - i] = gaussianDistribution(i, stDev); 60 | } 61 | 62 | return ret; 63 | } 64 | 65 | @("gaussian matrix") 66 | unittest { 67 | const matrix = [0.004432, 0.053991, 0.241971, 0.398942, 0.241971, 0.053991, 0.004432]; 68 | assert(approxEqual(gaussianMatrix(), matrix)); 69 | 70 | assert(gaussianMatrix(10).length == 61); 71 | } 72 | 73 | /** 74 | * Return a copy of ``image`` with a gaussian blur applied across axies 75 | * ``axis`` with a given standard deviation and the number of standard 76 | * deviations to stop at. 77 | */ 78 | auto gaussianBlurred(string axis = "xy", V)(const Image!V image, real stDev = 1, real maxDev = 3) { 79 | auto matrix = gaussianMatrix(stDev, maxDev); 80 | 81 | return image.convolved!axis(matrix); 82 | } 83 | 84 | @("gaussian blur") 85 | unittest { 86 | import daffodil; 87 | 88 | auto image = new Image!ubyte(2, 2, 3, RGB); 89 | image[0, 0] = [1f, 1f, 1f]; 90 | image[0, 1] = [1f, 0f, 0f]; 91 | image[1, 0] = [0f, 1f, 0f]; 92 | image[1, 1] = [0f, 0f, 1f]; 93 | 94 | auto blurred = image.gaussianBlurred(2); 95 | assert(blurred[0, 0] == [130, 130, 122]); 96 | assert(blurred[0, 1] == [130, 117, 122]); 97 | assert(blurred[1, 0] == [114, 130, 122]); 98 | assert(blurred[1, 1] == [114, 117, 122]); 99 | } 100 | -------------------------------------------------------------------------------- /source/daffodil/filter/package.d: -------------------------------------------------------------------------------- 1 | /** 2 | * This module provides various filter functions that can be performed on 3 | * images. Filter functions differ from transformations 4 | * (:d:mod:`daffodil.transform`) in that they cannot be performed in-place, ie. 5 | * a copy of the image is required to perform the filter. 6 | */ 7 | module daffodil.filter; 8 | 9 | public { 10 | import daffodil.filter.convolve; 11 | import daffodil.filter.gaussian; 12 | } 13 | -------------------------------------------------------------------------------- /source/daffodil/image.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Exposes the :d:class:`Image` class, which provides basic storage, access and 3 | * conversion of images. 4 | */ 5 | module daffodil.image; 6 | 7 | import std.conv; 8 | import std.math; 9 | import std.typecons; 10 | 11 | import daffodil; 12 | import daffodil.util.data; 13 | import daffodil.util.range; 14 | 15 | /** 16 | * Generic Image class for a given pixel format. 17 | * Holds a two dimensional array of pixels in a specified format, allowing 18 | * generic transformations and manipulations using other interfaces. 19 | */ 20 | class Image(V) if (isColorValue!V) { 21 | /** 22 | * Storage type for each value in a channel. 23 | */ 24 | alias Value = V; 25 | 26 | /// The metadata of the image. 27 | MetaData meta; 28 | 29 | private { 30 | size_t[2] _size; 31 | size_t _channelCount; 32 | Value[] raster; 33 | 34 | const ColorSpace* colorSpace; 35 | } 36 | 37 | /** 38 | * Create an empty Image given a width and a height. 39 | * Pixels default to `init` 40 | */ 41 | this(size_t width, size_t height, size_t channelCount, 42 | const ColorSpace* colorSpace, MetaData meta = null) { 43 | _size = [width, height]; 44 | this._channelCount = channelCount; 45 | this.colorSpace = colorSpace; 46 | raster.length = width * height * channelCount; 47 | this.meta = meta; 48 | } 49 | 50 | /** 51 | * Create a Image from a given image range, color space and optional 52 | * metadata. 53 | */ 54 | this(R)(R range, MetaData meta = null) if (isImageRange!(R, PixelData)) { 55 | this(range.width, range.height, range.channelCount, range.colorSpace, meta); 56 | 57 | foreach (pixel; range) { 58 | this[pixel.x, pixel.y] = pixel.data; 59 | } 60 | } 61 | 62 | /// Create a image from data copied off another image. 63 | this(const Image other) { 64 | this._size = other._size; 65 | this._channelCount = other._channelCount; 66 | this.raster = other.raster.dup; 67 | this.colorSpace = other.colorSpace; 68 | // TODO: Take copy here? 69 | this.meta = cast(MetaData)other.meta; 70 | } 71 | 72 | /** 73 | * Get the width and height of the Image. 74 | */ 75 | @property auto width() const { return _size[0]; } 76 | /// Ditto 77 | @property auto height() const { return _size[1]; } 78 | /// Ditto 79 | @property auto size() const { return _size; } 80 | /// Ditto 81 | auto opDollar(size_t pos)() const { return _size[pos]; } 82 | 83 | /** 84 | * Get the number of channels in the image. 85 | */ 86 | @property auto channelCount() const { return _channelCount; } 87 | 88 | /** 89 | * Get a pixel of the given pixel format at a location on the image. 90 | */ 91 | auto opIndex(size_t x, size_t y) const { 92 | auto index = (x + y * width) * channelCount; 93 | auto slice = raster[index..index + channelCount]; 94 | 95 | return Pixel!Value(cast(Value[])slice, colorSpace); 96 | } 97 | /// Ditto 98 | void opIndexAssign(const Pixel!Value color, size_t x, size_t y) { 99 | (cast(Pixel!Value)this[x, y]).opAssign(color); 100 | } 101 | /// Ditto 102 | void opIndexAssign(real[] values, size_t x, size_t y) { 103 | assert(values.length == channelCount); 104 | auto index = (x + y * width) * channelCount; 105 | foreach (i; 0..channelCount) { 106 | raster[index + i] = values[i].toColorValue!Value; 107 | } 108 | } 109 | 110 | /** 111 | * Create a new color within the color space of the image. 112 | */ 113 | auto newColor() const { 114 | return Pixel!Value(channelCount, colorSpace); 115 | } 116 | 117 | /// Return a copy of the image. 118 | @property Image dup() const { 119 | return new Image(this); 120 | } 121 | 122 | /// 123 | override string toString() const { 124 | return raster.to!string; 125 | } 126 | 127 | /// Return a image range for the image. 128 | @property auto range() const { 129 | struct Range { 130 | const Image image; 131 | real[] outBuffer; 132 | 133 | this(const Image image) { 134 | this.image = image; 135 | outBuffer = new real[channelCount]; 136 | } 137 | @property auto width() { return image.width; } 138 | @property auto height() { return image.height;} 139 | @property auto channelCount() { return image.channelCount; } 140 | @property auto colorSpace() { return image.colorSpace; } 141 | @property auto front() { return outBuffer; } 142 | @property auto empty() { return false; } 143 | void popFront() {} 144 | real[] opIndex(size_t x, size_t y) { 145 | auto color = image[x, y]; 146 | foreach (index; 0..channelCount) { 147 | outBuffer[index] = color[index].toReal; 148 | } 149 | return outBuffer; 150 | } 151 | } 152 | static assert(isRandomAccessImageRange!Range); 153 | 154 | return Range(this); 155 | } 156 | } 157 | 158 | @("Image size properties") 159 | unittest { 160 | auto image = new Image!uint(123, 234, 1, RGB); 161 | assert(image.width == 123); 162 | assert(image.height == 234); 163 | assert(image.size == [123, 234]); 164 | assert(image.opDollar!0 == 123); 165 | assert(image.opDollar!1 == 234); 166 | } 167 | 168 | @("Image to string") 169 | unittest { 170 | auto image = new Image!uint(2, 2, 3, RGB); 171 | assert(image.toString == "[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]"); 172 | } 173 | 174 | @("Image range") 175 | unittest { 176 | auto image = new Image!uint(2, 2, 3, RGB); 177 | assert(isRandomAccessImageRange!(typeof(image.range))); 178 | } 179 | -------------------------------------------------------------------------------- /source/daffodil/meta.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Exposes the :d:class:`MetaData` class, used to store metadata for images. 3 | */ 4 | module daffodil.meta; 5 | 6 | /** 7 | * Metadata class for images. 8 | * Any image format can defined subclasses to provide more format-specific 9 | * metadata. 10 | */ 11 | class MetaData { 12 | /// 13 | size_t width; 14 | /// 15 | size_t height; 16 | } 17 | -------------------------------------------------------------------------------- /source/daffodil/package.d: -------------------------------------------------------------------------------- 1 | /** 2 | * This module provides the public interface for Daffodil. 3 | */ 4 | module daffodil; 5 | 6 | import std.path; 7 | import std.stdio; 8 | import std.typecons; 9 | import std.algorithm; 10 | 11 | import daffodil.util.data; 12 | import daffodil.util.range; 13 | 14 | public { 15 | import daffodil.meta; 16 | import daffodil.image; 17 | import daffodil.pixel; 18 | import daffodil.colorspace; 19 | import daffodil.util.errors; 20 | 21 | // Submodules 22 | static { 23 | import filter = daffodil.filter; 24 | import transform = daffodil.transform; 25 | // Image Formats 26 | import bmp = daffodil.bmp; 27 | } 28 | } 29 | 30 | /** 31 | * Detects the :d:struct:`Format` a given input is in. 32 | */ 33 | Format detectFormat(T)(T data) if (isDataRange!T) { 34 | auto range = data.inputRangeObject; 35 | foreach (format; formats) { 36 | try { 37 | if (format.check(range)) { 38 | return format; 39 | } 40 | } catch (ImageException e) { 41 | continue; 42 | } 43 | 44 | } 45 | throw new NotSupported("Unknown Format"); 46 | } 47 | /// Ditto 48 | auto detectFormat(T)(T loadeable) if (isLoadeable!T) { 49 | return detectFormat(dataLoad(loadeable)); 50 | } 51 | /// Ditto 52 | Format detectFormat(V = real)(const Image!V image) if (isColorValue!V) { 53 | auto typeInfo = typeid(image.meta); 54 | foreach (format; formats) { 55 | if (format.metaType && format.metaType == typeInfo) { 56 | return format; 57 | } 58 | } 59 | throw new NotSupported("Unknown Format"); 60 | } 61 | 62 | /** 63 | * Loads the metadata from a given input. 64 | */ 65 | auto loadMeta(T)(T data) if (isDataRange!T) { 66 | auto format = detectFormat(data); 67 | return format.loadMeta(data.inputRangeObject); 68 | } 69 | /// Ditto 70 | auto loadMeta(T)(T loadeable) if (isLoadeable!T) { 71 | return loadMeta(dataLoad(loadeable)); 72 | } 73 | 74 | /** 75 | * Loads a :d:class:`Image` from a given input. 76 | */ 77 | auto load(V = real, T)(T data) if (isColorValue!V && isDataRange!T) { 78 | auto range = data.inputRangeObject; 79 | auto format = detectFormat(data); 80 | auto meta = format.loadMeta(range); 81 | return new Image!V(format.loadImage(range, meta), meta); 82 | } 83 | /// Ditto 84 | auto load(V = real, T)(T loadeable) if (isColorValue!V && isLoadeable!T) { 85 | return load!V(dataLoad(loadeable)); 86 | } 87 | 88 | /** 89 | * Saves a particular :d:class:`Image` to a given output. 90 | */ 91 | void save(V = real, T)(const Image!V image, T data) if (isColorValue!V && isOutRange!T) { 92 | auto format = detectFormat(image); 93 | format.save(data.outputRangeObject!ubyte, image.range.imageRangeObject, image.meta); 94 | } 95 | /// Ditto 96 | void save(V = real)(const Image!V image, string path) if (isColorValue!V) { 97 | // Specialcase for paths, to match by extension 98 | Nullable!Format format; 99 | foreach (f; formats) { 100 | if (f.extensions.canFind(path.extension)) { 101 | format = f; 102 | break; 103 | } 104 | } 105 | 106 | if (format.isNull) { 107 | format = detectFormat(image); 108 | } 109 | 110 | auto data = dataSave(path).outputRangeObject!ubyte; 111 | format.save(data, image.range.imageRangeObject, image.meta); 112 | } 113 | /// Ditto 114 | void save(V = real, T)(const Image!V image, T saveable) if (isColorValue!V && isSaveable!T && !is(T == string)) { 115 | return save(image, dataSave(saveable)); 116 | } 117 | 118 | /// 119 | alias DataRange = InputRange!ubyte; 120 | /// 121 | template isDataRange(T) { 122 | enum isDataRange = isInputRange!T && is(ElementType!T == ubyte); 123 | } 124 | /// 125 | alias OutRange = OutputRange!ubyte; 126 | /// 127 | alias isOutRange(T) = isOutputRange!(T, ubyte); 128 | 129 | /** 130 | * A struct for metadata about an Image Format. See :d:func:`registerFormat` for 131 | * more information. 132 | */ 133 | struct Format { 134 | /// 135 | string name; 136 | /// 137 | bool function(DataRange) check; 138 | /// 139 | MetaData function(DataRange) loadMeta; 140 | /// 141 | ImageRange!PixelData function(DataRange, MetaData) loadImage; 142 | /// 143 | void function(OutRange, RandomAccessImageRange!(real[]), const MetaData) save; 144 | /// 145 | string[] extensions; 146 | /// 147 | TypeInfo metaType; 148 | } 149 | 150 | private Format[] formats; 151 | private Format[string] formatsByExt; 152 | 153 | /** 154 | * Register a new format for auto-detection with :d:func:`detectFormat`, 155 | * :d:func:`loadMeta` and :d:func:`load` functions. 156 | */ 157 | void registerFormat(Format format) { 158 | formats ~= format; 159 | 160 | foreach (ext; format.extensions) { 161 | assert(ext !in formatsByExt); 162 | formatsByExt[ext] = format; 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /source/daffodil/pixel.d: -------------------------------------------------------------------------------- 1 | /** 2 | * This module contains the implementation for the internal pixel storage 3 | * mechanisms. 4 | */ 5 | module daffodil.pixel; 6 | 7 | import std.conv; 8 | import std.array; 9 | import std.format; 10 | import std.traits; 11 | import std.algorithm; 12 | 13 | import daffodil.colorspace; 14 | 15 | /** 16 | * The storage struct for a color. 17 | */ 18 | struct Pixel(V) if (isColorValue!V) { 19 | /// The type used to store individual values for a color 20 | alias Value = V; 21 | 22 | /// The values of the color 23 | Value[] values; 24 | 25 | /// The color space used for operations with the color 26 | const ColorSpace* colorSpace; 27 | 28 | alias values this; 29 | 30 | /// 31 | this(Value[] values, const ColorSpace* colorSpace) { 32 | this.values = values; 33 | this.colorSpace = colorSpace; 34 | } 35 | 36 | /// Ditto 37 | static if (!is(V == real)) { 38 | this(real[] values, const ColorSpace* colorSpace) { 39 | this(values.toColorValues!V, colorSpace); 40 | } 41 | } 42 | 43 | /// Ditto 44 | this(size_t size, const ColorSpace* colorSpace) { 45 | this(new Value[size], colorSpace); 46 | } 47 | 48 | // TODO: Memory optimisation 49 | 50 | /// 51 | Pixel!V opBinary(string op : "*")(const real other) const { 52 | real[] target = new real[this.length]; 53 | colorSpace.opScalarMul(values.toReals, other, target); 54 | return Pixel!V(target, colorSpace); 55 | } 56 | 57 | /// 58 | Pixel!V opBinary(string op : "+")(const Pixel!V other) const { 59 | // TODO: Check other.colorSpace 60 | real[] target = new real[this.length]; 61 | colorSpace.opColorAdd(values.toReals, other.values.toReals, target); 62 | return Pixel!V(target, colorSpace); 63 | } 64 | 65 | /// 66 | void opOpAssign(string op : "*")(const real other) { 67 | colorSpace.opScalarMul(values, other, values); 68 | } 69 | 70 | /// 71 | void opOpAssign(string op : "+")(const Pixel!V other) { 72 | colorSpace.opColorAdd(values, other, values); 73 | } 74 | 75 | /// 76 | void opAssign(const Pixel!V other) { 77 | assert(other.length == this.length); 78 | foreach (index; 0..this.length) { 79 | this[index] = other[index]; 80 | } 81 | } 82 | 83 | /// Clear all the color values to 0 84 | void clear() { 85 | foreach (index; 0..this.length) { 86 | this[index] = 0; 87 | } 88 | } 89 | 90 | /// Return a duplicate color in the same color space 91 | @property auto dup() { 92 | return Pixel!V(values.dup, colorSpace); 93 | } 94 | } 95 | 96 | /** 97 | * Template for checking whether a type is a valid color value. Color values are 98 | * what daffodil stores internally. 99 | * 100 | * Any floating point type, unsigned integreal or valid 101 | * :d:func:`isCustomColorValue` is a valid color value. 102 | */ 103 | template isColorValue(V) { 104 | enum isColorValue = isFloatingPoint!V || 105 | isIntegral!V && isUnsigned!V || 106 | isCustomColorValue!V; 107 | } 108 | 109 | /// 110 | @("isColorValue") 111 | unittest { 112 | assert(isColorValue!ubyte); 113 | assert(isColorValue!uint); 114 | assert(isColorValue!ulong); 115 | assert(!isColorValue!int); 116 | assert(isColorValue!float); 117 | assert(isColorValue!real); 118 | } 119 | 120 | /** 121 | * Template for checking whether a type is a valid custom color value. A custom 122 | * color value must have a static ``init`` property, a static ``fromReal`` that 123 | * converts a real to ``V``, and a ``toReal`` function that converts a ``V`` 124 | * back to a real. 125 | */ 126 | template isCustomColorValue(V) { 127 | enum isCustomColorValue = is(typeof( 128 | (inout int = 0) { 129 | V v = V.init; 130 | v = V.fromReal(cast(real)1.0); 131 | real r = v.toReal(); 132 | } 133 | )); 134 | } 135 | 136 | /// 137 | @("isCustomColorValue") 138 | unittest { 139 | static struct IntCV { 140 | int value = 0; 141 | 142 | static auto fromReal(real v) { 143 | return IntCV(cast(int)(v / int.max)); 144 | } 145 | 146 | real toReal() { 147 | return cast(real)value / int.max; 148 | } 149 | } 150 | 151 | assert(isCustomColorValue!IntCV); 152 | assert(isColorValue!IntCV); 153 | } 154 | 155 | /** 156 | * Converts a real to a specified color value. 157 | */ 158 | V toColorValue(V)(const real value) if (isColorValue!V) { 159 | static if (isFloatingPoint!V) { 160 | return value; 161 | } else static if (isIntegral!V) { 162 | return cast(V)(V.max * value.clamp(0, 1)); 163 | } else { 164 | return V.fromReal(value); 165 | } 166 | } 167 | 168 | /** 169 | * Converts an array of reals to an array of specified color values. 170 | */ 171 | V[] toColorValues(V)(const real[] values) if (isColorValue!V) { 172 | return values.map!(toColorValue!V).array; 173 | } 174 | 175 | /** 176 | * Converts a valid color value to a real. 177 | */ 178 | real toReal(V)(const V value) if (isColorValue!V) { 179 | static if (isFloatingPoint!V) { 180 | return value; 181 | } else static if (isIntegral!V) { 182 | return cast(real)value / V.max; 183 | } else { 184 | return value.toReal(); 185 | } 186 | } 187 | 188 | /** 189 | * Converts an array of valid color values to an array of reals. 190 | */ 191 | real[] toReals(V)(const V[] values) if (isColorValue!V) { 192 | return values.map!(toReal!V).array; 193 | } 194 | -------------------------------------------------------------------------------- /source/daffodil/transform/flip.d: -------------------------------------------------------------------------------- 1 | module daffodil.transform.flip; 2 | 3 | import std.algorithm; 4 | 5 | import daffodil.image; 6 | 7 | /** 8 | * Flip ``image`` along ``axis`` in-place. ``axis`` may contain ``x``, ``y`` or 9 | * both. 10 | */ 11 | void flip(string axis, V)(Image!V image) { 12 | static if (canFind(axis, 'x')) { 13 | foreach (y; 0..image.height) { 14 | foreach (x; 0..image.width/2) { 15 | auto temp = image[$ - x - 1, y].dup; 16 | image[$ - x - 1, y] = image[x, y]; 17 | image[x, y] = temp; 18 | } 19 | } 20 | } 21 | 22 | static if (canFind(axis, 'y')) { 23 | foreach (x; 0..image.width) { 24 | foreach(y; 0..image.height/2) { 25 | auto temp = image[x, $ - y - 1].dup; 26 | image[x, $ - y - 1] = image[x, y]; 27 | image[x, y] = temp; 28 | } 29 | } 30 | } 31 | } 32 | 33 | /** 34 | * Same as :d:func:`flip` but performs the operation on a copy of ``image``. 35 | * Allows for stringing operations together. 36 | */ 37 | auto flipped(string axis, V)(const Image!V image) { 38 | auto output = image.dup; 39 | output.flip!axis(); 40 | return output; 41 | } 42 | 43 | @("flip transformation") 44 | unittest { 45 | import daffodil; 46 | 47 | auto image = new Image!ubyte(2, 2, 3, RGB); 48 | image[0, 0] = [1f, 1f, 1f]; 49 | image[0, 1] = [1f, 0f, 0f]; 50 | image[1, 0] = [0f, 1f, 0f]; 51 | image[1, 1] = [0f, 0f, 1f]; 52 | 53 | image.flip!"x"(); 54 | assert(image[0, 0] == [ 0, 255, 0]); 55 | assert(image[0, 1] == [ 0, 0, 255]); 56 | assert(image[1, 0] == [255, 255, 255]); 57 | assert(image[1, 1] == [255, 0, 0]); 58 | 59 | image.flip!"y"(); 60 | assert(image[0, 0] == [ 0, 0, 255]); 61 | assert(image[0, 1] == [ 0, 255, 0]); 62 | assert(image[1, 0] == [255, 0, 0]); 63 | assert(image[1, 1] == [255, 255, 255]); 64 | 65 | image = image.flipped!"xy"(); 66 | assert(image[0, 0] == [255, 255, 255]); 67 | assert(image[0, 1] == [255, 0, 0]); 68 | assert(image[1, 0] == [ 0, 255, 0]); 69 | assert(image[1, 1] == [ 0, 0, 255]); 70 | } 71 | -------------------------------------------------------------------------------- /source/daffodil/transform/package.d: -------------------------------------------------------------------------------- 1 | /** 2 | * This module provides various transformation functions that can be performed 3 | * on images. Transform functions differ from filters (:d:mod:`daffodil.filter`) 4 | * in that they can be performed in-place. 5 | */ 6 | module daffodil.transform; 7 | 8 | public { 9 | import daffodil.transform.flip; 10 | } 11 | -------------------------------------------------------------------------------- /source/daffodil/util/data.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Provides common data manipulation functions. 3 | */ 4 | module daffodil.util.data; 5 | 6 | import std.math; 7 | import std.meta; 8 | import std.stdio; 9 | import std.typecons; 10 | import std.algorithm; 11 | import core.bitop; 12 | 13 | import daffodil; 14 | import daffodil.util.range; 15 | import daffodil.util.errors; 16 | 17 | private const FILE_CHUNK_SIZE = 4096; 18 | 19 | /** 20 | * Converts any sort of possible input/location into a forward ubyte range. 21 | * Useful for providing simple overloading of load functions for a variety of inputs. 22 | */ 23 | DataRange dataLoad(File file) { 24 | return file.byChunk(FILE_CHUNK_SIZE).joiner.inputRangeObject; 25 | } 26 | /// Ditto 27 | DataRange dataLoad(string path) { 28 | import std.path : expandTilde; 29 | return dataLoad(File(path.expandTilde, "r")); 30 | } 31 | 32 | alias Loadeable = AliasSeq!(File, string); 33 | 34 | template isLoadeable(E) { 35 | enum isLoadeable = staticIndexOf!(E, Loadeable) != -1; 36 | } 37 | 38 | /** 39 | * Converts any sort of possible output into a output range. 40 | * Useful for providing simple overloading of save functions for a variety of outputs. 41 | */ 42 | OutRange dataSave(File file) { 43 | struct F { 44 | File f; 45 | this(File f) { this.f = f; } 46 | void put(ubyte data) { f.rawWrite([data]); } 47 | } 48 | return F(file).outputRangeObject!ubyte; 49 | } 50 | /// Ditto 51 | OutRange dataSave(string path) { 52 | import std.path : expandTilde; 53 | return dataSave(File(path.expandTilde, "w")); 54 | } 55 | 56 | alias Saveable = AliasSeq!(File, string); 57 | 58 | template isSaveable(E) { 59 | enum isSaveable = staticIndexOf!(E, Saveable) != -1; 60 | } 61 | 62 | struct PixelData { 63 | size_t x, y; 64 | real[] data; 65 | } 66 | 67 | /// Calculate the padding needed when padding a bpp with a width to a multiple of bytes 68 | auto paddingFor(size_t width, size_t bpp, size_t padding) { 69 | auto bitsPerRow = width * bpp; 70 | auto paddingBits = padding * 8; 71 | auto bitRowSize = ((bitsPerRow + paddingBits - 1) / paddingBits) * paddingBits; 72 | return (bitRowSize - bitsPerRow) / 8; 73 | } 74 | 75 | /** 76 | * Documentation 77 | */ 78 | auto maskedRasterLoad(R, T)( 79 | R data, 80 | T[] mask, 81 | size_t bpp, 82 | ptrdiff_t _width, 83 | ptrdiff_t _height, 84 | const ColorSpace* _colorSpace, 85 | size_t padding = 1) if (isInputRange!R && 86 | is(ElementType!R == ubyte)) { 87 | // currently don't support non multiples of 8 88 | assert(bpp % 8 == 0); 89 | 90 | struct Range { 91 | R range; 92 | size_t x = 0; 93 | size_t y = 0; 94 | size_t channelCount; 95 | real[] loadBuffer; 96 | 97 | this(R range) { 98 | this.range = range; 99 | this.channelCount = mask.length; 100 | this.loadBuffer = new real[channelCount]; 101 | } 102 | 103 | @property bool empty() { 104 | return y >= height; 105 | } 106 | 107 | @property PixelData front() { 108 | // TODO: Make this happen in popFront 109 | maskedLoad(loadBuffer, data, mask, bpp); 110 | 111 | // Adjust for negative heights 112 | auto yReal = _height < 0 ? height - y - 1 : y; 113 | return PixelData(x, yReal, loadBuffer); 114 | } 115 | 116 | private void popPadding() { 117 | auto pad = paddingFor(_width, bpp, padding); 118 | range.popFrontExactly(pad); 119 | } 120 | 121 | void popFront() { 122 | if (x + 1 < _width) x++; 123 | else { 124 | // perform padding (next multiple of padding for x) 125 | popPadding(); 126 | x = 0; 127 | y++; 128 | } 129 | } 130 | 131 | @property size_t width() { return _width; } 132 | @property size_t height() { return abs(_height); } 133 | @property auto colorSpace() const { return _colorSpace; } 134 | } 135 | 136 | return Range(data); 137 | } 138 | 139 | void maskedLoad(R, T)(real[] target, R range, T[] masks, size_t bpp) { 140 | auto data = range.take(bpp / 8).array; 141 | enforce!UnexpectedEndOfData(data.length == bpp / 8); 142 | 143 | foreach (maskIndex, mask; masks) { 144 | auto bitStart = T.sizeof * 8 - bsr(mask) - 1; 145 | auto bitEnd = T.sizeof * 8 - bsf(mask); 146 | 147 | auto max = pow(2f, bitEnd - bitStart) - 1f; 148 | target[maskIndex] = 0; 149 | // TODO: Optimise 150 | foreach (index, value; data) { 151 | target[maskIndex] += (value >> (bitStart - index * 8)) / max; 152 | } 153 | } 154 | } 155 | 156 | void maskedRasterSave(R, O, T)( 157 | R image, 158 | O output, 159 | T[] mask, 160 | size_t bpp, 161 | ptrdiff_t width, 162 | ptrdiff_t height, 163 | size_t padding = 1) if (isOutputRange!(O, ubyte) && 164 | isRandomAccessImageRange!R && 165 | is(ElementType!R == real[])) { 166 | assert(bpp % 8 == 0); 167 | 168 | ubyte[] saveBuffer = new ubyte[bpp / 8]; 169 | 170 | foreach (y; 0..abs(height)) { 171 | auto yReal = height < 0 ? -height - y - 1 : y; 172 | 173 | foreach (x; 0..width) { 174 | auto data = image[x, yReal]; 175 | maskedSave(data, saveBuffer, mask, bpp); 176 | put(output, saveBuffer); 177 | } 178 | 179 | // Apply padding 180 | auto pad = paddingFor(width, bpp, padding); 181 | foreach (_; 0..pad) { 182 | output.put(cast(ubyte)0); 183 | } 184 | } 185 | } 186 | 187 | void maskedSave(T)(real[] data, ubyte[] target, T[] masks, size_t bpp) { 188 | foreach (index; 0..target.length) { 189 | target[index] = 0; 190 | } 191 | 192 | foreach (maskIndex, mask; masks) { 193 | auto bitStart = T.sizeof * 8 - bsr(mask) - 1; 194 | auto bitEnd = T.sizeof * 8 - bsf(mask); 195 | 196 | auto max = pow(2f, bitEnd - bitStart) - 1f; 197 | 198 | // TODO: Optimise 199 | foreach (index; 0..target.length) { 200 | auto value = cast(size_t)(data[maskIndex] * max); 201 | target[index] += cast(ubyte)(value << (bitStart - index * 8)); 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /source/daffodil/util/errors.d: -------------------------------------------------------------------------------- 1 | module daffodil.util.errors; 2 | 3 | public import std.exception; 4 | 5 | /// Create a basic exception subclass with the default exception constructor. 6 | mixin template classException(string name, base = Exception) { 7 | mixin(q{ 8 | class }~name~q{ : base { 9 | @safe pure nothrow this(string m, string f = __FILE__, size_t l = __LINE__, Throwable n = null ) { 10 | super(m, f, l, n); 11 | } 12 | } 13 | }); 14 | } 15 | 16 | /// Exception thrown when a image failed to load 17 | mixin classException!"ImageException"; 18 | 19 | /// Exception thrown when the header for an image is invalid 20 | mixin classException!("InvalidHeader", ImageException); 21 | 22 | /// Exception thrown when image data was not in a required format, ie. wrong file type 23 | mixin classException!("InvalidImageType", ImageException); 24 | 25 | /// Exception thrown when a image has unsupported features. 26 | mixin classException!("NotSupported", ImageException); 27 | 28 | /// Exception thrown when the end of a file is reached before all expected data was read 29 | mixin classException!("UnexpectedEndOfData", ImageException); 30 | -------------------------------------------------------------------------------- /source/daffodil/util/headers.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Templates for easily parsing headers using structs 3 | */ 4 | module daffodil.util.headers; 5 | 6 | import std.array; 7 | import std.meta; 8 | import std.traits; 9 | import std.bitmanip; 10 | import std.algorithm; 11 | 12 | import daffodil.util.range; 13 | import daffodil.util.errors; 14 | 15 | /** 16 | * Attribute signifying the endianess of a field or type 17 | */ 18 | enum Endianess { 19 | little, 20 | big, 21 | } 22 | 23 | private template endianess(alias field, Endianess default_) { 24 | template isEndianess(alias E) { 25 | enum isEndianess = is(typeof(E) == Endianess); 26 | } 27 | 28 | // Use either the given default or the last attribute 29 | alias endianessAttrs = Filter!(isEndianess, __traits(getAttributes, field)); 30 | static if (endianessAttrs.length > 0) { 31 | enum endianess = endianessAttrs[$-1]; 32 | } else { 33 | enum endianess = default_; 34 | } 35 | } 36 | 37 | /** 38 | * Documentation 39 | */ 40 | template convertable(T) { 41 | enum convertable = isIntegral!T || isSomeChar!T || isBoolean!T || 42 | (__traits(isPOD, T) && isAggregateType!T); 43 | } 44 | 45 | /** 46 | * Given a type and endianess, convert a input range of ubyte to that type. 47 | * Does not support any dynamically sized types or non-standard alignments 48 | */ 49 | T parseHeader(T, Endianess e = Endianess.little, R)(R data) if (convertable!T && 50 | isInputRange!R && 51 | is(ElementType!R == ubyte)) { 52 | static if (isAggregateType!T) { 53 | T value; 54 | 55 | foreach (field; FieldNameTuple!T) { 56 | enum member = "value."~field; 57 | enum endian = endianess!(mixin(member), e); 58 | mixin(member~" = parseHeader!(typeof("~member~"), endian)(data);"); 59 | } 60 | return value; 61 | } else { 62 | auto taken = data.take(T.sizeof).array; 63 | enforce!UnexpectedEndOfData(taken.length == T.sizeof); 64 | 65 | ubyte[T.sizeof] fieldData = taken.array[0..T.sizeof]; 66 | 67 | static if (e is Endianess.little) { 68 | return littleEndianToNative!T(fieldData); 69 | } else { 70 | return bigEndianToNative!T(fieldData); 71 | } 72 | } 73 | } 74 | 75 | @("Able to parse headers") 76 | unittest { 77 | static struct Data { 78 | ushort field1; 79 | @(Endianess.little) 80 | ushort field2; 81 | } 82 | 83 | ubyte[] data = [0xDE, 0xAD, 0xAD, 0xDE]; 84 | 85 | Data d = parseHeader!(Data, Endianess.big)(data.iter); 86 | assert(d.field1 == 0xDEAD); 87 | assert(d.field2 == 0xDEAD); 88 | } 89 | 90 | /** 91 | * Given a instance and default endianess, write that instance to a output range of ubyte. 92 | */ 93 | void writeHeader(Endianess e = Endianess.little, T, R)(T value, R output) if (convertable!T && 94 | isOutputRange!(R, ubyte)) { 95 | static if (isAggregateType!T) { 96 | foreach (field; FieldNameTuple!T) { 97 | enum member = "value."~field; 98 | enum endian = endianess!(mixin(member), e); 99 | writeHeader!endian(mixin(member), output); 100 | } 101 | } else { 102 | ubyte[T.sizeof] fieldData; 103 | 104 | static if (e is Endianess.little) { 105 | fieldData = nativeToLittleEndian(value); 106 | } else { 107 | fieldData = nativeToBigEndian(value); 108 | } 109 | 110 | put(output, fieldData[]); 111 | } 112 | } 113 | 114 | @("Able to write headers") 115 | unittest { 116 | import std.outbuffer; 117 | static struct Data { 118 | ushort field1; 119 | @(Endianess.little) 120 | ushort field2; 121 | } 122 | 123 | Data d = { field1: 0xDEAD, field2: 0xDEAD }; 124 | auto buffer = new OutBuffer(); 125 | writeHeader!(Endianess.big)(d, buffer); 126 | 127 | ubyte[] data = [0xDE, 0xAD, 0xAD, 0xDE]; 128 | assert(buffer.toBytes() == data); 129 | } 130 | 131 | /** 132 | * A mixin template that adds by-field casting. 133 | * Allows headers to be implemented as templates given a version with minimal effort. 134 | */ 135 | mixin template Upcast() { 136 | T opCast(T)() if (convertable!T) { 137 | import std.meta; 138 | import std.traits; 139 | 140 | alias This = typeof(this); 141 | template hasMember(string name) { 142 | enum hasMember = std.traits.hasMember!(This, name); 143 | } 144 | alias inCommon = Filter!(hasMember, FieldNameTuple!T); 145 | // Cast by assigning by member 146 | T value; 147 | foreach (field; inCommon) { 148 | mixin("value."~field~" = cast(typeof(value."~field~"))this."~field~";"); 149 | } 150 | return value; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /source/daffodil/util/range.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Provides extended functionality to std.range 3 | */ 4 | module daffodil.util.range; 5 | 6 | public import std.range; 7 | 8 | import daffodil.colorspace; 9 | 10 | template isImageRange(R, E) { 11 | enum bool isImageRange = isImageRange!R && is(ElementType!R == E); 12 | } 13 | 14 | template isImageRange(R) { 15 | enum bool isImageRange = isInputRange!R && is(typeof( 16 | (inout int = 0) { 17 | R r = R.init; 18 | size_t w = r.width; 19 | size_t h = r.height; 20 | size_t c = r.channelCount; 21 | const ColorSpace* s = r.colorSpace; 22 | } 23 | )); 24 | } 25 | 26 | template isRandomAccessImageRange(R) { 27 | enum bool isRandomAccessImageRange = isImageRange!R && is(typeof( 28 | (inout int = 0) { 29 | R r = R.init; 30 | ElementType!R e = r[0, 0]; 31 | } 32 | )); 33 | } 34 | 35 | interface ImageRange(E) : InputRange!E { 36 | @property size_t width(); 37 | @property size_t height(); 38 | @property size_t channelCount(); 39 | @property const(ColorSpace*) colorSpace(); 40 | } 41 | 42 | unittest { 43 | static assert(isImageRange!(ImageRange!int)); 44 | } 45 | 46 | interface RandomAccessImageRange(E) : ImageRange!E { 47 | E opIndex(size_t x, size_t y); 48 | } 49 | 50 | unittest { 51 | static assert(isRandomAccessImageRange!(RandomAccessImageRange!int)); 52 | } 53 | 54 | template MostDerivedImageRange(R) if (isImageRange!R) { 55 | alias E = ElementType!R; 56 | 57 | static if (isRandomAccessImageRange!R) { 58 | alias MostDerivedImageRange = RandomAccessImageRange!E; 59 | } else { 60 | alias MostDerivedImageRange = ImageRange!E; 61 | } 62 | } 63 | 64 | class ImageRangeObject(R) : MostDerivedImageRange!R if (isImageRange!R) { 65 | private alias E = ElementType!R; 66 | 67 | private R _range; 68 | 69 | this(R range) { 70 | _range = range; 71 | } 72 | 73 | @property E front() { return _range.front; } 74 | void popFront() { _range.popFront(); } 75 | @property bool empty() { return _range.empty; } 76 | @property size_t width() { return _range.width; } 77 | @property size_t height() { return _range.height; } 78 | @property size_t channelCount() { return _range.channelCount; } 79 | @property const(ColorSpace*) colorSpace() { return _range.colorSpace; } 80 | 81 | E moveFront() { 82 | return .moveFront(_range); 83 | } 84 | 85 | // Optimization: One delegate call is faster than three virtual 86 | // function calls. Use opApply for foreach syntax. 87 | int opApply(scope int delegate(E) dg) { 88 | int res; 89 | 90 | foreach (i, e; this) { 91 | res = dg(e); 92 | if (res) break; 93 | } 94 | 95 | return res; 96 | } 97 | 98 | int opApply(scope int delegate(size_t, E) dg) { 99 | int res; 100 | 101 | size_t i = 0; 102 | for(; !empty; popFront()) { 103 | res = dg(i, front); 104 | if (res) break; 105 | i++; 106 | } 107 | 108 | return res; 109 | } 110 | 111 | static if (isRandomAccessImageRange!R) { 112 | 113 | E opIndex(size_t x, size_t y) { 114 | return _range[x, y]; 115 | } 116 | 117 | } 118 | } 119 | 120 | unittest { 121 | static assert(isImageRange!(ImageRangeObject!(ImageRange!int))); 122 | static assert(isRandomAccessImageRange!(ImageRangeObject!(RandomAccessImageRange!int))); 123 | } 124 | 125 | ImageRangeObject!R imageRangeObject(R)(R range) if (isImageRange!R) { 126 | static if (is(R : ImageRange!(ElementType!R))) { 127 | return range; 128 | } else { 129 | return new ImageRangeObject!R(range); 130 | } 131 | } 132 | 133 | class Iter(R) if (isRandomAccessRange!R) { 134 | private R range; 135 | 136 | this(R r) { 137 | range = r; 138 | } 139 | 140 | void popFront() { 141 | range = range[1..$]; 142 | } 143 | 144 | @property bool empty() { return range.length == 0; } 145 | @property ubyte front() { return range[0]; } 146 | } 147 | 148 | auto iter(R)(R range) if (isRandomAccessRange!R) { 149 | return new Iter!R(range); 150 | } 151 | -------------------------------------------------------------------------------- /test/bmp_suite/README.md: -------------------------------------------------------------------------------- 1 | # BMP Test Suite 2 | 3 | Images were generated from the 4 | [bmp test suite](http://entropymine.com/jason/bmpsuite/) and are in the public 5 | domain. 6 | -------------------------------------------------------------------------------- /test/bmp_suite/b/badbitcount.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/b/badbitcount.bmp -------------------------------------------------------------------------------- /test/bmp_suite/b/badbitssize.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/b/badbitssize.bmp -------------------------------------------------------------------------------- /test/bmp_suite/b/baddens1.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/b/baddens1.bmp -------------------------------------------------------------------------------- /test/bmp_suite/b/baddens2.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/b/baddens2.bmp -------------------------------------------------------------------------------- /test/bmp_suite/b/badfilesize.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/b/badfilesize.bmp -------------------------------------------------------------------------------- /test/bmp_suite/b/badheadersize.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/b/badheadersize.bmp -------------------------------------------------------------------------------- /test/bmp_suite/b/badpalettesize.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/b/badpalettesize.bmp -------------------------------------------------------------------------------- /test/bmp_suite/b/badplanes.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/b/badplanes.bmp -------------------------------------------------------------------------------- /test/bmp_suite/b/badrle.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/b/badrle.bmp -------------------------------------------------------------------------------- /test/bmp_suite/b/badrle4.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/b/badrle4.bmp -------------------------------------------------------------------------------- /test/bmp_suite/b/badrle4bis.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/b/badrle4bis.bmp -------------------------------------------------------------------------------- /test/bmp_suite/b/badrle4ter.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/b/badrle4ter.bmp -------------------------------------------------------------------------------- /test/bmp_suite/b/badrlebis.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/b/badrlebis.bmp -------------------------------------------------------------------------------- /test/bmp_suite/b/badrleter.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/b/badrleter.bmp -------------------------------------------------------------------------------- /test/bmp_suite/b/badwidth.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/b/badwidth.bmp -------------------------------------------------------------------------------- /test/bmp_suite/b/pal8badindex.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/b/pal8badindex.bmp -------------------------------------------------------------------------------- /test/bmp_suite/b/reallybig.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/b/reallybig.bmp -------------------------------------------------------------------------------- /test/bmp_suite/b/rgb16-880.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/b/rgb16-880.bmp -------------------------------------------------------------------------------- /test/bmp_suite/b/rletopdown.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/b/rletopdown.bmp -------------------------------------------------------------------------------- /test/bmp_suite/b/shortfile.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/b/shortfile.bmp -------------------------------------------------------------------------------- /test/bmp_suite/g/pal1.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/g/pal1.bmp -------------------------------------------------------------------------------- /test/bmp_suite/g/pal1bg.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/g/pal1bg.bmp -------------------------------------------------------------------------------- /test/bmp_suite/g/pal1wb.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/g/pal1wb.bmp -------------------------------------------------------------------------------- /test/bmp_suite/g/pal4.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/g/pal4.bmp -------------------------------------------------------------------------------- /test/bmp_suite/g/pal4gs.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/g/pal4gs.bmp -------------------------------------------------------------------------------- /test/bmp_suite/g/pal4rle.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/g/pal4rle.bmp -------------------------------------------------------------------------------- /test/bmp_suite/g/pal8-0.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/g/pal8-0.bmp -------------------------------------------------------------------------------- /test/bmp_suite/g/pal8.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/g/pal8.bmp -------------------------------------------------------------------------------- /test/bmp_suite/g/pal8gs.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/g/pal8gs.bmp -------------------------------------------------------------------------------- /test/bmp_suite/g/pal8nonsquare.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/g/pal8nonsquare.bmp -------------------------------------------------------------------------------- /test/bmp_suite/g/pal8os2.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/g/pal8os2.bmp -------------------------------------------------------------------------------- /test/bmp_suite/g/pal8rle.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/g/pal8rle.bmp -------------------------------------------------------------------------------- /test/bmp_suite/g/pal8topdown.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/g/pal8topdown.bmp -------------------------------------------------------------------------------- /test/bmp_suite/g/pal8v4.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/g/pal8v4.bmp -------------------------------------------------------------------------------- /test/bmp_suite/g/pal8v5.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/g/pal8v5.bmp -------------------------------------------------------------------------------- /test/bmp_suite/g/pal8w124.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/g/pal8w124.bmp -------------------------------------------------------------------------------- /test/bmp_suite/g/pal8w125.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/g/pal8w125.bmp -------------------------------------------------------------------------------- /test/bmp_suite/g/pal8w126.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/g/pal8w126.bmp -------------------------------------------------------------------------------- /test/bmp_suite/g/rgb16-565.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/g/rgb16-565.bmp -------------------------------------------------------------------------------- /test/bmp_suite/g/rgb16-565pal.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/g/rgb16-565pal.bmp -------------------------------------------------------------------------------- /test/bmp_suite/g/rgb16.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/g/rgb16.bmp -------------------------------------------------------------------------------- /test/bmp_suite/g/rgb24.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/g/rgb24.bmp -------------------------------------------------------------------------------- /test/bmp_suite/g/rgb24pal.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/g/rgb24pal.bmp -------------------------------------------------------------------------------- /test/bmp_suite/g/rgb32.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/g/rgb32.bmp -------------------------------------------------------------------------------- /test/bmp_suite/g/rgb32bf.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/g/rgb32bf.bmp -------------------------------------------------------------------------------- /test/bmp_suite/q/pal1p1.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/q/pal1p1.bmp -------------------------------------------------------------------------------- /test/bmp_suite/q/pal2.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/q/pal2.bmp -------------------------------------------------------------------------------- /test/bmp_suite/q/pal2color.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/q/pal2color.bmp -------------------------------------------------------------------------------- /test/bmp_suite/q/pal4rlecut.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/q/pal4rlecut.bmp -------------------------------------------------------------------------------- /test/bmp_suite/q/pal4rletrns.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/q/pal4rletrns.bmp -------------------------------------------------------------------------------- /test/bmp_suite/q/pal8offs.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/q/pal8offs.bmp -------------------------------------------------------------------------------- /test/bmp_suite/q/pal8os2-sz.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/q/pal8os2-sz.bmp -------------------------------------------------------------------------------- /test/bmp_suite/q/pal8os2sp.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/q/pal8os2sp.bmp -------------------------------------------------------------------------------- /test/bmp_suite/q/pal8os2v2-16.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/q/pal8os2v2-16.bmp -------------------------------------------------------------------------------- /test/bmp_suite/q/pal8os2v2-40sz.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/q/pal8os2v2-40sz.bmp -------------------------------------------------------------------------------- /test/bmp_suite/q/pal8os2v2-sz.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/q/pal8os2v2-sz.bmp -------------------------------------------------------------------------------- /test/bmp_suite/q/pal8os2v2.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/q/pal8os2v2.bmp -------------------------------------------------------------------------------- /test/bmp_suite/q/pal8oversizepal.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/q/pal8oversizepal.bmp -------------------------------------------------------------------------------- /test/bmp_suite/q/pal8rlecut.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/q/pal8rlecut.bmp -------------------------------------------------------------------------------- /test/bmp_suite/q/pal8rletrns.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/q/pal8rletrns.bmp -------------------------------------------------------------------------------- /test/bmp_suite/q/rgb16-231.bmp: -------------------------------------------------------------------------------- 1 | BMB@B(@@  0 2 | 3 |   ! ! !!11111(((((****,,<<>>>     4 | 5 |  !!!! 1 1 1 1 11(((*(:*<*<,>,>>'''' 6 |   ! !!1 11111(((****<,<<>>>  7 |  8 |  # #!# ! 3 1 3!11&&((*(**<*<,>,>>''''''''  9 | 10 | 11 |  ! ! !!1!111((((****,,<,>>>    12 | 13 |   #### # 3 3 3 31(((*(**<*<,>,>>''''''''  14 | #" # ##3 31313(((****<,<<>>>  15 |  16 |  #"### # 3 3 3!31&&)(*(**=*<,>,>>'''''''' 17 | 18 | """""#"#"##33333(((((****,,<<>>> 19 |  20 |   ####"3"3"3"3"33(((*(:*<*<,>,>>'''''''''''' 21 | """#"##3"33333(((****<,<<>>> 22 |  23 |   %"%#%"#"5"3"5#33'&((+(**<*<,?,>>'''''''' 24 | 25 | 26 |  ""#"""#"##3#333((((****,,<,>>> 27 |  28 |   %%%%"%"5"5"5"53(((*(**<*<,>,>>''&''''''''''''' 29 |   %$"%"##5"53533(((****<,<<>>> 30 |  31 |   %$%%%"%"5"5"5#53&&)(*(**=*<,>,>>)&')&')'')'' 32 |  33 |   $$$$$%$%$%%55555(((((****,,<<>>> 34 |  35 |   %%%%"5$5"5"5"55(((*(:*<*<,>,>>''''''''''''''''$$$%$%%5$55555(((****<,<<>>> 36 | $'%5$7%55'&(**=*<,?,>>)&)&)&)')')')')'$$%%5%555****,,<,>>>??????????????????''??7$7$75??(??(**<*=,>,>>))))))))))))))))???????????????????'$??$55755????****<,<<>>>&&&' "%')+-?????'??7$7%75??'&??(**=*<,?,>>)()&)))&)))')))'????&&&??'75777????****,,<<>>>"$&))+????'??7$7$75??)??(;*<*=,>,?>))))))))))))))))????&&??&77777????****<,<<>>>((((())) "%')+-????'??7&7'77??'&??(**=*<,?,>>)())))))))))))))????&&'??'7'777????****,,<,>>>(((( ""$$'&)))++-????))??9&9&9??)??)))))))))))))) "$&)+????)&??&777????((()()) ""$%&'&))++-?????????????????())??9&9????'&??????????????????()))))))))))) ""$$&&(((++?????????????????((&(??'9?????????????????????????((((((((( ""$$&&)))++-????))??9??????)?????)))))))))))) ""$$$&&((++????((????????????())))) ""$%$'&))++-????)????7????''??(+*=*=,?,?>??)))))))))))) """$$$&&&&(()**+??/??(()????99??????****,,<,>>>??(((((((((()( ""$$'&)))++-?? 37 | ??+????(99????)??(+*=*=,?,?>)??)))))))))))) """""$$$$&&&&()(((+*++??/??)(????9999??????+***=,<<?>>??)(()())() """$%$&'&&)()+(+-????+???(;);9???''????)))))))))))) """"$$$$&&&&(&((((+*+*?????((*??);9;9;???????(((((((()()( " ""$"$$&$&&)&)&)(+(+*-???????????????????+?(;(;(;9?)???????????????????)))))))))))) """"$$$$$&&&&(((((+*++??????????????????**+;(;;;9;??????????????????()(()()(()()(() " 2"$"%$$$7&&&)()(;(+*-++(;(;(;);9''))))))))))))) """"$$$$&$&&&&(()(**+***+*+*++;+;;;(((((((()((()( 2 ""$"$$7$&$)&)&9(+(+*-*=,/,// 38 | 39 | 40 | 41 | 42 | 43 |  44 |  45 | ++++*+*;*;*;*;;)))+(+*=*=,?,?>)))))))))))))))) """""$$$$&&&&()(((+*++-,--/// 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | +**+*++;*;;;;;))(+***=,=<?>>()((((()(()(()(()(()(()0 " 2"$"5$&$7&&&9()(;(+*=*-,?-// 54 | 55 |  56 |  57 |  58 | -*---*+*=*=*=+;;''))+(+*=*=,?,?>)))))))))))))))) """"$$$$&&&&(&((((+*+*----/// 59 | 60 | 61 | 62 | 63 | 64 | 65 |  66 | *****+*+*++;;;;;))()(+*+*-,=<?>?((((((((((()(()(()(())(0 2 2"4"4$6$6&9&9&9(;(;*=*=,?,?/ 67 | 68 |  69 |  70 |  71 | ----*=*=*=*=*=;)))+(;*=*=,?,?>)))))))))))))))) 0 ""2"$$4$$&6&&(8(((;*++=,--?// 72 | 73 | 74 | ,,,-*-+=*===;=)))+*++=,==?>?()(()(()(()(()(()(()(()0 2 2"4"5$4$7&6&9(9(;(;*=*=,?-?/ 75 | 76 |  77 |  78 |  79 | -,---*-*=*=*=+=;''))+(+*=*=,?,?>))+)))+)))+)))+) 0 2"""4$4$6$&&6&8(9(**;*=-=-//?  ,,-,,,-,--=-===)))(++++--=,???(((((((((()(((((()(()()())(0 2 2"4"4$7$6$9&9&9(;(;*=*=,?,?/ 80 | 81 |   82 | ----*-,=*=,=*==)))+(+*=*=,?,?>)+)))+)))+)))+)) 0 2"2"2$4$4&6&6(9(8(;*;+=,=-?/? -,,-,--=,=====)))+*++=-==?>?()(()(()(()(()(()(()(()0 2 2"4"5$6$7&6&9(9(;(;*=*=,?-?/    /,/-/,-,?,=,?-==''))+(+*=*=,?,?>+)+)+)+)+)+)+)+) 002"224$446&668&888(;:;*===-???  ,,,,,-,-,--=====)))))++++--==???((((((()((()((()((()()()()()()(0 2 2"4"4$6$6&9&9&9(;(;*=*=,?,?/     ////,?,?,?,?,?=)))+(;*=*=,?,?>++++++++++++++++00 2222444$4666688(88;:;;=,==???   .,,/,--?,==?==)))++++=-==???()((()((()((()((()((()((()((()(0 2 2"4"5$4$7&6&9(9(;(;*=*=,?-?/     /.///,/,?,?,?-?=''))+)+*=+=,?-?>++++++++++++++++000222244446466668898::;:====??? ../,.././/?-???))))++++--=-???((()((((((()((()()()()()()()()(0 202"424$746$969&98;(;:=*=<?,??     ////,/,?,?,?,?=)))+(++=+=-?,??++++++++++++++++000222224444666689888;:;;=<==???/.././/?.?????)))++++=-==???(+(((+(((+(((+(((+(((+(((+()(+(00202242546476669898;8;:=:=<?=??/.///./.?.?.?/??''))+)++=+=-?-??+(++(++(++(++(++(++(++(+ -------------------------------------------------------------------------------- /test/bmp_suite/q/rgb16-3103.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/q/rgb16-3103.bmp -------------------------------------------------------------------------------- /test/bmp_suite/q/rgb24jpeg.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/q/rgb24jpeg.bmp -------------------------------------------------------------------------------- /test/bmp_suite/q/rgb24largepal.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/q/rgb24largepal.bmp -------------------------------------------------------------------------------- /test/bmp_suite/q/rgb24lprof.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/q/rgb24lprof.bmp -------------------------------------------------------------------------------- /test/bmp_suite/q/rgb24png.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/q/rgb24png.bmp -------------------------------------------------------------------------------- /test/bmp_suite/q/rgb24prof.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/q/rgb24prof.bmp -------------------------------------------------------------------------------- /test/bmp_suite/q/rgb32-111110.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/q/rgb32-111110.bmp -------------------------------------------------------------------------------- /test/bmp_suite/q/rgb32-7187.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/q/rgb32-7187.bmp -------------------------------------------------------------------------------- /test/bmp_suite/q/rgb32fakealpha.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/q/rgb32fakealpha.bmp -------------------------------------------------------------------------------- /test/bmp_suite/q/rgb32h52.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/q/rgb32h52.bmp -------------------------------------------------------------------------------- /test/bmp_suite/q/rgba16-1924.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/q/rgba16-1924.bmp -------------------------------------------------------------------------------- /test/bmp_suite/q/rgba16-4444.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/q/rgba16-4444.bmp -------------------------------------------------------------------------------- /test/bmp_suite/q/rgba32-61754.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/q/rgba32-61754.bmp -------------------------------------------------------------------------------- /test/bmp_suite/q/rgba32-81284.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/q/rgba32-81284.bmp -------------------------------------------------------------------------------- /test/bmp_suite/q/rgba32.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/q/rgba32.bmp -------------------------------------------------------------------------------- /test/bmp_suite/q/rgba32abf.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/q/rgba32abf.bmp -------------------------------------------------------------------------------- /test/bmp_suite/q/rgba32h56.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/bmp_suite/q/rgba32h56.bmp -------------------------------------------------------------------------------- /test/bmp_suite/testbmp_suite.d: -------------------------------------------------------------------------------- 1 | module bmp_suite.testbmp_suite; 2 | 3 | import std.file; 4 | import std.format; 5 | import std.algorithm; 6 | 7 | import unit_threaded; 8 | import daffodil; 9 | 10 | static this() { 11 | mkdirRecurse("build/test/bmp_suite/g/"); 12 | } 13 | 14 | @ShouldFail 15 | @("pal1bg.bmp", "pal1wb.bmp", "pal4gs.bmp", "pal8-0.bmp", "pal8gs.bmp", 16 | "pal8os2.bmp", "pal8topdown.bmp", "pal8v5.bmp", "pal8w125.bmp", 17 | "rgb24pal.bmp", "pal1.bmp", "pal4.bmp", "pal4rle.bmp", "pal8.bmp", 18 | "pal8nonsquare.bmp", "pal8rle.bmp", "pal8v4.bmp", "pal8w124.bmp", 19 | "pal8w126.bmp", "rgb16-565pal.bmp") 20 | void testGoodBMPImagesFailLoading(string fileName) { 21 | testGoodBMPImagesLoading(fileName); 22 | } 23 | 24 | @("rgb24.bmp", "rgb32.bmp", "rgb32bf.bmp", "rgb16.bmp", "rgb16-565.bmp") 25 | void testGoodBMPImagesLoading(string fileName) { 26 | auto file = "test/bmp_suite/g/" ~ fileName; 27 | 28 | auto meta = loadMeta(file); 29 | assert(meta !is null); 30 | 31 | auto image = load(file); 32 | assert(image !is null); 33 | } 34 | 35 | @ShouldFail 36 | @("pal1bg.bmp", "pal1wb.bmp", "pal4gs.bmp", "pal8-0.bmp", "pal8gs.bmp", 37 | "pal8os2.bmp", "pal8topdown.bmp", "pal8v5.bmp", "pal8w125.bmp", 38 | "rgb24pal.bmp", "pal1.bmp", "pal4.bmp", "pal4rle.bmp", "pal8.bmp", 39 | "pal8nonsquare.bmp", "pal8rle.bmp", "pal8v4.bmp", "pal8w124.bmp", 40 | "pal8w126.bmp", "rgb16-565pal.bmp", "rgb32bf.bmp", "rgb16-565.bmp", 41 | "rgb16.bmp") 42 | void testGoodBMPImagesFailSaving(string fileName) { 43 | testGoodBMPImagesSaving(fileName); 44 | } 45 | 46 | @("rgb24.bmp", "rgb32.bmp") 47 | void testGoodBMPImagesSaving(string fileName) { 48 | auto origPath = "test/bmp_suite/g/" ~ fileName; 49 | auto copyPath = "build/" ~ origPath; 50 | 51 | auto image = load(origPath); 52 | image.save(copyPath); 53 | 54 | auto origData = cast(ubyte[])read(origPath); 55 | auto copyData = cast(ubyte[])read(copyPath); 56 | writelnUt(origData, copyData); 57 | assert(origData[0..copyData.length] == copyData); 58 | } 59 | 60 | @("badbitcount.bmp", "baddens1.bmp", "badfilesize.bmp", "badpalettesize.bmp", 61 | "badrle4bis.bmp", "badrle4ter.bmp", "badrle.bmp", "badwidth.bmp", 62 | "reallybig.bmp", "rletopdown.bmp", "badbitssize.bmp", "baddens2.bmp", 63 | "badheadersize.bmp", "badplanes.bmp", "badrle4.bmp", "badrlebis.bmp", 64 | "badrleter.bmp", "pal8badindex.bmp", "rgb16-880.bmp", "shortfile.bmp") 65 | void testBadBMPImages(string fileName) { 66 | shouldThrow!(ImageException)( 67 | load("test/bmp_suite/b/" ~ fileName) 68 | ); 69 | } 70 | 71 | @("pal1p1.bmp", "pal4rlecut.bmp", "pal8os2sp.bmp", "pal8os2v2-40sz.bmp", 72 | "pal8oversizepal.bmp", "rgb16-231.bmp", "rgb24largepal.bmp", "rgb24prof.bmp", 73 | "rgb32fakealpha.bmp", "rgba16-4444.bmp", "rgba32abf.bmp", "rgba32h56.bmp", 74 | "pal4rletrns.bmp", "pal8os2-sz.bmp", "pal8os2v2.bmp", "pal8rlecut.bmp", 75 | "rgb16-3103.bmp", "rgb24lprof.bmp", "rgb32-111110.bmp", "rgb32h52.bmp", 76 | "rgba32-61754.bmp", "rgba32.bmp", "pal2color.bmp", "pal8offs.bmp", "pal2.bmp", 77 | "pal8os2v2-16.bmp", "pal8os2v2-sz.bmp", "pal8rletrns.bmp", "rgb24jpeg.bmp", 78 | "rgb24png.bmp", "rgb32-7187.bmp", "rgba16-1924.bmp", "rgba32-81284.bmp") 79 | void testQuestionableBMPImages(string fileName) { 80 | try { 81 | auto image = load("test/bmp_suite/q/" ~ fileName); 82 | assert(image !is null); 83 | } catch (ImageException e) { 84 | assert(true); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /test/images/bmp_small-24bpp.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/images/bmp_small-24bpp.bmp -------------------------------------------------------------------------------- /test/images/empty.file: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenjaminSchaaf/daffodil/8a417d03135912f71e7a6781dc8275faf7e3bbf3/test/images/empty.file -------------------------------------------------------------------------------- /test/testall.d: -------------------------------------------------------------------------------- 1 | module testall; 2 | 3 | import daffodil; 4 | import unit_threaded; 5 | 6 | @("fails on empty file") 7 | unittest { 8 | shouldThrow!(ImageException)( 9 | load("test/images/empty.file") 10 | ); 11 | } 12 | 13 | @("detect and load a image file") 14 | unittest { 15 | auto image = load("test/images/bmp_small-24bpp.bmp"); 16 | assert(image.size == [41, 45]); 17 | } 18 | -------------------------------------------------------------------------------- /test/testbmp.d: -------------------------------------------------------------------------------- 1 | module testbmp; 2 | 3 | import daffodil; 4 | import bmp = daffodil.bmp; 5 | import unit_threaded; 6 | 7 | @("fails on empty file") 8 | unittest { 9 | shouldThrow!(ImageException)( 10 | bmp.load("test/images/empty.file") 11 | ); 12 | } 13 | 14 | @("load BMP meta data") 15 | unittest { 16 | auto meta = loadMeta("test/images/bmp_small-24bpp.bmp"); 17 | assert(meta !is null); 18 | assert(meta.width); 19 | assert(meta.height); 20 | assert(cast(bmp.BmpMetaData)meta); 21 | } 22 | 23 | @("load BMP image data") 24 | unittest { 25 | auto image = load!ubyte("test/images/bmp_small-24bpp.bmp"); 26 | assert(image !is null); 27 | assert(image.width == 41); 28 | assert(image.height == 45); 29 | assert(image[10, 10] == [ 0, 0, 255]); 30 | assert(image[30, 10] == [ 0, 255, 0]); 31 | assert(image[10, 30] == [255, 0, 0]); 32 | assert(image[30, 30] == [255, 255, 255]); 33 | } 34 | 35 | @("save same BMP image") 36 | unittest { 37 | import std.stdio; 38 | import daffodil.util.range : array, iter; 39 | import std.outbuffer; 40 | import std.algorithm; 41 | 42 | auto imageData = File("test/images/bmp_small-24bpp.bmp").byChunk(4096).joiner.array; 43 | auto image = load(imageData.iter); 44 | 45 | auto buffer = new OutBuffer(); 46 | image.save(buffer); 47 | 48 | assert(buffer.toBytes() == imageData); 49 | } 50 | --------------------------------------------------------------------------------