├── .circleci └── config.yml ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── README.tpl ├── benches └── draw.rs ├── doc └── releasing.md ├── examples └── display.rs ├── filter_readme.sed ├── justfile ├── release.toml ├── rustfmt.nightly.toml ├── src ├── color_map.rs ├── footer.rs ├── header.rs ├── lib.rs ├── parse_error.rs ├── pixels.rs ├── raw_iter.rs └── raw_tga.rs └── tests ├── cbw8.rs ├── cbw8.tga ├── chequerboard-rle-topleft.tga ├── chequerboard-uncompressed-topleft.rs ├── chequerboard-uncompressed-topleft.tga ├── chessboard_4px_raw.rs ├── chessboard_4px_raw.tga ├── chessboard_4px_rle.rs ├── chessboard_4px_rle.tga ├── chessboard_raw.tga ├── chessboard_rle.rs ├── chessboard_rle.tga ├── chessboard_uncompressed.png ├── chessboard_uncompressed.rs ├── chessboard_uncompressed.tga ├── coordinates.rs ├── embedded_graphics.rs ├── error_color_map.tga ├── error_no_image_data.tga ├── error_truncated_image_data.tga ├── errors.rs ├── image_id.rs ├── image_id.tga ├── issue_216.rs ├── issue_216_compressed.tga ├── issue_216_uncompressed.tga ├── logo.rs ├── logo_gray8.raw ├── logo_rgb555.raw ├── logo_rgb888.raw ├── logo_type10_16bpp_bl.tga ├── logo_type10_16bpp_tl.tga ├── logo_type10_24bpp_bl.tga ├── logo_type10_24bpp_tl.tga ├── logo_type11_bl.tga ├── logo_type11_tl.tga ├── logo_type1_16bpp_bl.tga ├── logo_type1_16bpp_tl.tga ├── logo_type1_24bpp_bl.tga ├── logo_type1_24bpp_tl.tga ├── logo_type2_16bpp_bl.tga ├── logo_type2_16bpp_tl.tga ├── logo_type2_24bpp_bl.tga ├── logo_type2_24bpp_br.tga ├── logo_type2_24bpp_tl.tga ├── logo_type2_24bpp_tr.tga ├── logo_type3_bl.tga ├── logo_type3_tl.tga ├── logo_type9_16bpp_bl.tga ├── logo_type9_16bpp_tl.tga ├── logo_type9_24bpp_bl.tga ├── logo_type9_24bpp_tl.tga ├── rust-rle-bw-bottomleft.tga ├── rust-rle-bw-topleft.tga ├── rust-uncompressed-bw-bottomleft.tga ├── rust-uncompressed-bw-topleft.tga ├── type10_16bpp_bl.tga ├── type10_16bpp_tl.tga ├── type10_24bpp_bl.tga ├── type10_24bpp_tl.tga ├── type11_bl.tga ├── type11_tl.tga ├── type1_16bpp_bl.tga ├── type1_16bpp_tl.tga ├── type1_24bpp_bl.tga ├── type1_24bpp_tl.tga ├── type2_16bpp_bl.tga ├── type2_16bpp_tl.tga ├── type2_24bpp_bl.tga ├── type2_24bpp_tl.tga ├── type3_bl.tga ├── type3_tl.tga ├── type9_16bpp_bl.tga ├── type9_16bpp_tl.tga ├── type9_24bpp_bl.tga ├── type9_24bpp_tl.tga ├── types.rs ├── ubw8.rs └── ubw8.tga /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Check that everything (tests, benches, etc) builds in std environments 2 | precheck_steps: &precheck_steps 3 | docker: &docker 4 | - image: jamwaffles/circleci-embedded-graphics:1.61.0-0 5 | auth: 6 | username: jamwaffles 7 | password: $DOCKERHUB_PASSWORD 8 | steps: 9 | - checkout 10 | - restore_cache: &restore_cache 11 | key: v1-{{ .Environment.CIRCLE_PROJECT_REPONAME }}-{{ .Environment.CIRCLE_JOB }}-{{ checksum "Cargo.toml" }} 12 | - run: rustup default ${RUST_VERSION:-stable} 13 | - run: rustup component add rustfmt 14 | - run: cargo update 15 | - run: just build 16 | - save_cache: &save_cache 17 | key: v1-{{ .Environment.CIRCLE_PROJECT_REPONAME }}-{{ .Environment.CIRCLE_JOB }}-{{ checksum "Cargo.toml" }} 18 | paths: 19 | - ./target 20 | - /home/circleci/.cargo/registry 21 | 22 | # Build crates for embedded target 23 | target_steps: &target_steps 24 | resource_class: large 25 | docker: *docker 26 | steps: 27 | - checkout 28 | - restore_cache: *restore_cache 29 | - run: just install-targets 30 | - run: cargo update 31 | - run: just build-targets --release 32 | - save_cache: *save_cache 33 | 34 | version: 2 35 | jobs: 36 | precheck-stable: 37 | <<: *precheck_steps 38 | precheck-beta: 39 | environment: 40 | - RUST_VERSION: "beta" 41 | <<: *precheck_steps 42 | 43 | all-targets: 44 | <<: *target_steps 45 | 46 | build_jobs: &build_jobs 47 | jobs: 48 | - precheck-stable 49 | - precheck-beta 50 | - all-targets 51 | 52 | workflows: 53 | version: 2 54 | build_all: 55 | <<: *build_jobs 56 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: jamwaffles 4 | patreon: 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | - Version of tinytga in use (if applicable): [version here] 2 | 3 | ## Description of the problem/feature request/other 4 | 5 | [description here] 6 | 7 | ## Test case (if applicable) 8 | 9 | ```rust 10 | // Failing test case demonstrating the issue here 11 | ``` 12 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Thank you for helping out with tinytga development! Please: 2 | 3 | - [ ] Check that you've added passing tests and documentation 4 | - [ ] Add a `CHANGELOG.md` entry in the **Unreleased** section under the appropriate heading (**Added**, **Fixed**, etc) if your changes affect the **public API** 5 | - [ ] Run `rustfmt` on the project 6 | - [ ] Run `just build` (Linux/macOS only) and make sure it passes. If you use Windows, check that CI passes once you've opened the PR. 7 | 8 | ## PR description 9 | 10 | [add your PR description here] 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | target/ 3 | **/*.rs.bk 4 | Cargo.lock 5 | .DS_Store 6 | .idea/ 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | [`tinytga`](https://crates.io/crates/tinytga) is a no_std, low memory footprint TGA loading library for embedded applications. 4 | 5 | 6 | 7 | ## [Unreleased] - ReleaseDate 8 | 9 | ## [0.5.0] - 2023-05-17 10 | 11 | ### Added 12 | 13 | - [#16](https://github.com/embedded-graphics/tinytga/pull/16) Added support for bottom right and top right image origins. 14 | 15 | ### Changed 16 | 17 | - **(breaking)** [#16](https://github.com/embedded-graphics/tinytga/pull/16) Use 1.61 as MSRV. 18 | - **(breaking)** [#16](https://github.com/embedded-graphics/tinytga/pull/16) Replaced `ImageType` enum with `DataType` and `Compression`. 19 | - **(breaking)** [#16](https://github.com/embedded-graphics/tinytga/pull/16) Color types used with `Tga` are now required to implement `From + From + From`. 20 | - [#16](https://github.com/embedded-graphics/tinytga/pull/16) Improved drawing performance for bottom left origin images by using `fill_contiguous`. 21 | - [#16](https://github.com/embedded-graphics/tinytga/pull/16) Use correct lifetimes for `RawTga::image_id`, `RawTga::developer_dictionary` and `RawTga::extension_area`. 22 | - **(breaking)** [#18](https://github.com/embedded-graphics/tinytga/pull/18) Updated `embedded-graphics` to `0.8`. 23 | 24 | ### Removed 25 | 26 | - **(breaking)** [#16](https://github.com/embedded-graphics/tinytga/pull/16) Removed `DynamicTga`, use `Tga` instead. 27 | 28 | ## [0.4.1] - 2021-06-16 29 | 30 | ### Changed 31 | 32 | - [#10](https://github.com/embedded-graphics/tinytga/pull/10) Bump embedded-graphics minimum version from 0.7.0 to 0.7.1 33 | 34 | ## [0.4.0] - 2021-06-06 35 | 36 | ## [0.4.0-beta.1] - 2021-05-24 37 | 38 | ## [0.4.0-alpha.1] - 2020-12-27 39 | 40 | ### Changed 41 | 42 | - **(breaking)** [#3](https://github.com/embedded-graphics/tinytga/pull/3) `tinytga` now depends on `embedded-graphics-core` instead of `embedded-graphics`. 43 | 44 | ## [0.4.0-alpha.1 - `embedded-graphics` repository] - 2020-12-27 45 | 46 | > Note: PR numbers from this point onwards are from the old `embedded-graphics/embedded-graphics` repository. New PR numbers above this note refer to PRs in the `embedded-graphics/tinytga` repository. 47 | 48 | ### Changed 49 | 50 | - **(breaking)** [#407](https://github.com/embedded-graphics/embedded-graphics/pull/407) The `image_descriptor` in `TgaHeader` was replaced by `image_origin` and `alpha_channel_bits`. 51 | - **(breaking)** [#420](https://github.com/embedded-graphics/embedded-graphics/pull/420) To support the new embedded-graphics 0.7 image API a color type parameter was added to `Tga`. 52 | - **(breaking)** [#430](https://github.com/embedded-graphics/embedded-graphics/pull/430) The `graphics` feature was removed and the `embedded-graphics` dependency is now non optional. 53 | - **(breaking)** [#430](https://github.com/embedded-graphics/embedded-graphics/pull/430) `Tga` no longer implements `IntoIterator`. Pixel iterators can now be created using the `pixels` and `raw_pixels` methods. 54 | - **(breaking)** [#430](https://github.com/embedded-graphics/embedded-graphics/pull/430) `Tga::from_slice` now checks that the specified color type matches the bit depth of the image. 55 | - **(breaking)** [#450](https://github.com/embedded-graphics/embedded-graphics/pull/450) The `TgaFooter` struct was replaced by the `developer_dictionary` and `extension_area` methods in `RawTga`. 56 | - **(breaking)** [#430](https://github.com/embedded-graphics/embedded-graphics/pull/430) `Tga::width` and `Tga::height` were replaced by `Tga::size` which requires `embedded_graphics::geometry::OriginDimensions` to be in scope (also included in the embedded-graphics `prelude`). 57 | - **(breaking)** [#430](https://github.com/embedded-graphics/embedded-graphics/pull/430) The color map can now be accessed using the new `ColorMap` type. 58 | - **(breaking)** [#450](https://github.com/embedded-graphics/embedded-graphics/pull/450) `Tga` no longer provides direct access to low level information like the TGA header, instead `Tga::as_raw` can be used to access the underlying `RawTga` instance. 59 | 60 | ### Added 61 | 62 | - [#407](https://github.com/embedded-graphics/embedded-graphics/pull/407) Added support for bottom-left origin images to `TgaIterator`. 63 | - [#430](https://github.com/embedded-graphics/embedded-graphics/pull/430) The image ID can now be accessed using `Tga::image_id`. 64 | - [#450](https://github.com/embedded-graphics/embedded-graphics/pull/450) Added `RawTga` to use `tinytga` without using a embedded-graphic color type. 65 | - [#450](https://github.com/embedded-graphics/embedded-graphics/pull/450) Added `Tga::from_raw` to convert a `RawTga` into a `Tga` object. 66 | - [#450](https://github.com/embedded-graphics/embedded-graphics/pull/450) Added `DynamicTga` to allow drawing of TGA images without a known color format at compile time. 67 | 68 | ### Fixed 69 | 70 | - [#407](https://github.com/embedded-graphics/embedded-graphics/pull/407) Additional data in `pixel_data`, beyond `width * height` pixels, is now discarded by `TgaIterator`. 71 | - [#430](https://github.com/embedded-graphics/embedded-graphics/pull/430) Images with unsupported BPP values in the header no longer cause panics. Instead an error is returned by `Tga::from_slice`. 72 | - [#430](https://github.com/embedded-graphics/embedded-graphics/pull/430) Errors during the execution of a pixel iterator no longer cause panics. Instead the corrupted portion of the image is filled with black pixels. 73 | 74 | ## [0.3.2] - 2020-03-20 75 | 76 | ## [0.3.1] - 2020-02-17 77 | 78 | - **(breaking)** [#247](https://github.com/embedded-graphics/embedded-graphics/pull/247) "reverse" integration of tinytga into [`embedded-graphics`](https://crates.io/crates/embedded-graphics). tinytga now has a `graphics` feature that must be turned on to enable embedded-graphics support. The `tga` feature from embedded-graphics is removed. 79 | 80 | **Before** 81 | 82 | `Cargo.toml` 83 | 84 | ```toml 85 | [dependencies] 86 | embedded-graphics = { version = "0.6.0-alpha.3", features = [ "tga" ]} 87 | ``` 88 | 89 | Your code 90 | 91 | ```rust 92 | use embedded_graphics::prelude::*; 93 | use embedded_graphics::image::ImageTga; 94 | 95 | let image = ImageTga::new(include_bytes!("../../../assets/patch.tga")).unwrap(); 96 | display.draw(&image); 97 | ``` 98 | 99 | **After** 100 | 101 | `Cargo.toml` 102 | 103 | ```toml 104 | [dependencies] 105 | embedded-graphics = "0.6.0" 106 | tinytga = { version = "*", features = [ "graphics" ]} 107 | ``` 108 | 109 | Your code 110 | 111 | ```rust 112 | use embedded_graphics::{prelude::*, image::Image}; 113 | use tinytga::Tga; 114 | 115 | let image = Tga::new(include_bytes!("../../../assets/patch.tga")).unwrap(); 116 | let image = Image::new(&image); 117 | display.draw(&image); 118 | ``` 119 | 120 | ## 0.2.0 121 | 122 | ### Added 123 | 124 | - [#217](https://github.com/embedded-graphics/embedded-graphics/pull/217) Added support for TGA files with color map. 125 | 126 | ### Fixed 127 | 128 | - [#217](https://github.com/embedded-graphics/embedded-graphics/pull/217) Images without a TGA footer are now parsed correctly. 129 | - [#216](https://github.com/embedded-graphics/embedded-graphics/pull/216) Fixed integer overflow for some RLE compressed TGA files. 130 | - [#218](https://github.com/embedded-graphics/embedded-graphics/pull/218) Test README examples in CI and update them to work with latest crate versions. 131 | 132 | 133 | [unreleased]: https://github.com/embedded-graphics/tinytga/compare/v0.5.0...HEAD 134 | [0.5.0]: https://github.com/embedded-graphics/tinytga/compare/v0.4.1...v0.5.0 135 | 136 | [0.4.1]: https://github.com/embedded-graphics/tinytga/compare/v0.4.0...v0.4.1 137 | [0.4.0]: https://github.com/embedded-graphics/tinytga/compare/v0.4.0-beta.1...v0.4.0 138 | [0.4.0-beta.1]: https://github.com/embedded-graphics/tinytga/compare/v0.4.0-alpha.1...v0.4.0-beta.1 139 | [0.4.0-alpha.1]: https://github.com/embedded-graphics/tinytga/compare/after-split...v0.4.0-alpha.1 140 | [0.4.0-alpha.1 - `embedded-graphics` repository]: https://github.com/embedded-graphics/embedded-graphics/compare/tinytga-v0.3.2...before-split 141 | [0.3.2]: https://github.com/embedded-graphics/embedded-graphics/compare/tinytga-v0.3.0...tinytga-v0.3.2 142 | [0.3.1]: https://github.com/embedded-graphics/embedded-graphics/compare/tinytga-v0.2.0...tinytga-v0.3.1 143 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tinytga" 3 | version = "0.5.0" 4 | description = "No-std, low memory footprint TGA image loader" 5 | authors = ["James Waples ", "Ralf Fuest "] 6 | edition = "2021" 7 | repository = "https://github.com/embedded-graphics/tinytga" 8 | documentation = "https://docs.rs/tinytga" 9 | categories = ["embedded", "no-std", "multimedia::images"] 10 | keywords = ["graphics", "embedded-graphics", "tga", "targa", "image"] 11 | readme = "./README.md" 12 | license = "MIT OR Apache-2.0" 13 | exclude = [ 14 | "/.github/", 15 | "/.circleci/", 16 | ".gitignore", 17 | ] 18 | 19 | [badges] 20 | circle-ci = { repository = "embedded-graphics/tinytga", branch = "master" } 21 | 22 | [[bench]] 23 | name = "draw" 24 | harness = false 25 | 26 | [dependencies] 27 | embedded-graphics = "0.8.0" 28 | nom = { version = "7.1.1", default-features = false } 29 | 30 | [dev-dependencies] 31 | paste = "1.0" 32 | criterion = "0.3.5" 33 | clap = { version = "3.2.22", features = ["derive"] } 34 | embedded-graphics-simulator = "0.5.0" 35 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 James Waples 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TinyTGA 2 | 3 | [![Build Status](https://circleci.com/gh/embedded-graphics/tinytga/tree/master.svg?style=shield)](https://circleci.com/gh/embedded-graphics/tinytga/tree/master) 4 | [![Crates.io](https://img.shields.io/crates/v/tinytga.svg)](https://crates.io/crates/tinytga) 5 | [![Docs.rs](https://docs.rs/tinytga/badge.svg)](https://docs.rs/tinytga) 6 | [![embedded-graphics on Matrix](https://img.shields.io/matrix/rust-embedded-graphics:matrix.org)](https://matrix.to/#/#rust-embedded-graphics:matrix.org) 7 | 8 | ## [Documentation](https://docs.rs/tinytga) 9 | 10 | A small TGA parser designed for use with [embedded-graphics] targeting no-std environments but 11 | usable anywhere. Beyond parsing the image header, no other allocations are made. 12 | 13 | tinytga provides two methods of accessing the pixel data inside a TGA file. The most convenient 14 | way is to use a color type provided by [embedded-graphics] to define the format stored inside 15 | the TGA file. But it is also possible to directly access the raw pixel representation instead. 16 | 17 | ## Examples 18 | 19 | ### Using `Tga` to draw an image 20 | 21 | This example demonstrates how a TGA image can be drawn to a [embedded-graphics] draw target. 22 | 23 | ```rust 24 | use embedded_graphics::{image::Image, pixelcolor::Rgb888, prelude::*}; 25 | use tinytga::Tga; 26 | 27 | // Include an image from a local path as bytes 28 | let data = include_bytes!("../tests/chessboard_4px_rle.tga"); 29 | 30 | let tga: Tga = Tga::from_slice(data).unwrap(); 31 | 32 | let image = Image::new(&tga, Point::zero()); 33 | 34 | image.draw(&mut display)?; 35 | ``` 36 | 37 | ### Accessing pixels using an embedded-graphics color type 38 | 39 | If [embedded-graphics] is not used to draw the TGA image, the color types provided by 40 | [embedded-graphics] can still be used to access the pixel data using the 41 | `pixels` method. 42 | 43 | ```rust 44 | use embedded_graphics::{prelude::*, pixelcolor::Rgb888}; 45 | use tinytga::Tga; 46 | 47 | // Include an image from a local path as bytes 48 | let data = include_bytes!("../tests/chessboard_4px_rle.tga"); 49 | 50 | // Create a TGA instance from a byte slice. 51 | // The color type is set by defining the type of the `img` variable. 52 | let img: Tga = Tga::from_slice(data).unwrap(); 53 | 54 | // Check the size of the image. 55 | assert_eq!(img.size(), Size::new(4, 4)); 56 | 57 | // Collect pixels into a vector. 58 | let pixels: Vec<_> = img.pixels().collect(); 59 | ``` 60 | 61 | ### Accessing raw pixel data 62 | 63 | If [embedded-graphics] is not used in the target application, the raw image data can be 64 | accessed with the `pixels` method on 65 | `RawTga`. The returned iterator produces a `u32` for each pixel value. 66 | 67 | ```rust 68 | use embedded_graphics::{prelude::*, pixelcolor::Rgb888}; 69 | use tinytga::{Bpp, Compression, DataType, ImageOrigin, RawPixel, RawTga, TgaHeader}; 70 | 71 | // Include an image from a local path as bytes. 72 | let data = include_bytes!("../tests/chessboard_4px_rle.tga"); 73 | 74 | // Create a TGA instance from a byte slice. 75 | let img = RawTga::from_slice(data).unwrap(); 76 | 77 | // Take a look at the raw image header. 78 | assert_eq!( 79 | img.header(), 80 | TgaHeader { 81 | id_len: 0, 82 | has_color_map: false, 83 | data_type: DataType::TrueColor, 84 | compression: Compression::Rle, 85 | color_map_start: 0, 86 | color_map_len: 0, 87 | color_map_depth: None, 88 | x_origin: 0, 89 | y_origin: 4, 90 | width: 4, 91 | height: 4, 92 | pixel_depth: Bpp::Bits24, 93 | image_origin: ImageOrigin::TopLeft, 94 | alpha_channel_depth: 0, 95 | } 96 | ); 97 | 98 | // Collect raw pixels into a vector. 99 | let pixels: Vec<_> = img.pixels().collect(); 100 | ``` 101 | 102 | ## Embedded-graphics drawing performance 103 | 104 | `tinytga` uses different code paths to draw images with different `ImageOrigin`s. 105 | The performance difference between the origins will depend on the display driver, but using 106 | images with the origin at the top left corner will generally result in the best performance. 107 | 108 | ## Minimum supported Rust version 109 | 110 | The minimum supported Rust version for tinytga is `1.61` or greater. 111 | Ensure you have the correct version of Rust installed, preferably through . 112 | 113 | [embedded-graphics]: https://docs.rs/embedded-graphics 114 | 115 | ## License 116 | 117 | Licensed under either of 118 | 119 | - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 120 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 121 | 122 | at your option. 123 | 124 | ### Contribution 125 | 126 | Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the 127 | work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any 128 | additional terms or conditions. 129 | -------------------------------------------------------------------------------- /README.tpl: -------------------------------------------------------------------------------- 1 | # TinyTGA 2 | 3 | [![Build Status](https://circleci.com/gh/embedded-graphics/tinytga/tree/master.svg?style=shield)](https://circleci.com/gh/embedded-graphics/tinytga/tree/master) 4 | [![Crates.io](https://img.shields.io/crates/v/tinytga.svg)](https://crates.io/crates/tinytga) 5 | [![Docs.rs](https://docs.rs/tinytga/badge.svg)](https://docs.rs/tinytga) 6 | [![embedded-graphics on Matrix](https://img.shields.io/matrix/rust-embedded-graphics:matrix.org)](https://matrix.to/#/#rust-embedded-graphics:matrix.org) 7 | 8 | ## [Documentation](https://docs.rs/tinytga) 9 | 10 | {{readme}} 11 | 12 | ## License 13 | 14 | Licensed under either of 15 | 16 | - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 17 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 18 | 19 | at your option. 20 | 21 | ### Contribution 22 | 23 | Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the 24 | work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any 25 | additional terms or conditions. 26 | -------------------------------------------------------------------------------- /benches/draw.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, Criterion}; 2 | use embedded_graphics::{ 3 | image::Image, 4 | pixelcolor::{Gray8, Rgb555, Rgb888}, 5 | prelude::*, 6 | }; 7 | use tinytga::Tga; 8 | 9 | // TODO: use e-g framebuffer when it's added 10 | struct Framebuffer { 11 | pixels: [[C; 240]; 320], 12 | } 13 | 14 | impl> Framebuffer { 15 | pub fn new() -> Self { 16 | let color = C::from(Rgb888::BLACK); 17 | 18 | Self { 19 | pixels: [[color; 240]; 320], 20 | } 21 | } 22 | } 23 | 24 | impl DrawTarget for Framebuffer { 25 | type Color = C; 26 | type Error = std::convert::Infallible; 27 | 28 | fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error> 29 | where 30 | I: IntoIterator>, 31 | { 32 | for Pixel(p, c) in pixels { 33 | self.pixels[p.y as usize][p.x as usize] = c; 34 | } 35 | 36 | Ok(()) 37 | } 38 | } 39 | 40 | impl OriginDimensions for Framebuffer { 41 | fn size(&self) -> embedded_graphics::prelude::Size { 42 | Size::new(240, 320) 43 | } 44 | } 45 | 46 | macro_rules! bench { 47 | ($c:expr, $color_type:ty, $file:expr) => { 48 | $c.bench_function(concat!(stringify!($color_type), " ", $file), |b| { 49 | let mut fb = Framebuffer::<$color_type>::new(); 50 | b.iter(|| { 51 | let bmp = Tga::<$color_type>::from_slice(include_bytes!(concat!( 52 | "../tests/", 53 | $file, 54 | ".tga" 55 | ))) 56 | .unwrap(); 57 | Image::new(&bmp, Point::zero()).draw(&mut fb).unwrap(); 58 | }) 59 | }); 60 | }; 61 | 62 | ($c:expr, $color_type:ty) => { 63 | bench!($c, $color_type, "logo_type1_16bpp_bl"); 64 | bench!($c, $color_type, "logo_type1_16bpp_tl"); 65 | bench!($c, $color_type, "logo_type1_24bpp_bl"); 66 | bench!($c, $color_type, "logo_type1_24bpp_tl"); 67 | bench!($c, $color_type, "logo_type2_16bpp_bl"); 68 | bench!($c, $color_type, "logo_type2_16bpp_tl"); 69 | bench!($c, $color_type, "logo_type2_24bpp_bl"); 70 | bench!($c, $color_type, "logo_type2_24bpp_tl"); 71 | bench!($c, $color_type, "logo_type3_bl"); 72 | bench!($c, $color_type, "logo_type3_tl"); 73 | bench!($c, $color_type, "logo_type9_16bpp_bl"); 74 | bench!($c, $color_type, "logo_type9_16bpp_tl"); 75 | bench!($c, $color_type, "logo_type9_24bpp_bl"); 76 | bench!($c, $color_type, "logo_type9_24bpp_tl"); 77 | bench!($c, $color_type, "logo_type10_16bpp_bl"); 78 | bench!($c, $color_type, "logo_type10_16bpp_tl"); 79 | bench!($c, $color_type, "logo_type10_24bpp_bl"); 80 | bench!($c, $color_type, "logo_type10_24bpp_tl"); 81 | bench!($c, $color_type, "logo_type11_bl"); 82 | bench!($c, $color_type, "logo_type11_tl"); 83 | }; 84 | } 85 | 86 | fn draw_benchmarks(c: &mut Criterion) { 87 | bench!(c, Rgb888); 88 | bench!(c, Rgb555); 89 | bench!(c, Gray8); 90 | } 91 | 92 | criterion_group!(benches, draw_benchmarks); 93 | criterion_main!(benches); 94 | -------------------------------------------------------------------------------- /doc/releasing.md: -------------------------------------------------------------------------------- 1 | # Release process 2 | 3 | Target audience: crate maintainers who wish to release `tinytga`. 4 | 5 | > Please take a cautious approach to this. If any step doesn't feel right or doesn't succeed smoothly, stop and rectify any issues before continuing. 6 | 7 | ## On GitHub 8 | 9 | - Check that all desired PRs are merged and all desired issues are closed/resolved. 10 | - Check that the latest master build passed in CircleCI. 11 | 12 | ## On your local machine 13 | 14 | - `cd` to the repository root 15 | - Check that `cargo-release` is installed and available in `$PATH`: 16 | 17 | ```bash 18 | cargo release --version 19 | ``` 20 | 21 | - Ensure you have the latest changes with `git switch master` and `git pull --rebase` 22 | - Check that your local repository is clean with no uncommitted changes and no unpushed commits. Ideally, use `git reset --hard origin/master` to ensure your local state is up to date with `origin/master`. You may need to change `origin` to the name of the remote pointing to . 23 | - Before a **stable** release: 24 | - Search the repository for any `TODO` or `FIXME` comments. If any need resolving before release, stop this process and fix them with one or more PRs. 25 | - Check that the crate version in `Cargo.toml` matches the latest released versions on . 26 | - Run `just build` to ensure the build passes locally. 27 | - If the build fails for any reason, stop the release process and fix any issues by creating PRs. The upstream master branch must remain the source of truth. Restart this checklist once `just build` passes. 28 | - Double check the release level (major, minor, patch) 29 | - Release the crate: 30 | 31 | ```bash 32 | cargo release --push-remote 33 | ``` 34 | 35 | Where `` is `major`, `minor`, `patch`, or a specific SemVer version number, and where `` is the git remote for the upstream repository `embedded-graphics/tinytga`. 36 | 37 | ## Post release 38 | 39 | - Check that the release command pushed a Git tag when the crate was published, something like `v0.4.0-beta.1` or `v0.3.1`. 40 | - For the new tag, go to its page at e.g. , click Edit tag and draft a release: 41 | 42 | - Copy and paste the tag into the `Release title` field. 43 | - Copy and paste the latest released section out of the crate's `CHANGELOG.md` file into the `Describe this release` field. Do not include the version header, e.g.: 44 | 45 | ```markdown 46 | ### Added 47 | 48 | - [#111](https://github.com/embedded-graphics/tinytga/pull/111) Added something 49 | 50 | ### Removed 51 | 52 | - [#222](https://github.com/embedded-graphics/tinytga/pull/222) Removed a thing 53 | ``` 54 | 55 | - For `alpha` or `beta` releases, check the `This is a pre-release` checkbox. 56 | - Hit Publish release 57 | 58 | - Check that the release is displayed on the [repository homepage](https://github.com/embedded-graphics/tinytga). 59 | - Post a link to the released tag (e.g. ) to the embedded-graphics Matrix room at 60 | - If you are @jamwaffles, post a Tweet tagging @rustembedded with a happy announcement message. 61 | 62 | - Check the other repositories in the [embedded-graphics organization](https://github.com/embedded-graphics) for dependencies on `tinytga`. The version should be updated to the latest releases made whilst following this guide. 63 | -------------------------------------------------------------------------------- /examples/display.rs: -------------------------------------------------------------------------------- 1 | //! This example displays TGA images using the embedded-graphics simulator. 2 | //! 3 | //! Basic usage: `cargo run --example display -- TGA_FILE` 4 | //! 5 | //! More usage and arguments can be listed by running `cargo run --example display -- --help` 6 | 7 | use clap::{ArgEnum, Parser}; 8 | use embedded_graphics::{ 9 | image::Image, 10 | pixelcolor::{BinaryColor, Gray8, Rgb555, Rgb565, Rgb888}, 11 | prelude::*, 12 | }; 13 | use embedded_graphics_simulator::{ 14 | OutputSettings, OutputSettingsBuilder, SimulatorDisplay, Window, 15 | }; 16 | use std::{fs, num::NonZeroU32, path::PathBuf}; 17 | use tinytga::Tga; 18 | 19 | #[derive(Debug, Clone, Copy, ArgEnum)] 20 | #[clap(rename_all = "PascalCase")] 21 | enum ColorType { 22 | Rgb555, 23 | Rgb565, 24 | Rgb888, 25 | Gray8, 26 | BinaryColor, 27 | } 28 | 29 | #[derive(Parser)] 30 | struct Args { 31 | /// Pixel scale 32 | #[clap(long, default_value = "1")] 33 | scale: NonZeroU32, 34 | 35 | /// Display color type 36 | #[clap(arg_enum, long, default_value = "Rgb888")] 37 | color_type: ColorType, 38 | 39 | /// BMP file 40 | bmp_file: PathBuf, 41 | } 42 | 43 | fn display_tga(data: &[u8], settings: &OutputSettings) 44 | where 45 | C: PixelColor + From + From + From + Into, 46 | { 47 | let bmp = Tga::::from_slice(&data).unwrap(); 48 | 49 | let mut display = SimulatorDisplay::::new(bmp.size()); 50 | 51 | Image::new(&bmp, Point::zero()) 52 | .draw(&mut display.color_converted()) 53 | .unwrap(); 54 | 55 | let mut window = Window::new("TGA viewer", &settings); 56 | window.show_static(&display); 57 | } 58 | 59 | fn main() { 60 | let args = Args::parse(); 61 | 62 | let settings = OutputSettingsBuilder::new() 63 | .scale(args.scale.into()) 64 | .build(); 65 | 66 | let data = fs::read(&args.bmp_file).unwrap(); 67 | 68 | match args.color_type { 69 | ColorType::Rgb555 => display_tga::(&data, &settings), 70 | ColorType::Rgb565 => display_tga::(&data, &settings), 71 | ColorType::Rgb888 => display_tga::(&data, &settings), 72 | ColorType::Gray8 => display_tga::(&data, &settings), 73 | ColorType::BinaryColor => display_tga::(&data, &settings), 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /filter_readme.sed: -------------------------------------------------------------------------------- 1 | # Taken from https://github.com/porglezomp/pixel-canvas/blob/develop/gen-readme.sh 2 | 3 | # Remove footer-reference-style doc links like "[`Foo`]: ./foo/trait.Foo.html" 4 | /\[.+\]: .*(struct|enum|trait|type|fn|index)\./d 5 | 6 | # Remove inline-style doc links like "[`Foo`](./foo/trait.Foo.html)", 7 | # leaving just "`Foo`" in its place 8 | s/\[(.+)\]\(.*(struct|enum|trait|type|fn|index).*\)/\1/g 9 | 10 | # Remove square braces from footer-reference-style inline links like "[`Foo`]", 11 | # leaving "`Foo`" in its place 12 | s/\[(`[^]]*`)\]([^\(:]|$)/\1\2/g 13 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | targets := "arm-unknown-linux-gnueabi armv7-unknown-linux-gnueabihf x86_64-unknown-linux-gnu x86_64-unknown-linux-musl thumbv6m-none-eabi thumbv7em-none-eabi thumbv7em-none-eabihf thumbv7m-none-eabi" 2 | target_dir := "target" 3 | 4 | #---------- 5 | # Building 6 | #---------- 7 | 8 | build: check-formatting test build-benches check-readme check-links 9 | 10 | # Build the benches 11 | build-benches: 12 | cargo bench --no-run 13 | 14 | # Run cargo test 15 | test: 16 | cargo test 17 | 18 | # Check the formatting 19 | check-formatting: 20 | cargo fmt --all -- --check 21 | 22 | # Cross compiles tinytga for a target 23 | build-target target *args: 24 | cargo build --target {{target}} {{args}} 25 | 26 | # Cross compiles tinytga for all targets 27 | build-targets *args: 28 | #!/usr/bin/env bash 29 | set -e 30 | for target in {{targets}}; do just build-target $target {{args}}; done 31 | 32 | # Install all targets used in the `build-targets` command 33 | install-targets: 34 | #!/usr/bin/env bash 35 | set -e 36 | 37 | sysroot=$(rustc --print sysroot) 38 | 39 | for target in {{targets}}; do 40 | if [[ ! "$sysroot" =~ "$target" ]]; then 41 | rustup target add $target 42 | else 43 | echo "Target $target is already installed" 44 | fi 45 | done 46 | 47 | #------ 48 | # Docs 49 | #------ 50 | 51 | # Generates the docs 52 | generate-docs: 53 | cargo clean --doc 54 | cargo doc --all-features 55 | 56 | # Runs cargo-deadlinks on the docs 57 | check-links: generate-docs 58 | cargo deadlinks 59 | 60 | #---------------------- 61 | # README.md generation 62 | # --------------------- 63 | 64 | # Generate README.md for a single crate 65 | generate-readme: (_build-readme) 66 | cp {{target_dir}}/README.md README.md 67 | 68 | # Check README.md for a single crate 69 | @check-readme: (_build-readme) 70 | diff -q {{target_dir}}/README.md README.md || ( \ 71 | echo -e "\033[1;31mError:\033[0m README.md needs to be regenerated."; \ 72 | echo -e " Run 'just generate-readme' to regenerate.\n"; \ 73 | exit 1 \ 74 | ) 75 | 76 | # Builds README.md for a single crate 77 | _build-readme: 78 | #!/usr/bin/env bash 79 | set -e -o pipefail 80 | mkdir -p {{target_dir}}/readme 81 | echo "Building README.md" 82 | cargo readme | sed -E -f filter_readme.sed > {{target_dir}}/README.md 83 | -------------------------------------------------------------------------------- /release.toml: -------------------------------------------------------------------------------- 1 | pre-release-replacements = [ 2 | {file="CHANGELOG.md", prerelease=true, search="[Uu]nreleased", replace="{{version}}"}, 3 | {file="CHANGELOG.md", prerelease=true, search="\\.\\.\\.HEAD", replace="...{{tag_name}}"}, 4 | {file="CHANGELOG.md", prerelease=true, search="ReleaseDate", replace="{{date}}"}, 5 | {file="CHANGELOG.md", prerelease=true, search="", replace="\n\n## [Unreleased] - ReleaseDate"}, 6 | {file="CHANGELOG.md", prerelease=true, search="", replace="\n[unreleased]: https://github.com/embedded-graphics/{{crate_name}}/compare/{{tag_name}}...HEAD"}, 7 | ] 8 | tag-message = "Release {{crate_name}} {{version}}" 9 | -------------------------------------------------------------------------------- /rustfmt.nightly.toml: -------------------------------------------------------------------------------- 1 | # Run with `cargo +nightly fmt -- --config-path ./rustfmt.nightly.toml` 2 | # This config only works in nightly. It can be used to clean up doc comments without having to do it 3 | # manually. 4 | 5 | format_code_in_doc_comments = true 6 | merge_imports = true 7 | format_macro_matchers = true 8 | format_macro_bodies = true 9 | -------------------------------------------------------------------------------- /src/color_map.rs: -------------------------------------------------------------------------------- 1 | use crate::{parse_error::ParseError, Bpp, TgaHeader}; 2 | use embedded_graphics::{ 3 | iterator::raw::RawDataSlice, pixelcolor::raw::LittleEndian, prelude::PixelColor, 4 | }; 5 | use nom::bytes::complete::take; 6 | 7 | /// Color map. 8 | #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] 9 | pub struct ColorMap<'a> { 10 | /// First color index. 11 | start_index: u16, 12 | /// Number of entries. 13 | length: u16, 14 | /// Entry bit depth. 15 | entry_bpp: Bpp, 16 | /// Color map data. 17 | data: &'a [u8], 18 | } 19 | 20 | impl<'a> ColorMap<'a> { 21 | pub(crate) fn parse( 22 | input: &'a [u8], 23 | header: &TgaHeader, 24 | ) -> Result<(&'a [u8], Option), ParseError> { 25 | if !header.has_color_map { 26 | return Ok((input, None)); 27 | } 28 | 29 | let entry_bpp = header.color_map_depth.ok_or(ParseError::ColorMap)?; 30 | 31 | let length = usize::from(header.color_map_len) * usize::from(entry_bpp.bytes()); 32 | 33 | let (input, color_map_data) = 34 | take(length)(input).map_err(|_: nom::Err<()>| ParseError::ColorMap)?; 35 | 36 | Ok(( 37 | input, 38 | Some(Self { 39 | start_index: header.color_map_start, 40 | length: header.color_map_len, 41 | entry_bpp, 42 | data: color_map_data, 43 | }), 44 | )) 45 | } 46 | 47 | /// Returns the bit depth for the entries in the color map. 48 | pub fn entry_bpp(&self) -> Bpp { 49 | self.entry_bpp 50 | } 51 | 52 | /// Returns the raw color value for a color map entry. 53 | pub fn get_raw(&self, index: usize) -> Option { 54 | //TODO: use start_index and add test 55 | if index >= usize::from(self.length) { 56 | return None; 57 | } 58 | 59 | let start = index * usize::from(self.entry_bpp.bytes()); 60 | 61 | Some(match self.entry_bpp { 62 | Bpp::Bits8 => self.data[start] as u32, 63 | Bpp::Bits16 => u32::from_le_bytes([self.data[start], self.data[start + 1], 0, 0]), 64 | Bpp::Bits24 => u32::from_le_bytes([ 65 | self.data[start], 66 | self.data[start + 1], 67 | self.data[start + 2], 68 | 0, 69 | ]), 70 | Bpp::Bits32 => u32::from_le_bytes([ 71 | self.data[start], 72 | self.data[start + 1], 73 | self.data[start + 2], 74 | self.data[start + 3], 75 | ]), 76 | }) 77 | } 78 | 79 | pub(crate) fn get(&self, index: usize) -> Option 80 | where 81 | C: PixelColor + From, 82 | RawDataSlice<'a, C::Raw, LittleEndian>: IntoIterator, 83 | { 84 | RawDataSlice::new(self.data) 85 | .into_iter() 86 | .nth(index) 87 | .map(|r| C::from(r)) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/footer.rs: -------------------------------------------------------------------------------- 1 | use core::num::NonZeroUsize; 2 | use nom::{bytes::complete::tag, combinator::map, number::complete::le_u32, IResult, Needed}; 3 | 4 | /// TGA footer length in bytes 5 | const TGA_FOOTER_LENGTH: usize = 26; 6 | 7 | /// TGA footer structure, referenced from 8 | #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] 9 | pub(crate) struct TgaFooter { 10 | /// Footer start offset 11 | footer_start: usize, 12 | 13 | /// Extension area offset 14 | extension_area_offset: Option, 15 | 16 | /// Developer directory 17 | developer_directory_offset: Option, 18 | } 19 | 20 | impl TgaFooter { 21 | /// Parses the TGA footer. 22 | /// 23 | /// Returns `None` if the file doesn't contain a valid footer. 24 | pub fn parse(image_data: &[u8]) -> Option { 25 | parse_footer(image_data).ok().map(|(_, footer)| footer) 26 | } 27 | 28 | /// Returns the length of the footer section of the TGA file. 29 | /// 30 | /// The length includes the footer, extension area and developer directory. 31 | pub fn length(&self, image_data: &[u8]) -> usize { 32 | let mut length = TGA_FOOTER_LENGTH; 33 | 34 | if let Some(offset) = self.extension_area_offset { 35 | length = length.max(image_data.len() - offset.get()); 36 | } 37 | 38 | if let Some(offset) = self.developer_directory_offset { 39 | length = length.max(image_data.len() - offset.get()); 40 | } 41 | 42 | length 43 | } 44 | 45 | /// Returns the extension area. 46 | /// 47 | /// Returns `None` if the file doesn't contain an extension area. 48 | pub fn extension_area<'a>(&self, image_data: &'a [u8]) -> Option<&'a [u8]> { 49 | self.extension_area_offset 50 | .map(NonZeroUsize::get) 51 | .and_then(|start| { 52 | let end = self 53 | .developer_directory_offset 54 | .map(NonZeroUsize::get) 55 | .filter(|offset| *offset > start) 56 | .unwrap_or(self.footer_start); 57 | 58 | image_data.get(start..end) 59 | }) 60 | } 61 | 62 | /// Returns the developer directory. 63 | /// 64 | /// Returns `None` if the file doesn't contain a developer directory. 65 | pub fn developer_directory<'a>(&self, image_data: &'a [u8]) -> Option<&'a [u8]> { 66 | self.developer_directory_offset 67 | .map(NonZeroUsize::get) 68 | .and_then(|start| { 69 | let end = self 70 | .extension_area_offset 71 | .map(NonZeroUsize::get) 72 | .filter(|offset| *offset > start) 73 | .unwrap_or(self.footer_start); 74 | 75 | image_data.get(start..end) 76 | }) 77 | } 78 | } 79 | 80 | fn offset(input: &[u8]) -> IResult<&[u8], Option> { 81 | map(le_u32, |offset| NonZeroUsize::new(offset as usize))(input) 82 | } 83 | 84 | fn parse_footer(input: &[u8]) -> IResult<&[u8], TgaFooter> { 85 | let footer_start = input 86 | .len() 87 | .checked_sub(TGA_FOOTER_LENGTH) 88 | .ok_or(nom::Err::Incomplete(Needed::Size( 89 | NonZeroUsize::new(TGA_FOOTER_LENGTH).unwrap(), 90 | )))?; 91 | let input = &input[footer_start..input.len()]; 92 | 93 | let (input, extension_area_offset) = offset(input)?; 94 | let (input, developer_directory_offset) = offset(input)?; 95 | let (input, _) = tag("TRUEVISION-XFILE.\0")(input)?; 96 | 97 | Ok(( 98 | input, 99 | TgaFooter { 100 | footer_start, 101 | extension_area_offset, 102 | developer_directory_offset, 103 | }, 104 | )) 105 | } 106 | -------------------------------------------------------------------------------- /src/header.rs: -------------------------------------------------------------------------------- 1 | use nom::{ 2 | combinator::{map, map_opt, map_res}, 3 | number::complete::{le_u16, le_u8}, 4 | IResult, 5 | }; 6 | 7 | use crate::parse_error::ParseError; 8 | 9 | /// Bits per pixel. 10 | #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] 11 | #[non_exhaustive] 12 | pub enum Bpp { 13 | /// 8 bits per pixel. 14 | Bits8, 15 | /// 16 bits per pixel. 16 | Bits16, 17 | /// 24 bits per pixel. 18 | Bits24, 19 | /// 32 bits per pixel. 20 | Bits32, 21 | } 22 | 23 | impl Bpp { 24 | fn new(value: u8) -> Option { 25 | Some(match value { 26 | 8 => Self::Bits8, 27 | 16 => Self::Bits16, 28 | 24 => Self::Bits24, 29 | 32 => Self::Bits32, 30 | _ => return None, 31 | }) 32 | } 33 | 34 | fn parse(input: &[u8]) -> IResult<&[u8], Self> { 35 | map_opt(le_u8, Bpp::new)(input) 36 | } 37 | 38 | fn parse_opt(input: &[u8]) -> IResult<&[u8], Option> { 39 | map(le_u8, Bpp::new)(input) 40 | } 41 | 42 | /// Returns the number of bits. 43 | pub fn bits(self) -> u8 { 44 | match self { 45 | Self::Bits8 => 8, 46 | Self::Bits16 => 16, 47 | Self::Bits24 => 24, 48 | Self::Bits32 => 32, 49 | } 50 | } 51 | 52 | /// Returns the number of bytes needed to store values with this bit depth. 53 | pub fn bytes(self) -> u8 { 54 | match self { 55 | Self::Bits8 => 1, 56 | Self::Bits16 => 2, 57 | Self::Bits24 => 3, 58 | Self::Bits32 => 4, 59 | } 60 | } 61 | } 62 | 63 | /// Image data compression. 64 | #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] 65 | pub enum Compression { 66 | /// Uncompressed image data. 67 | Uncompressed, 68 | /// Run-length encoded image data. 69 | Rle, 70 | } 71 | 72 | /// Image data type. 73 | #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] 74 | pub enum DataType { 75 | /// No image data. 76 | NoData, 77 | /// Color mapped. 78 | ColorMapped, 79 | /// True color. 80 | TrueColor, 81 | /// Black and white or grayscale. 82 | BlackAndWhite, 83 | } 84 | 85 | fn parse_image_type(image_type: u8) -> Result<(DataType, Compression), ParseError> { 86 | if image_type & !0b1011 != 0 || image_type == 8 { 87 | return Err(ParseError::UnsupportedImageType(image_type)); 88 | } 89 | 90 | let data_type = match image_type & 0x3 { 91 | 1 => DataType::ColorMapped, 92 | 2 => DataType::TrueColor, 93 | 3 => DataType::BlackAndWhite, 94 | _ => DataType::NoData, 95 | }; 96 | 97 | let compression = if image_type & 0x8 != 0 { 98 | Compression::Rle 99 | } else { 100 | Compression::Uncompressed 101 | }; 102 | 103 | Ok((data_type, compression)) 104 | } 105 | 106 | /// Image origin 107 | #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] 108 | pub enum ImageOrigin { 109 | /// Bottom left 110 | BottomLeft, 111 | /// Bottom right 112 | BottomRight, 113 | /// Top left 114 | TopLeft, 115 | /// Top right 116 | TopRight, 117 | } 118 | 119 | impl ImageOrigin { 120 | fn from_image_descriptor(value: u8) -> Self { 121 | match (value & 0x30) >> 4 { 122 | 0 => Self::BottomLeft, 123 | 1 => Self::BottomRight, 124 | 2 => Self::TopLeft, 125 | _ => Self::TopRight, 126 | } 127 | } 128 | 129 | pub(crate) fn is_bottom(self) -> bool { 130 | matches!(self, Self::BottomLeft | Self::BottomRight) 131 | } 132 | } 133 | 134 | /// TGA header. 135 | /// 136 | /// See for a detailed description of the fields. 137 | #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] 138 | pub struct TgaHeader { 139 | /// Image ID field length 140 | pub id_len: u8, 141 | 142 | /// Whether a color map is included in the image data 143 | pub has_color_map: bool, 144 | 145 | /// Data type. 146 | pub data_type: DataType, 147 | 148 | /// Compression. 149 | pub compression: Compression, 150 | 151 | /// Color map origin 152 | pub color_map_start: u16, 153 | 154 | /// Length of color map 155 | pub color_map_len: u16, 156 | 157 | /// Number of bits in each color palette entry 158 | pub color_map_depth: Option, 159 | 160 | /// Image origin (X) 161 | pub x_origin: u16, 162 | 163 | /// Image origin (Y) 164 | pub y_origin: u16, 165 | 166 | /// Image width in pixels 167 | pub width: u16, 168 | 169 | /// Image height in pixels 170 | pub height: u16, 171 | 172 | /// Pixel bit depth 173 | pub pixel_depth: Bpp, 174 | 175 | /// Image origin 176 | pub image_origin: ImageOrigin, 177 | 178 | /// Alpha channel depth 179 | pub alpha_channel_depth: u8, 180 | } 181 | 182 | impl TgaHeader { 183 | pub(crate) fn parse(input: &[u8]) -> IResult<&[u8], Self> { 184 | let (input, id_len) = le_u8(input)?; 185 | let (input, has_color_map) = has_color_map(input)?; 186 | let (input, (data_type, compression)) = map_res(le_u8, parse_image_type)(input)?; 187 | let (input, color_map_start) = le_u16(input)?; 188 | let (input, color_map_len) = le_u16(input)?; 189 | let (input, color_map_depth) = Bpp::parse_opt(input)?; 190 | let (input, x_origin) = le_u16(input)?; 191 | let (input, y_origin) = le_u16(input)?; 192 | let (input, width) = le_u16(input)?; 193 | let (input, height) = le_u16(input)?; 194 | let (input, pixel_depth) = Bpp::parse(input)?; 195 | 196 | let (input, image_descriptor) = le_u8(input)?; 197 | let image_origin = ImageOrigin::from_image_descriptor(image_descriptor); 198 | let alpha_channel_depth = image_descriptor & 0xF; 199 | 200 | Ok(( 201 | input, 202 | TgaHeader { 203 | id_len, 204 | has_color_map, 205 | data_type, 206 | compression, 207 | color_map_start, 208 | color_map_len, 209 | color_map_depth, 210 | x_origin, 211 | y_origin, 212 | width, 213 | height, 214 | pixel_depth, 215 | image_origin, 216 | alpha_channel_depth, 217 | }, 218 | )) 219 | } 220 | } 221 | 222 | fn has_color_map(input: &[u8]) -> IResult<&[u8], bool> { 223 | map_res(le_u8, |b| match b { 224 | 0 => Ok(false), 225 | 1 => Ok(true), 226 | _ => Err(ParseError::ColorMap), 227 | })(input) 228 | } 229 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A small TGA parser designed for use with [embedded-graphics] targeting no-std environments but 2 | //! usable anywhere. Beyond parsing the image header, no other allocations are made. 3 | //! 4 | //! tinytga provides two methods of accessing the pixel data inside a TGA file. The most convenient 5 | //! way is to use a color type provided by [embedded-graphics] to define the format stored inside 6 | //! the TGA file. But it is also possible to directly access the raw pixel representation instead. 7 | //! 8 | //! # Examples 9 | //! 10 | //! ## Using `Tga` to draw an image 11 | //! 12 | //! This example demonstrates how a TGA image can be drawn to a [embedded-graphics] draw target. 13 | //! 14 | //! ```rust 15 | //! # fn main() -> Result<(), core::convert::Infallible> { 16 | //! # let mut display = embedded_graphics::mock_display::MockDisplay::default(); 17 | //! use embedded_graphics::{image::Image, pixelcolor::Rgb888, prelude::*}; 18 | //! use tinytga::Tga; 19 | //! 20 | //! // Include an image from a local path as bytes 21 | //! let data = include_bytes!("../tests/chessboard_4px_rle.tga"); 22 | //! 23 | //! let tga: Tga = Tga::from_slice(data).unwrap(); 24 | //! 25 | //! let image = Image::new(&tga, Point::zero()); 26 | //! 27 | //! image.draw(&mut display)?; 28 | //! # Ok::<(), core::convert::Infallible>(()) } 29 | //! ``` 30 | //! 31 | //! ## Accessing pixels using an embedded-graphics color type 32 | //! 33 | //! If [embedded-graphics] is not used to draw the TGA image, the color types provided by 34 | //! [embedded-graphics] can still be used to access the pixel data using the 35 | //! [`pixels`](struct.Tga.html#method.pixels) method. 36 | //! 37 | //! ```rust 38 | //! use embedded_graphics::{prelude::*, pixelcolor::Rgb888}; 39 | //! use tinytga::Tga; 40 | //! 41 | //! // Include an image from a local path as bytes 42 | //! let data = include_bytes!("../tests/chessboard_4px_rle.tga"); 43 | //! 44 | //! // Create a TGA instance from a byte slice. 45 | //! // The color type is set by defining the type of the `img` variable. 46 | //! let img: Tga = Tga::from_slice(data).unwrap(); 47 | //! 48 | //! // Check the size of the image. 49 | //! assert_eq!(img.size(), Size::new(4, 4)); 50 | //! 51 | //! // Collect pixels into a vector. 52 | //! let pixels: Vec<_> = img.pixels().collect(); 53 | //! ``` 54 | //! 55 | //! ## Accessing raw pixel data 56 | //! 57 | //! If [embedded-graphics] is not used in the target application, the raw image data can be 58 | //! accessed with the [`pixels`](struct.RawTga.html#method.pixels) method on 59 | //! [`RawTga`]. The returned iterator produces a `u32` for each pixel value. 60 | //! 61 | //! ```rust 62 | //! use embedded_graphics::{prelude::*, pixelcolor::Rgb888}; 63 | //! use tinytga::{Bpp, Compression, DataType, ImageOrigin, RawPixel, RawTga, TgaHeader}; 64 | //! 65 | //! // Include an image from a local path as bytes. 66 | //! let data = include_bytes!("../tests/chessboard_4px_rle.tga"); 67 | //! 68 | //! // Create a TGA instance from a byte slice. 69 | //! let img = RawTga::from_slice(data).unwrap(); 70 | //! 71 | //! // Take a look at the raw image header. 72 | //! assert_eq!( 73 | //! img.header(), 74 | //! TgaHeader { 75 | //! id_len: 0, 76 | //! has_color_map: false, 77 | //! data_type: DataType::TrueColor, 78 | //! compression: Compression::Rle, 79 | //! color_map_start: 0, 80 | //! color_map_len: 0, 81 | //! color_map_depth: None, 82 | //! x_origin: 0, 83 | //! y_origin: 4, 84 | //! width: 4, 85 | //! height: 4, 86 | //! pixel_depth: Bpp::Bits24, 87 | //! image_origin: ImageOrigin::TopLeft, 88 | //! alpha_channel_depth: 0, 89 | //! } 90 | //! ); 91 | //! 92 | //! // Collect raw pixels into a vector. 93 | //! let pixels: Vec<_> = img.pixels().collect(); 94 | //! ``` 95 | //! 96 | //! # Embedded-graphics drawing performance 97 | //! 98 | //! `tinytga` uses different code paths to draw images with different [`ImageOrigin`]s. 99 | //! The performance difference between the origins will depend on the display driver, but using 100 | //! images with the origin at the top left corner will generally result in the best performance. 101 | //! 102 | //! # Minimum supported Rust version 103 | //! 104 | //! The minimum supported Rust version for tinytga is `1.61` or greater. 105 | //! Ensure you have the correct version of Rust installed, preferably through . 106 | //! 107 | //! [`ImageOrigin`]: enum.ImageOrigin.html 108 | //! [embedded-graphics]: https://docs.rs/embedded-graphics 109 | //! [`Tga`]: ./struct.Tga.html 110 | //! [`RawTga`]: ./struct.RawTga.html 111 | 112 | #![no_std] 113 | #![deny(missing_docs)] 114 | #![deny(missing_debug_implementations)] 115 | #![deny(missing_copy_implementations)] 116 | #![deny(trivial_casts)] 117 | #![deny(trivial_numeric_casts)] 118 | #![deny(unsafe_code)] 119 | #![deny(unstable_features)] 120 | #![deny(unused_import_braces)] 121 | #![deny(unused_qualifications)] 122 | 123 | mod color_map; 124 | mod footer; 125 | mod header; 126 | mod parse_error; 127 | mod pixels; 128 | mod raw_iter; 129 | mod raw_tga; 130 | 131 | use core::marker::PhantomData; 132 | use embedded_graphics::{ 133 | pixelcolor::{ 134 | raw::{RawU16, RawU24, RawU8}, 135 | Gray8, Rgb555, Rgb888, 136 | }, 137 | prelude::*, 138 | primitives::Rectangle, 139 | }; 140 | use raw_iter::{RawColors, Rle, Uncompressed}; 141 | 142 | pub use crate::{ 143 | color_map::ColorMap, 144 | header::{Bpp, Compression, DataType, ImageOrigin, TgaHeader}, 145 | parse_error::ParseError, 146 | pixels::Pixels, 147 | raw_iter::{RawPixel, RawPixels}, 148 | raw_tga::RawTga, 149 | }; 150 | 151 | /// TGA image. 152 | #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] 153 | pub struct Tga<'a, C> { 154 | /// Raw TGA file. 155 | raw: RawTga<'a>, 156 | 157 | image_color_type: ColorType, 158 | 159 | /// Color type. 160 | target_color_type: PhantomData, 161 | } 162 | 163 | impl<'a, C> Tga<'a, C> 164 | where 165 | C: PixelColor + From + From + From, 166 | { 167 | /// Parses a TGA image from a byte slice. 168 | pub fn from_slice(data: &'a [u8]) -> Result { 169 | let raw = RawTga::from_slice(data)?; 170 | 171 | let image_color_type = match (raw.color_bpp(), raw.data_type()) { 172 | (Bpp::Bits8, DataType::BlackAndWhite) => ColorType::Gray8, 173 | (Bpp::Bits16, DataType::ColorMapped) => ColorType::Rgb555, 174 | (Bpp::Bits16, DataType::TrueColor) => ColorType::Rgb555, 175 | (Bpp::Bits24, DataType::ColorMapped) => ColorType::Rgb888, 176 | (Bpp::Bits24, DataType::TrueColor) => ColorType::Rgb888, 177 | _ => { 178 | return Err(ParseError::UnsupportedTgaType( 179 | raw.data_type(), 180 | raw.color_bpp(), 181 | )); 182 | } 183 | }; 184 | 185 | Ok(Tga { 186 | raw, 187 | image_color_type, 188 | target_color_type: PhantomData, 189 | }) 190 | } 191 | 192 | /// Returns a reference to the raw TGA image. 193 | /// 194 | /// The [`RawTga`] object can be used to access lower level details about the TGA file. 195 | /// 196 | /// [`RawTga`]: struct.RawTga.html 197 | pub fn as_raw(&self) -> &RawTga<'a> { 198 | &self.raw 199 | } 200 | 201 | /// Returns an iterator over the pixels in this image. 202 | pub fn pixels(&self) -> Pixels<'_, C> { 203 | Pixels::new(self) 204 | } 205 | 206 | fn draw_colors( 207 | &self, 208 | target: &mut D, 209 | mut colors: impl Iterator, 210 | ) -> Result<(), D::Error> 211 | where 212 | D: DrawTarget, 213 | { 214 | let bounding_box = self.bounding_box(); 215 | if bounding_box.is_zero_sized() { 216 | return Ok(()); 217 | } 218 | 219 | let origin = self.raw.image_origin(); 220 | 221 | // TGA files with the origin in the top left corner can be drawn using `fill_contiguous`. 222 | // All other origins are drawn by falling back to `draw_iter`. 223 | match origin { 224 | ImageOrigin::TopLeft => target.fill_contiguous(&bounding_box, colors), 225 | ImageOrigin::BottomLeft => { 226 | let mut row_rect = 227 | Rectangle::new(Point::zero(), Size::new(bounding_box.size.width, 1)); 228 | 229 | for y in bounding_box.rows().rev() { 230 | row_rect.top_left.y = y; 231 | let row_colors = (&mut colors).take(bounding_box.size.width as usize); 232 | target.fill_contiguous(&row_rect, row_colors)?; 233 | } 234 | 235 | Ok(()) 236 | } 237 | ImageOrigin::TopRight => { 238 | let max_x = bounding_box.bottom_right().map(|p| p.x).unwrap_or_default(); 239 | 240 | bounding_box 241 | .points() 242 | .zip(colors) 243 | .map(|(p, c)| Pixel(Point::new(max_x - p.x, p.y), c)) 244 | .draw(target) 245 | } 246 | ImageOrigin::BottomRight => { 247 | let bottom_right = bounding_box.bottom_right().unwrap_or_default(); 248 | 249 | bounding_box 250 | .points() 251 | .zip(colors) 252 | .map(|(p, c)| Pixel(bottom_right - p, c)) 253 | .draw(target) 254 | } 255 | } 256 | } 257 | 258 | fn draw_regular( 259 | &self, 260 | target: &mut D, 261 | colors: RawColors<'a, CI::Raw, F>, 262 | ) -> Result<(), D::Error> 263 | where 264 | D: DrawTarget, 265 | CI: PixelColor + From + Into, 266 | RawColors<'a, CI::Raw, F>: Iterator, 267 | { 268 | self.draw_colors(target, colors.map(|c| CI::from(c).into())) 269 | } 270 | 271 | fn draw_color_mapped( 272 | &self, 273 | target: &mut D, 274 | indices: RawColors<'a, R, F>, 275 | ) -> Result<(), D::Error> 276 | where 277 | D: DrawTarget, 278 | R: RawData, 279 | R::Storage: Into, 280 | RawColors<'a, R, F>: Iterator, 281 | { 282 | let color_map = if let Some(color_map) = self.raw.color_map() { 283 | color_map 284 | } else { 285 | return Ok(()); 286 | }; 287 | 288 | match self.image_color_type { 289 | ColorType::Rgb555 => { 290 | let colors = indices.map(|index| { 291 | let index = index.into_inner().into() as usize; 292 | color_map.get::(index).unwrap().into() 293 | }); 294 | 295 | self.draw_colors(target, colors) 296 | } 297 | ColorType::Rgb888 => { 298 | let colors = indices.map(|index| { 299 | let index = index.into_inner().into() as usize; 300 | color_map.get::(index).unwrap().into() 301 | }); 302 | 303 | self.draw_colors(target, colors) 304 | } 305 | // Color mapped Gray8 images aren't supported. Using a color map for Gray8 images 306 | // doesn't make sense, because this encoding will always be larger than a type 3 image. 307 | ColorType::Gray8 => Ok(()), 308 | } 309 | } 310 | } 311 | 312 | impl OriginDimensions for Tga<'_, C> { 313 | fn size(&self) -> Size { 314 | self.raw.size() 315 | } 316 | } 317 | 318 | impl ImageDrawable for Tga<'_, C> 319 | where 320 | C: PixelColor + From + From + From, 321 | { 322 | type Color = C; 323 | 324 | fn draw(&self, target: &mut D) -> Result<(), D::Error> 325 | where 326 | D: DrawTarget, 327 | { 328 | match self.raw.image_data_bpp() { 329 | Bpp::Bits8 => match self.raw.compression() { 330 | Compression::Uncompressed => { 331 | let colors = RawColors::::new(&self.raw); 332 | 333 | if self.raw.color_map().is_some() { 334 | self.draw_color_mapped(target, colors) 335 | } else { 336 | self.draw_regular::<_, Gray8, _>(target, colors) 337 | } 338 | } 339 | Compression::Rle => { 340 | let colors = RawColors::::new(&self.raw); 341 | 342 | if self.raw.color_map().is_some() { 343 | self.draw_color_mapped(target, colors) 344 | } else { 345 | self.draw_regular::<_, Gray8, _>(target, colors) 346 | } 347 | } 348 | }, 349 | Bpp::Bits16 => match self.raw.compression() { 350 | Compression::Uncompressed => { 351 | let colors = RawColors::::new(&self.raw); 352 | 353 | if self.raw.color_map().is_some() { 354 | self.draw_color_mapped(target, colors) 355 | } else { 356 | self.draw_regular::<_, Rgb555, _>(target, colors) 357 | } 358 | } 359 | Compression::Rle => { 360 | let colors = RawColors::::new(&self.raw); 361 | 362 | if self.raw.color_map().is_some() { 363 | self.draw_color_mapped(target, colors) 364 | } else { 365 | self.draw_regular::<_, Rgb555, _>(target, colors) 366 | } 367 | } 368 | }, 369 | Bpp::Bits24 => match self.raw.compression() { 370 | Compression::Uncompressed => { 371 | let colors = RawColors::::new(&self.raw); 372 | 373 | if self.raw.color_map().is_some() { 374 | self.draw_color_mapped(target, colors) 375 | } else { 376 | self.draw_regular::<_, Rgb888, _>(target, colors) 377 | } 378 | } 379 | Compression::Rle => { 380 | let colors = RawColors::::new(&self.raw); 381 | 382 | if self.raw.color_map().is_some() { 383 | self.draw_color_mapped(target, colors) 384 | } else { 385 | self.draw_regular::<_, Rgb888, _>(target, colors) 386 | } 387 | } 388 | }, 389 | Bpp::Bits32 => Ok(()), 390 | } 391 | } 392 | 393 | fn draw_sub_image(&self, target: &mut D, area: &Rectangle) -> Result<(), D::Error> 394 | where 395 | D: DrawTarget, 396 | { 397 | self.draw(&mut target.translated(-area.top_left).clipped(area)) 398 | } 399 | } 400 | 401 | #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] 402 | pub(crate) enum ColorType { 403 | Gray8, 404 | Rgb555, 405 | Rgb888, 406 | } 407 | -------------------------------------------------------------------------------- /src/parse_error.rs: -------------------------------------------------------------------------------- 1 | use crate::header::{Bpp, DataType}; 2 | 3 | /// Possible parse errors 4 | #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] 5 | #[non_exhaustive] 6 | pub enum ParseError { 7 | /// An error occurred when parsing the color map. 8 | ColorMap, 9 | 10 | /// An error occurred when parsing the TGA header. 11 | Header, 12 | 13 | /// An error occurred when parsing the TGA footer. 14 | Footer, 15 | 16 | /// An unsupported image type value was encountered. 17 | UnsupportedImageType(u8), 18 | 19 | /// An unsupported bits per pixel value was encountered. 20 | UnsupportedBpp(u8), 21 | 22 | /// Mismatched bits per pixel. 23 | /// 24 | /// The bit depth of the image doesn't match the depth that was specified 25 | /// when `Tga::from_slice` was called. 26 | /// 27 | /// [`Tga::from_slice`]: struct.Tga.html#method.from_slice 28 | MismatchedBpp(u8), 29 | 30 | /// Unsupported combination of image type and bits per pixel. 31 | UnsupportedTgaType(DataType, Bpp), 32 | } 33 | -------------------------------------------------------------------------------- /src/pixels.rs: -------------------------------------------------------------------------------- 1 | use embedded_graphics::{ 2 | pixelcolor::{ 3 | raw::{RawU16, RawU24, RawU8}, 4 | Gray8, Rgb555, Rgb888, 5 | }, 6 | prelude::*, 7 | }; 8 | 9 | use crate::{ColorType, RawPixel, RawPixels, Tga}; 10 | 11 | /// Iterator over individual TGA pixels. 12 | /// 13 | /// See the [`pixels`] method for additional information. 14 | /// 15 | /// [`pixels`]: struct.Tga.html#method.pixels 16 | #[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] 17 | pub struct Pixels<'a, C> { 18 | tga: &'a Tga<'a, C>, 19 | raw_pixels: RawPixels<'a>, 20 | } 21 | 22 | impl<'a, C> Pixels<'a, C> 23 | where 24 | C: PixelColor + From + From + From, 25 | { 26 | pub(crate) fn new(tga: &'a Tga<'a, C>) -> Self { 27 | Self { 28 | tga, 29 | raw_pixels: RawPixels::new(&tga.raw), 30 | } 31 | } 32 | } 33 | 34 | impl Iterator for Pixels<'_, C> 35 | where 36 | C: PixelColor + From + From + From, 37 | { 38 | type Item = Pixel; 39 | 40 | fn next(&mut self) -> Option { 41 | let RawPixel { 42 | position, 43 | mut color, 44 | } = self.raw_pixels.next()?; 45 | 46 | if let Some(color_map) = self.tga.raw.color_map() { 47 | color = color_map.get_raw(color as usize).unwrap() 48 | } 49 | 50 | let color = match self.tga.image_color_type { 51 | ColorType::Gray8 => Gray8::from(RawU8::from_u32(color)).into(), 52 | ColorType::Rgb555 => Rgb555::from(RawU16::from_u32(color)).into(), 53 | ColorType::Rgb888 => Rgb888::from(RawU24::from_u32(color)).into(), 54 | }; 55 | 56 | Some(Pixel(position, color)) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/raw_iter.rs: -------------------------------------------------------------------------------- 1 | use core::{convert::TryInto, marker::PhantomData}; 2 | 3 | use embedded_graphics::{ 4 | pixelcolor::raw::{RawU16, RawU24, RawU32, RawU8}, 5 | prelude::*, 6 | }; 7 | 8 | use crate::{raw_tga::RawTga, Bpp, Compression}; 9 | 10 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 11 | pub enum Uncompressed {} 12 | 13 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 14 | pub enum Rle {} 15 | 16 | #[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] 17 | pub struct RawColors<'a, R, F> { 18 | remaining_data: &'a [u8], 19 | 20 | rle_pixel: u32, 21 | rle_repeat: u8, 22 | rle_take_raw: u8, 23 | 24 | raw_data_type: PhantomData, 25 | format: PhantomData, 26 | } 27 | 28 | impl<'a, R: RawData, F> RawColors<'a, R, F> { 29 | pub fn new(raw_tga: &'a RawTga<'a>) -> Self { 30 | debug_assert_eq!( 31 | usize::from(raw_tga.image_data_bpp().bits()), 32 | R::BITS_PER_PIXEL 33 | ); 34 | 35 | let image_data = raw_tga.image_data(); 36 | 37 | Self { 38 | remaining_data: image_data, 39 | rle_pixel: 0, 40 | rle_repeat: 0, 41 | rle_take_raw: 0, 42 | raw_data_type: PhantomData, 43 | format: PhantomData, 44 | } 45 | } 46 | } 47 | 48 | trait NextColor { 49 | fn next_color(&mut self) -> Option; 50 | } 51 | 52 | impl<'a, F> NextColor for RawColors<'a, RawU8, F> { 53 | fn next_color(&mut self) -> Option { 54 | self.remaining_data.split_first().map(|(r, rest)| { 55 | self.remaining_data = rest; 56 | RawU8::new(*r) 57 | }) 58 | } 59 | } 60 | 61 | impl<'a, F> NextColor for RawColors<'a, RawU16, F> { 62 | fn next_color(&mut self) -> Option { 63 | if self.remaining_data.len() < 2 { 64 | return None; 65 | } 66 | 67 | let (bytes, rest) = self.remaining_data.split_at(2); 68 | self.remaining_data = rest; 69 | 70 | Some(RawU16::new(u16::from_le_bytes(bytes.try_into().unwrap()))) 71 | } 72 | } 73 | 74 | impl<'a, F> NextColor for RawColors<'a, RawU24, F> { 75 | fn next_color(&mut self) -> Option { 76 | if self.remaining_data.len() < 3 { 77 | return None; 78 | } 79 | 80 | let (bytes, rest) = self.remaining_data.split_at(3); 81 | self.remaining_data = rest; 82 | 83 | let mut bytes_padded = [0u8; 4]; 84 | bytes_padded[0..3].copy_from_slice(bytes); 85 | 86 | Some(RawU24::new(u32::from_le_bytes(bytes_padded))) 87 | } 88 | } 89 | 90 | impl<'a, F> NextColor for RawColors<'a, RawU32, F> { 91 | fn next_color(&mut self) -> Option { 92 | if self.remaining_data.len() < 4 { 93 | return None; 94 | } 95 | 96 | let (bytes, rest) = self.remaining_data.split_at(4); 97 | self.remaining_data = rest; 98 | 99 | Some(RawU32::new(u32::from_le_bytes(bytes.try_into().unwrap()))) 100 | } 101 | } 102 | 103 | impl<'a, R> Iterator for RawColors<'a, R, Uncompressed> 104 | where 105 | Self: NextColor, 106 | R: RawData, 107 | { 108 | type Item = R; 109 | 110 | fn next(&mut self) -> Option { 111 | self.next_color().or_else(|| Some(R::from_u32(0))) 112 | } 113 | } 114 | 115 | impl<'a, R> Iterator for RawColors<'a, R, Rle> 116 | where 117 | Self: NextColor, 118 | R: RawData, 119 | R::Storage: Into, 120 | { 121 | type Item = R; 122 | 123 | fn next(&mut self) -> Option { 124 | loop { 125 | if self.rle_repeat > 0 { 126 | self.rle_repeat -= 1; 127 | break Some(R::from_u32(self.rle_pixel)); 128 | } else if self.rle_take_raw > 0 { 129 | self.rle_take_raw -= 1; 130 | break self.next_color(); 131 | } else { 132 | let (type_and_count, rest) = self.remaining_data.split_first()?; 133 | self.remaining_data = rest; 134 | 135 | // The pixel count is encoded in the lower 7 bits and the actual number of pixels 136 | // is one more than the value stored in the packet. 137 | let pixel_count = (*type_and_count & 0x7F) + 1; 138 | 139 | // The packet type is encoded in the upper bit: 0 -> Raw, 1 -> Rle 140 | if *type_and_count & 0x80 != 0 { 141 | let pixel = self.next_color()?; 142 | 143 | self.rle_repeat = pixel_count; 144 | self.rle_pixel = pixel.into_inner().into(); 145 | } else { 146 | self.rle_take_raw = pixel_count; 147 | } 148 | } 149 | } 150 | } 151 | } 152 | 153 | #[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] 154 | enum DynamicRawColors<'a> { 155 | Bpp8Uncompressed(RawColors<'a, RawU8, Uncompressed>), 156 | Bpp8Rle(RawColors<'a, RawU8, Rle>), 157 | Bpp16Uncompressed(RawColors<'a, RawU16, Uncompressed>), 158 | Bpp16Rle(RawColors<'a, RawU16, Rle>), 159 | Bpp24Uncompressed(RawColors<'a, RawU24, Uncompressed>), 160 | Bpp24Rle(RawColors<'a, RawU24, Rle>), 161 | Bpp32Uncompressed(RawColors<'a, RawU32, Uncompressed>), 162 | Bpp32Rle(RawColors<'a, RawU32, Rle>), 163 | } 164 | 165 | /// Iterator over individual TGA pixels. 166 | /// 167 | /// See the [`pixels`] method for additional information. 168 | /// 169 | /// [`pixels`]: struct.RawTga.html#method.pixels 170 | #[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] 171 | pub struct RawPixels<'a> { 172 | raw_tga: &'a RawTga<'a>, 173 | colors: DynamicRawColors<'a>, 174 | position: Point, 175 | } 176 | 177 | impl<'a> RawPixels<'a> { 178 | pub(crate) fn new(raw_tga: &'a RawTga<'a>) -> Self { 179 | let colors = match (raw_tga.image_data_bpp(), raw_tga.compression()) { 180 | (Bpp::Bits8, Compression::Uncompressed) => { 181 | DynamicRawColors::Bpp8Uncompressed(RawColors::new(raw_tga)) 182 | } 183 | (Bpp::Bits8, Compression::Rle) => DynamicRawColors::Bpp8Rle(RawColors::new(raw_tga)), 184 | (Bpp::Bits16, Compression::Uncompressed) => { 185 | DynamicRawColors::Bpp16Uncompressed(RawColors::new(raw_tga)) 186 | } 187 | (Bpp::Bits16, Compression::Rle) => DynamicRawColors::Bpp16Rle(RawColors::new(raw_tga)), 188 | (Bpp::Bits24, Compression::Uncompressed) => { 189 | DynamicRawColors::Bpp24Uncompressed(RawColors::new(raw_tga)) 190 | } 191 | (Bpp::Bits24, Compression::Rle) => DynamicRawColors::Bpp24Rle(RawColors::new(raw_tga)), 192 | (Bpp::Bits32, Compression::Uncompressed) => { 193 | DynamicRawColors::Bpp32Uncompressed(RawColors::new(raw_tga)) 194 | } 195 | (Bpp::Bits32, Compression::Rle) => DynamicRawColors::Bpp32Rle(RawColors::new(raw_tga)), 196 | }; 197 | 198 | let start_y = if raw_tga.image_origin().is_bottom() { 199 | raw_tga.size().height.saturating_sub(1) 200 | } else { 201 | 0 202 | }; 203 | 204 | Self { 205 | raw_tga, 206 | colors, 207 | position: Point::new(0, start_y as i32), 208 | } 209 | } 210 | 211 | /// Returns the next pixel position. 212 | fn next_position(&mut self) -> Option { 213 | if self.position.y < 0 || self.position.y >= self.raw_tga.size().height as i32 { 214 | return None; 215 | } 216 | 217 | let position = self.position; 218 | 219 | self.position.x += 1; 220 | 221 | if self.position.x >= self.raw_tga.size().width as i32 { 222 | self.position.x = 0; 223 | 224 | if self.raw_tga.image_origin().is_bottom() { 225 | self.position.y -= 1; 226 | } else { 227 | self.position.y += 1; 228 | } 229 | } 230 | 231 | Some(position) 232 | } 233 | } 234 | 235 | impl Iterator for RawPixels<'_> { 236 | type Item = RawPixel; 237 | 238 | fn next(&mut self) -> Option { 239 | let position = self.next_position()?; 240 | 241 | let color = match &mut self.colors { 242 | DynamicRawColors::Bpp8Uncompressed(colors) => u32::from(colors.next()?.into_inner()), 243 | DynamicRawColors::Bpp8Rle(colors) => u32::from(colors.next()?.into_inner()), 244 | DynamicRawColors::Bpp16Uncompressed(colors) => u32::from(colors.next()?.into_inner()), 245 | DynamicRawColors::Bpp16Rle(colors) => u32::from(colors.next()?.into_inner()), 246 | DynamicRawColors::Bpp24Uncompressed(colors) => colors.next()?.into_inner(), 247 | DynamicRawColors::Bpp24Rle(colors) => colors.next()?.into_inner(), 248 | DynamicRawColors::Bpp32Uncompressed(colors) => colors.next()?.into_inner(), 249 | DynamicRawColors::Bpp32Rle(colors) => colors.next()?.into_inner(), 250 | }; 251 | 252 | Some(RawPixel::new(position, color)) 253 | } 254 | } 255 | 256 | /// Pixel with raw pixel color. 257 | /// 258 | /// This struct is returned by the [`RawPixels`] iterator. 259 | /// 260 | /// [`RawPixels`]: struct.RawPixels.html 261 | #[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Default)] 262 | pub struct RawPixel { 263 | /// The position relative to the top left corner of the image. 264 | pub position: Point, 265 | 266 | /// The raw pixel color. 267 | pub color: u32, 268 | } 269 | 270 | impl RawPixel { 271 | /// Creates a new raw pixel. 272 | pub const fn new(position: Point, color: u32) -> Self { 273 | Self { position, color } 274 | } 275 | } 276 | -------------------------------------------------------------------------------- /src/raw_tga.rs: -------------------------------------------------------------------------------- 1 | use embedded_graphics::prelude::*; 2 | use nom::{bytes::complete::take, IResult}; 3 | 4 | use crate::{ 5 | color_map::ColorMap, 6 | footer::TgaFooter, 7 | header::{Bpp, ImageOrigin, TgaHeader}, 8 | parse_error::ParseError, 9 | raw_iter::RawPixels, 10 | Compression, DataType, 11 | }; 12 | 13 | /// Raw TGA image. 14 | /// 15 | /// `RawTga` can be used to access lower level information about a TGA file and to access the 16 | /// raw pixel data. It can be created directly by using the [`from_slice`] constructor or accessed 17 | /// by calling [`as_raw`] method of a [`Tga`] object. 18 | /// 19 | /// [`from_slice`]: #method.from_slice 20 | /// [`Tga`]: struct.Tga.html 21 | /// [`as_raw`]: struct.Tga.html#method.as_raw 22 | #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] 23 | pub struct RawTga<'a> { 24 | /// Image data 25 | data: &'a [u8], 26 | 27 | /// Color map 28 | color_map: Option>, 29 | 30 | /// Image pixel data 31 | pixel_data: &'a [u8], 32 | 33 | /// Image size 34 | size: Size, 35 | 36 | /// Data type 37 | data_type: DataType, 38 | 39 | /// Compression 40 | compression: Compression, 41 | 42 | /// Bits per pixel 43 | bpp: Bpp, 44 | 45 | /// Image origin 46 | image_origin: ImageOrigin, 47 | } 48 | 49 | impl<'a> RawTga<'a> { 50 | /// Parse a TGA image from a byte slice. 51 | pub fn from_slice(data: &'a [u8]) -> Result { 52 | let input = data; 53 | let (input, header) = TgaHeader::parse(input).map_err(|_| ParseError::Header)?; 54 | let (input, _image_id) = parse_image_id(input, &header).map_err(|_| ParseError::Header)?; 55 | let (input, color_map) = ColorMap::parse(input, &header)?; 56 | 57 | let footer_length = TgaFooter::parse(data).map_or(0, |footer| footer.length(data)); 58 | 59 | // Use saturating_sub to make sure this can't panic 60 | let pixel_data = &input[0..input.len().saturating_sub(footer_length)]; 61 | 62 | let size = Size::new(u32::from(header.width), u32::from(header.height)); 63 | 64 | Ok(Self { 65 | data, 66 | color_map, 67 | pixel_data, 68 | size, 69 | bpp: header.pixel_depth, 70 | image_origin: header.image_origin, 71 | data_type: header.data_type, 72 | compression: header.compression, 73 | }) 74 | } 75 | 76 | /// Returns the dimensions of this image. 77 | pub fn size(&self) -> Size { 78 | self.size 79 | } 80 | 81 | /// Returns the color map. 82 | /// 83 | /// `None` is returned if the image contains no color map. 84 | pub fn color_map(&self) -> Option<&ColorMap<'a>> { 85 | self.color_map.as_ref() 86 | } 87 | 88 | /// Returns the color bit depth (BPP) of this image. 89 | /// 90 | /// This function always returns the bit depth of the decoded pixels, regardless of how they are 91 | /// stored in the TGA file. Use [`image_data_bpp`] to get the number of bits used to store one 92 | /// pixel in the image data. 93 | /// 94 | /// [`image_data_bpp`]: #method.image_data_bpp 95 | pub fn color_bpp(&self) -> Bpp { 96 | if let Some(color_map) = &self.color_map { 97 | color_map.entry_bpp() 98 | } else { 99 | self.bpp 100 | } 101 | } 102 | 103 | /// Returns the image origin. 104 | pub fn image_origin(&self) -> ImageOrigin { 105 | self.image_origin 106 | } 107 | 108 | /// Returns the data type. 109 | pub fn data_type(&self) -> DataType { 110 | self.data_type 111 | } 112 | 113 | /// Returns the compression type. 114 | pub fn compression(&self) -> Compression { 115 | self.compression 116 | } 117 | 118 | /// Returns the raw image data contained in this image. 119 | pub fn image_data(&self) -> &'a [u8] { 120 | self.pixel_data 121 | } 122 | 123 | /// Returns the size of a single pixel in bits. 124 | /// 125 | /// This function returns the number of bits used to store a single pixel in the image data. 126 | /// 127 | /// For true color and grayscale images, where the colors are stored directly in the image data, 128 | /// the returned value will match the value returned by [`color_bpp`]. 129 | /// 130 | /// For color mapped images, where the image data consists of color indices, the returned value 131 | /// describes the bit depth of the indices and may differ from the depth returned by 132 | /// [`color_bpp`]. 133 | /// 134 | /// [`color_bpp`]: #method.color_bpp 135 | pub fn image_data_bpp(&self) -> Bpp { 136 | self.bpp 137 | } 138 | 139 | /// Returns an iterator over the raw pixels in this image. 140 | pub fn pixels(&self) -> RawPixels<'_> { 141 | RawPixels::new(self) 142 | } 143 | 144 | /// Returns the TGA header. 145 | /// 146 | /// The returned object is a direct representation of the header contained 147 | /// in the TGA file. Most of the information contained in the header is also 148 | /// available using other methods, which are the preferred way of accessing 149 | /// them. 150 | /// 151 | /// # Performance 152 | /// 153 | /// To save memory the header is parsed every time this method is called. 154 | pub fn header(&self) -> TgaHeader { 155 | // unwrap can't fail because the header was checked when self was created 156 | TgaHeader::parse(self.data).unwrap().1 157 | } 158 | 159 | /// Returns the developer directory. 160 | /// 161 | /// # Performance 162 | /// 163 | /// To save memory the footer is parsed every time this method is called. 164 | pub fn developer_directory(&self) -> Option<&'a [u8]> { 165 | TgaFooter::parse(self.data).and_then(|footer| footer.developer_directory(self.data)) 166 | } 167 | 168 | /// Returns the extension area. 169 | /// 170 | /// # Performance 171 | /// 172 | /// To save memory the footer is parsed every time this method is called. 173 | pub fn extension_area(&self) -> Option<&'a [u8]> { 174 | TgaFooter::parse(self.data).and_then(|footer| footer.extension_area(self.data)) 175 | } 176 | 177 | /// Returns the content of the image ID. 178 | /// 179 | /// If the TGA file doesn't contain an image ID `None` is returned. 180 | /// 181 | /// # Performance 182 | /// 183 | /// To save memory the header is parsed every time this method is called. 184 | pub fn image_id(&self) -> Option<&'a [u8]> { 185 | let (input, header) = TgaHeader::parse(self.data).ok()?; 186 | 187 | parse_image_id(input, &header) 188 | .ok() 189 | .map(|(_input, id)| id) 190 | .filter(|id| !id.is_empty()) 191 | } 192 | } 193 | 194 | fn parse_image_id<'a>(input: &'a [u8], header: &TgaHeader) -> IResult<&'a [u8], &'a [u8]> { 195 | take(header.id_len)(input) 196 | } 197 | -------------------------------------------------------------------------------- /tests/cbw8.rs: -------------------------------------------------------------------------------- 1 | use tinytga::{Bpp, Compression, DataType, ImageOrigin, RawTga, TgaHeader}; 2 | 3 | #[test] 4 | fn cbw8() { 5 | let data = include_bytes!("./cbw8.tga"); 6 | 7 | let img = RawTga::from_slice(data).unwrap(); 8 | 9 | println!("{:#?}", img.header()); 10 | println!("Raw image data len {:#?}", img.image_data().len()); 11 | 12 | assert_eq!( 13 | img.header(), 14 | TgaHeader { 15 | id_len: 26, 16 | has_color_map: false, 17 | data_type: DataType::BlackAndWhite, 18 | compression: Compression::Rle, 19 | color_map_start: 0, 20 | color_map_len: 0, 21 | color_map_depth: None, 22 | x_origin: 0, 23 | y_origin: 0, 24 | width: 128, 25 | height: 128, 26 | pixel_depth: Bpp::Bits8, 27 | image_origin: ImageOrigin::BottomLeft, 28 | alpha_channel_depth: 0, 29 | } 30 | ); 31 | 32 | const TGA_FOOTER_LENGTH: usize = 26; 33 | assert_eq!( 34 | img.extension_area(), 35 | Some(&data[8238..data.len() - TGA_FOOTER_LENGTH]) 36 | ); 37 | assert_eq!(img.developer_directory(), None); 38 | 39 | let pixels = img.pixels().collect::>(); 40 | 41 | assert_eq!(pixels.len(), 128 * 128); 42 | } 43 | -------------------------------------------------------------------------------- /tests/cbw8.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/cbw8.tga -------------------------------------------------------------------------------- /tests/chequerboard-rle-topleft.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/chequerboard-rle-topleft.tga -------------------------------------------------------------------------------- /tests/chequerboard-uncompressed-topleft.rs: -------------------------------------------------------------------------------- 1 | use tinytga::{Bpp, Compression, DataType, ImageOrigin, RawTga, TgaHeader}; 2 | 3 | #[test] 4 | fn chequerboard_uncompressed_topleft() { 5 | let data = include_bytes!("./chequerboard-uncompressed-topleft.tga"); 6 | 7 | let img = RawTga::from_slice(data).unwrap(); 8 | 9 | println!("{:#?}", img.header()); 10 | println!("Raw image data len {:#?}", img.image_data().len()); 11 | 12 | let header = img.header(); 13 | let image_data_len = header.width * header.height * header.pixel_depth.bytes() as u16; 14 | 15 | // Source image is 8x8px, uncompressed, 8BPP color 16 | assert_eq!( 17 | img.header(), 18 | TgaHeader { 19 | id_len: 0, 20 | has_color_map: false, 21 | data_type: DataType::BlackAndWhite, 22 | compression: Compression::Uncompressed, 23 | color_map_start: 0, 24 | color_map_len: 0, 25 | color_map_depth: None, 26 | x_origin: 0, 27 | y_origin: 8, 28 | width: 8, 29 | height: 8, 30 | pixel_depth: Bpp::Bits8, 31 | image_origin: ImageOrigin::TopLeft, 32 | alpha_channel_depth: 0, 33 | } 34 | ); 35 | 36 | // Footer is empty for this image 37 | assert_eq!(img.extension_area(), None); 38 | assert_eq!(img.developer_directory(), None); 39 | 40 | assert_eq!(img.image_data().len(), image_data_len as usize); 41 | } 42 | -------------------------------------------------------------------------------- /tests/chequerboard-uncompressed-topleft.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/chequerboard-uncompressed-topleft.tga -------------------------------------------------------------------------------- /tests/chessboard_4px_raw.rs: -------------------------------------------------------------------------------- 1 | use tinytga::{Bpp, Compression, DataType, ImageOrigin, RawTga, TgaHeader}; 2 | 3 | #[test] 4 | fn chessboard_4px_raw() { 5 | let data = include_bytes!("./chessboard_4px_raw.tga"); 6 | 7 | let img = RawTga::from_slice(data).unwrap(); 8 | 9 | println!("{:#?}", img.header()); 10 | println!("Raw image data len {:#?}", img.image_data().len()); 11 | println!("Raw image data {:#?}", img.image_data()); 12 | 13 | assert_eq!( 14 | img.header(), 15 | TgaHeader { 16 | id_len: 0, 17 | has_color_map: false, 18 | data_type: DataType::TrueColor, 19 | compression: Compression::Uncompressed, 20 | color_map_start: 0, 21 | color_map_len: 0, 22 | color_map_depth: None, 23 | x_origin: 0, 24 | y_origin: 4, 25 | width: 4, 26 | height: 4, 27 | pixel_depth: Bpp::Bits24, 28 | image_origin: ImageOrigin::TopLeft, 29 | alpha_channel_depth: 0, 30 | } 31 | ); 32 | 33 | assert_eq!(img.extension_area(), None); 34 | assert_eq!(img.developer_directory(), None); 35 | 36 | let pixels = img.pixels().map(|p| p.color).collect::>(); 37 | 38 | dbg!(&pixels); 39 | 40 | assert_eq!(pixels.len(), 4 * 4); 41 | assert_eq!( 42 | pixels, 43 | vec![ 44 | 0x00ffffffu32, 45 | 0x00000000, 46 | 0x00ffffff, 47 | 0x00000000, 48 | 0x00000000, 49 | 0x00ff0000, 50 | 0x00000000, 51 | 0x0000ff00, 52 | 0x00ffffff, 53 | 0x00000000, 54 | 0x000000ff, 55 | 0x00000000, 56 | 0x00000000, 57 | 0x00ffffff, 58 | 0x00000000, 59 | 0x00ffffff, 60 | ] 61 | ); 62 | } 63 | -------------------------------------------------------------------------------- /tests/chessboard_4px_raw.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/chessboard_4px_raw.tga -------------------------------------------------------------------------------- /tests/chessboard_4px_rle.rs: -------------------------------------------------------------------------------- 1 | use tinytga::{Bpp, Compression, DataType, ImageOrigin, RawTga, TgaHeader}; 2 | 3 | #[test] 4 | fn chessboard_4px_rle() { 5 | let data = include_bytes!("./chessboard_4px_rle.tga"); 6 | 7 | let img = RawTga::from_slice(data).unwrap(); 8 | 9 | println!("{:#?}", img.header()); 10 | println!("Raw image data len {:#?}", img.image_data().len()); 11 | println!("Raw image data {:#?}", img.image_data()); 12 | 13 | assert_eq!( 14 | img.header(), 15 | TgaHeader { 16 | id_len: 0, 17 | has_color_map: false, 18 | data_type: DataType::TrueColor, 19 | compression: Compression::Rle, 20 | color_map_start: 0, 21 | color_map_len: 0, 22 | color_map_depth: None, 23 | x_origin: 0, 24 | y_origin: 4, 25 | width: 4, 26 | height: 4, 27 | pixel_depth: Bpp::Bits24, 28 | image_origin: ImageOrigin::TopLeft, 29 | alpha_channel_depth: 0, 30 | } 31 | ); 32 | 33 | assert_eq!(img.extension_area(), None); 34 | assert_eq!(img.developer_directory(), None); 35 | 36 | let pixels = img.pixels().map(|p| p.color).collect::>(); 37 | 38 | // dbg!(&pixels); 39 | 40 | assert_eq!(pixels.len(), 4 * 4); 41 | assert_eq!( 42 | pixels, 43 | vec![ 44 | 0x00ffffffu32, 45 | 0x00000000, 46 | 0x00ffffff, 47 | 0x00000000, 48 | 0x00000000, 49 | 0x00ff0000, 50 | 0x00000000, 51 | 0x0000ff00, 52 | 0x00ffffff, 53 | 0x00000000, 54 | 0x000000ff, 55 | 0x00000000, 56 | 0x00000000, 57 | 0x00ffffff, 58 | 0x00000000, 59 | 0x00ffffff, 60 | ] 61 | ); 62 | } 63 | -------------------------------------------------------------------------------- /tests/chessboard_4px_rle.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/chessboard_4px_rle.tga -------------------------------------------------------------------------------- /tests/chessboard_raw.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/chessboard_raw.tga -------------------------------------------------------------------------------- /tests/chessboard_rle.rs: -------------------------------------------------------------------------------- 1 | use tinytga::{Bpp, Compression, DataType, ImageOrigin, RawTga, TgaHeader}; 2 | 3 | #[test] 4 | fn chessboard_rle() { 5 | let data = include_bytes!("./chessboard_rle.tga"); 6 | 7 | let img = RawTga::from_slice(data).unwrap(); 8 | 9 | println!("{:#?}", img.header()); 10 | println!("Raw image data len {:#?}", img.image_data().len()); 11 | println!("Raw image data {:#?}", img.image_data()); 12 | 13 | assert_eq!( 14 | img.header(), 15 | TgaHeader { 16 | id_len: 0, 17 | has_color_map: false, 18 | data_type: DataType::TrueColor, 19 | compression: Compression::Rle, 20 | color_map_start: 0, 21 | color_map_len: 0, 22 | color_map_depth: None, 23 | x_origin: 0, 24 | y_origin: 8, 25 | width: 8, 26 | height: 8, 27 | pixel_depth: Bpp::Bits24, 28 | image_origin: ImageOrigin::TopLeft, 29 | alpha_channel_depth: 0, 30 | } 31 | ); 32 | 33 | assert_eq!(img.extension_area(), None); 34 | assert_eq!(img.developer_directory(), None); 35 | 36 | let pixels = img.pixels().map(|p| p.color).collect::>(); 37 | 38 | dbg!(&pixels); 39 | 40 | assert_eq!(pixels.len(), 8 * 8); 41 | assert_eq!( 42 | pixels, 43 | vec![ 44 | 0xffffffu32, 45 | 0xffffff, 46 | 0x000000, 47 | 0x000000, 48 | 0xffffff, 49 | 0xffffff, 50 | 0x000000, 51 | 0x000000, 52 | 0xffffff, 53 | 0xffffff, 54 | 0x000000, 55 | 0x000000, 56 | 0xffffff, 57 | 0xffffff, 58 | 0x000000, 59 | 0x000000, 60 | 0x000000, 61 | 0x000000, 62 | 0xff0000, 63 | 0xff0000, 64 | 0x000000, 65 | 0x000000, 66 | 0x00ff00, 67 | 0x00ff00, 68 | 0x000000, 69 | 0x000000, 70 | 0xff0000, 71 | 0xff0000, 72 | 0x000000, 73 | 0x000000, 74 | 0x00ff00, 75 | 0x00ff00, 76 | 0xffffff, 77 | 0xffffff, 78 | 0x000000, 79 | 0x000000, 80 | 0x0000ff, 81 | 0x0000ff, 82 | 0x000000, 83 | 0x000000, 84 | 0xffffff, 85 | 0xffffff, 86 | 0x000000, 87 | 0x000000, 88 | 0x0000ff, 89 | 0x0000ff, 90 | 0x000000, 91 | 0x000000, 92 | 0x000000, 93 | 0x000000, 94 | 0xffffff, 95 | 0xffffff, 96 | 0x000000, 97 | 0x000000, 98 | 0xffffff, 99 | 0xffffff, 100 | 0x000000, 101 | 0x000000, 102 | 0xffffff, 103 | 0xffffff, 104 | 0x000000, 105 | 0x000000, 106 | 0xffffff, 107 | 0xffffff, 108 | ] 109 | ); 110 | } 111 | -------------------------------------------------------------------------------- /tests/chessboard_rle.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/chessboard_rle.tga -------------------------------------------------------------------------------- /tests/chessboard_uncompressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/chessboard_uncompressed.png -------------------------------------------------------------------------------- /tests/chessboard_uncompressed.rs: -------------------------------------------------------------------------------- 1 | use tinytga::{Bpp, Compression, DataType, ImageOrigin, RawTga, TgaHeader}; 2 | 3 | #[test] 4 | fn chessboard_uncompressed() { 5 | let data = include_bytes!("./chessboard_uncompressed.tga"); 6 | 7 | let img = RawTga::from_slice(data).unwrap(); 8 | 9 | println!("{:#?}", img.header()); 10 | println!("Raw image data len {:#?}", img.image_data().len()); 11 | println!("Raw image data {:#?}", img.image_data()); 12 | 13 | assert_eq!( 14 | img.header(), 15 | TgaHeader { 16 | id_len: 0, 17 | has_color_map: false, 18 | data_type: DataType::TrueColor, 19 | compression: Compression::Uncompressed, 20 | color_map_start: 0, 21 | color_map_len: 0, 22 | color_map_depth: None, 23 | x_origin: 0, 24 | y_origin: 8, 25 | width: 8, 26 | height: 8, 27 | pixel_depth: Bpp::Bits24, 28 | image_origin: ImageOrigin::TopLeft, 29 | alpha_channel_depth: 0, 30 | } 31 | ); 32 | 33 | assert_eq!(img.extension_area(), None); 34 | assert_eq!(img.developer_directory(), None); 35 | 36 | let pixels = img.pixels().map(|p| p.color).collect::>(); 37 | 38 | assert_eq!(pixels.len(), 8 * 8); 39 | assert_eq!( 40 | pixels, 41 | vec![ 42 | 0xffffffu32, 43 | 0xffffff, 44 | 0x000000, 45 | 0x000000, 46 | 0xffffff, 47 | 0xffffff, 48 | 0x000000, 49 | 0x000000, 50 | 0xffffff, 51 | 0xffffff, 52 | 0x000000, 53 | 0x000000, 54 | 0xffffff, 55 | 0xffffff, 56 | 0x000000, 57 | 0x000000, 58 | 0x000000, 59 | 0x000000, 60 | 0xff0000, 61 | 0xff0000, 62 | 0x000000, 63 | 0x000000, 64 | 0x00ff00, 65 | 0x00ff00, 66 | 0x000000, 67 | 0x000000, 68 | 0xff0000, 69 | 0xff0000, 70 | 0x000000, 71 | 0x000000, 72 | 0x00ff00, 73 | 0x00ff00, 74 | 0xffffff, 75 | 0xffffff, 76 | 0x000000, 77 | 0x000000, 78 | 0x0000ff, 79 | 0x0000ff, 80 | 0x000000, 81 | 0x000000, 82 | 0xffffff, 83 | 0xffffff, 84 | 0x000000, 85 | 0x000000, 86 | 0x0000ff, 87 | 0x0000ff, 88 | 0x000000, 89 | 0x000000, 90 | 0x000000, 91 | 0x000000, 92 | 0xffffff, 93 | 0xffffff, 94 | 0x000000, 95 | 0x000000, 96 | 0xffffff, 97 | 0xffffff, 98 | 0x000000, 99 | 0x000000, 100 | 0xffffff, 101 | 0xffffff, 102 | 0x000000, 103 | 0x000000, 104 | 0xffffff, 105 | 0xffffff, 106 | ] 107 | ); 108 | } 109 | -------------------------------------------------------------------------------- /tests/chessboard_uncompressed.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/chessboard_uncompressed.tga -------------------------------------------------------------------------------- /tests/coordinates.rs: -------------------------------------------------------------------------------- 1 | use embedded_graphics::prelude::*; 2 | use tinytga::{Bpp, Compression, DataType, ImageOrigin, RawTga, TgaHeader}; 3 | 4 | #[test] 5 | fn coordinates() { 6 | let data = include_bytes!("./chessboard_4px_raw.tga"); 7 | 8 | let img = RawTga::from_slice(data).unwrap(); 9 | 10 | println!("{:#?}", img.header()); 11 | println!("Raw image data len {:#?}", img.image_data().len()); 12 | println!("Raw image data {:#?}", img.image_data()); 13 | 14 | assert_eq!( 15 | img.header(), 16 | TgaHeader { 17 | id_len: 0, 18 | has_color_map: false, 19 | data_type: DataType::TrueColor, 20 | compression: Compression::Uncompressed, 21 | color_map_start: 0, 22 | color_map_len: 0, 23 | color_map_depth: None, 24 | x_origin: 0, 25 | y_origin: 4, 26 | width: 4, 27 | height: 4, 28 | pixel_depth: Bpp::Bits24, 29 | image_origin: ImageOrigin::TopLeft, 30 | alpha_channel_depth: 0, 31 | } 32 | ); 33 | 34 | assert_eq!(img.extension_area(), None); 35 | assert_eq!(img.developer_directory(), None); 36 | 37 | let coords: Vec<_> = img.pixels().map(|p| p.position).collect(); 38 | 39 | assert_eq!(coords.len(), 4 * 4); 40 | assert_eq!( 41 | coords, 42 | vec![ 43 | Point::new(0, 0), 44 | Point::new(1, 0), 45 | Point::new(2, 0), 46 | Point::new(3, 0), 47 | Point::new(0, 1), 48 | Point::new(1, 1), 49 | Point::new(2, 1), 50 | Point::new(3, 1), 51 | Point::new(0, 2), 52 | Point::new(1, 2), 53 | Point::new(2, 2), 54 | Point::new(3, 2), 55 | Point::new(0, 3), 56 | Point::new(1, 3), 57 | Point::new(2, 3), 58 | Point::new(3, 3), 59 | ] 60 | ); 61 | } 62 | -------------------------------------------------------------------------------- /tests/embedded_graphics.rs: -------------------------------------------------------------------------------- 1 | use embedded_graphics::{ 2 | image::Image, 3 | mock_display::{ColorMapping, MockDisplay}, 4 | pixelcolor::{Gray8, Rgb555, Rgb888}, 5 | prelude::*, 6 | }; 7 | use paste::paste; 8 | use tinytga::Tga; 9 | 10 | const CHESSBOARD_PATTERN: &[&str] = &[ 11 | "WKWK", // 12 | "KRKG", // 13 | "WKBK", // 14 | "KWKW", // 15 | ]; 16 | 17 | const GRAY_PATTERN: &[&str] = &[ 18 | "0F0F0F0F0", 19 | "00FF00FF0", 20 | "0000FFFF0", 21 | "012345670", 22 | "89ABCDEF0", 23 | ]; 24 | 25 | const COLOR_PATTERN: &[&str] = &[ 26 | "WKRGBYMCW", 27 | "KKRGBYMCW", 28 | "WKRGBYMCW", 29 | "KKKKKKKKK", 30 | "WKWCMYBGR", 31 | ]; 32 | 33 | #[test] 34 | fn chessboard_compressed() { 35 | let tga = Tga::from_slice(include_bytes!("./chessboard_4px_rle.tga")).unwrap(); 36 | let image = Image::new(&tga, Point::zero()); 37 | 38 | let mut display = MockDisplay::::new(); 39 | image.draw(&mut display).unwrap(); 40 | 41 | display.assert_pattern(CHESSBOARD_PATTERN); 42 | } 43 | 44 | #[test] 45 | fn chessboard_uncompressed() { 46 | let tga = Tga::from_slice(include_bytes!("./chessboard_raw.tga")).unwrap(); 47 | let image = Image::new(&tga, Point::zero()); 48 | 49 | let mut display = MockDisplay::::new(); 50 | image.draw(&mut display).unwrap(); 51 | 52 | display.assert_pattern(CHESSBOARD_PATTERN); 53 | } 54 | 55 | fn test_tga(data: &[u8], pattern: &[&str]) 56 | where 57 | C: PixelColor + From + From + From + ColorMapping, 58 | { 59 | let tga = Tga::from_slice(data).unwrap(); 60 | let image = Image::new(&tga, Point::zero()); 61 | 62 | let mut display = MockDisplay::::new(); 63 | image.draw(&mut display).unwrap(); 64 | 65 | display.assert_pattern(pattern); 66 | } 67 | 68 | fn test_pixels_iter(data: &[u8], pattern: &[&str]) 69 | where 70 | C: PixelColor + From + From + From + ColorMapping, 71 | { 72 | let tga = Tga::from_slice(data).unwrap(); 73 | 74 | let mut display = MockDisplay::::new(); 75 | tga.pixels().draw(&mut display).unwrap(); 76 | 77 | let expected: MockDisplay = MockDisplay::::from_pattern(pattern).map(|c| c.into()); 78 | 79 | display.assert_eq(&expected); 80 | } 81 | 82 | macro_rules! test_tga { 83 | ($image_type:ident, $color_type:ty, $pattern:expr) => { 84 | paste! { 85 | #[test] 86 | fn [<$image_type _bl>]() { 87 | test_tga::<$color_type>(include_bytes!(concat!(stringify!($image_type), "_bl.tga")), $pattern); 88 | } 89 | 90 | #[test] 91 | fn [<$image_type _tl>]() { 92 | test_tga::<$color_type>(include_bytes!(concat!(stringify!($image_type), "_tl.tga")), $pattern); 93 | } 94 | 95 | #[test] 96 | fn [<$image_type _bl_pixels_iter>]() { 97 | test_pixels_iter::<$color_type>(include_bytes!(concat!(stringify!($image_type), "_bl.tga")), $pattern); 98 | } 99 | 100 | #[test] 101 | fn [<$image_type _tl_pixels_iter>]() { 102 | test_pixels_iter::<$color_type>(include_bytes!(concat!(stringify!($image_type), "_tl.tga")), $pattern); 103 | } 104 | } 105 | }; 106 | 107 | ($image_type:ident, Rgb555) => { 108 | test_tga!($image_type, Rgb555, COLOR_PATTERN); 109 | }; 110 | 111 | ($image_type:ident, Rgb888) => { 112 | test_tga!($image_type, Rgb888, COLOR_PATTERN); 113 | }; 114 | 115 | ($image_type:ident, Gray8) => { 116 | test_tga!($image_type, Gray8, GRAY_PATTERN); 117 | }; 118 | } 119 | 120 | // Type 1: color mapped, uncompressed 121 | test_tga!(type1_16bpp, Rgb555); 122 | test_tga!(type1_24bpp, Rgb888); 123 | 124 | // Type 2: true color, uncompressed 125 | test_tga!(type2_16bpp, Rgb555); 126 | test_tga!(type2_24bpp, Rgb888); 127 | 128 | // Type 3: grayscale, uncompressed 129 | test_tga!(type3, Gray8); 130 | 131 | // Type 9: color mapped, RLE compressed 132 | test_tga!(type9_16bpp, Rgb555); 133 | test_tga!(type9_24bpp, Rgb888); 134 | 135 | // Type 10: true color, RLE compressed 136 | test_tga!(type10_16bpp, Rgb555); 137 | test_tga!(type10_24bpp, Rgb888); 138 | 139 | // Type 11: grayscale, RLE compressed 140 | test_tga!(type11, Gray8); 141 | -------------------------------------------------------------------------------- /tests/error_color_map.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/error_color_map.tga -------------------------------------------------------------------------------- /tests/error_no_image_data.tga: -------------------------------------------------------------------------------- 1 |   -------------------------------------------------------------------------------- /tests/error_truncated_image_data.tga: -------------------------------------------------------------------------------- 1 |    -------------------------------------------------------------------------------- /tests/errors.rs: -------------------------------------------------------------------------------- 1 | use embedded_graphics::{prelude::*, primitives::Rectangle}; 2 | use std::iter::repeat; 3 | use tinytga::{ParseError, RawPixel, RawTga}; 4 | 5 | #[test] 6 | fn color_map() { 7 | // The color map in "error_color_map.tga" has too many entries and is larger than the file 8 | assert_eq!( 9 | RawTga::from_slice(include_bytes!("../tests/error_color_map.tga")), 10 | Err(ParseError::ColorMap) 11 | ); 12 | } 13 | 14 | #[test] 15 | fn image_data_missing() { 16 | // The image data in "error_no_image_data.tga" is missing 17 | let tga = RawTga::from_slice(include_bytes!("../tests/error_no_image_data.tga")).unwrap(); 18 | 19 | assert!(tga.image_data().is_empty()); 20 | 21 | let expected: Vec<_> = Rectangle::new(Point::zero(), tga.size()) 22 | .points() 23 | .map(|p| RawPixel::new(p, 0)) 24 | .collect(); 25 | 26 | let pixels: Vec<_> = tga.pixels().collect(); 27 | 28 | assert_eq!(pixels, expected); 29 | } 30 | 31 | #[test] 32 | fn image_data_truncated() { 33 | // The image data in "error_truncated_image_data.tga" is truncated. 34 | let tga = 35 | RawTga::from_slice(include_bytes!("../tests/error_truncated_image_data.tga")).unwrap(); 36 | 37 | assert_eq!(tga.image_data(), &[1, 2, 3, 4, 5, 6, 7, 8]); 38 | 39 | let expected: Vec<_> = Rectangle::new(Point::zero(), tga.size()) 40 | .points() 41 | .zip((1..=8).chain(repeat(0))) 42 | .map(|(p, c)| RawPixel::new(p, c)) 43 | .collect(); 44 | 45 | let pixels: Vec<_> = tga.pixels().collect(); 46 | 47 | assert_eq!(pixels, expected); 48 | } 49 | 50 | // #[test] 51 | // fn mismatched_bpp() { 52 | // // type2_tl_24bpp.tga is a 24 BPP image 53 | // assert_eq!( 54 | // Tga::::from_slice(include_bytes!("../tests/type2_24bpp_tl.tga")), 55 | // Err(ParseError::MismatchedBpp(24)) 56 | // ); 57 | 58 | // // type3_tl.tga is a 8 BPP image 59 | // assert_eq!( 60 | // Tga::::from_slice(include_bytes!("../tests/type3_tl.tga")), 61 | // Err(ParseError::MismatchedBpp(8)) 62 | // ); 63 | // } 64 | -------------------------------------------------------------------------------- /tests/image_id.rs: -------------------------------------------------------------------------------- 1 | use tinytga::RawTga; 2 | 3 | #[test] 4 | fn has_image_id() { 5 | // image_id.tga contains the image ID: "e-g" 6 | let data = include_bytes!("./image_id.tga"); 7 | 8 | let img = RawTga::from_slice(data).unwrap(); 9 | 10 | assert_eq!(img.image_id(), Some("e-g".as_bytes())); 11 | } 12 | 13 | #[test] 14 | fn no_image_id() { 15 | // type1_24bpp_bl.tga does not contain an image ID 16 | let data = include_bytes!("./type1_24bpp_bl.tga"); 17 | 18 | let img = RawTga::from_slice(data).unwrap(); 19 | 20 | assert_eq!(img.image_id(), None); 21 | } 22 | -------------------------------------------------------------------------------- /tests/image_id.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/image_id.tga -------------------------------------------------------------------------------- /tests/issue_216.rs: -------------------------------------------------------------------------------- 1 | use tinytga::RawTga; 2 | 3 | /// Test for issue #216 4 | /// 5 | /// RLE compressed images caused an integer overflow if `number_of_pixels * bytes_per_pixel` in a 6 | /// RLE packet was larger than 255. 7 | #[test] 8 | fn issue_216() { 9 | let uncompressed = RawTga::from_slice(include_bytes!("issue_216_uncompressed.tga")).unwrap(); 10 | let compressed = RawTga::from_slice(include_bytes!("issue_216_compressed.tga")).unwrap(); 11 | 12 | let uncompressed_header = uncompressed.header(); 13 | let compressed_header = uncompressed.header(); 14 | 15 | assert_eq!(uncompressed_header.width, compressed_header.width); 16 | assert_eq!(uncompressed_header.height, compressed_header.height); 17 | assert_eq!( 18 | uncompressed_header.pixel_depth, 19 | compressed_header.pixel_depth 20 | ); 21 | 22 | assert!(uncompressed.pixels().eq(compressed.pixels())); 23 | } 24 | -------------------------------------------------------------------------------- /tests/issue_216_compressed.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/issue_216_compressed.tga -------------------------------------------------------------------------------- /tests/issue_216_uncompressed.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/issue_216_uncompressed.tga -------------------------------------------------------------------------------- /tests/logo.rs: -------------------------------------------------------------------------------- 1 | use embedded_graphics::{ 2 | image::{Image, ImageRawBE, ImageRawLE}, 3 | pixelcolor::{Gray8, Rgb555, Rgb888}, 4 | prelude::*, 5 | }; 6 | use tinytga::{Bpp, Compression, DataType, ImageOrigin, Tga}; 7 | 8 | const WIDTH: usize = 240; 9 | const HEIGHT: usize = 320; 10 | 11 | // TODO: use e-g framebuffer when it's added 12 | #[derive(Debug, PartialEq)] 13 | struct Framebuffer { 14 | pixels: [[C; 240]; 320], 15 | } 16 | 17 | impl + std::fmt::Debug> Framebuffer { 18 | pub fn new() -> Self { 19 | let color = C::from(Rgb888::BLACK); 20 | 21 | Self { 22 | pixels: [[color; WIDTH]; HEIGHT], 23 | } 24 | } 25 | 26 | pub fn from_image(image: impl ImageDrawable) -> Self { 27 | let mut framebuffer = Framebuffer::::new(); 28 | Image::new(&image, Point::zero()) 29 | .draw(&mut framebuffer) 30 | .unwrap(); 31 | framebuffer 32 | } 33 | 34 | pub fn pixels(&self) -> impl Iterator + '_ { 35 | self.pixels.iter().flatten().copied() 36 | } 37 | 38 | pub fn assert_eq(&self, expected: &Self) { 39 | let zipped = || self.pixels().zip(expected.pixels()); 40 | 41 | let errors = zipped().filter(|(a, b)| a != b).count(); 42 | let first_error = zipped() 43 | .enumerate() 44 | .find(|(_, (a, b))| a != b) 45 | .map(|(i, (a, b))| (Point::new((i % WIDTH) as i32, (i / WIDTH) as i32), a, b)); 46 | 47 | if self != expected { 48 | let first_error = first_error.unwrap(); 49 | panic!( 50 | "framebuffer differs from expected\n{} errors\nfirst error at ({}): {:?} (expected {:?})", 51 | errors, 52 | first_error.0, 53 | first_error.1, 54 | first_error.2, 55 | ); 56 | } 57 | } 58 | } 59 | 60 | impl DrawTarget for Framebuffer { 61 | type Color = C; 62 | type Error = std::convert::Infallible; 63 | 64 | fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error> 65 | where 66 | I: IntoIterator>, 67 | { 68 | for Pixel(p, c) in pixels { 69 | self.pixels[p.y as usize][p.x as usize] = c; 70 | } 71 | 72 | Ok(()) 73 | } 74 | } 75 | 76 | impl OriginDimensions for Framebuffer { 77 | fn size(&self) -> embedded_graphics::prelude::Size { 78 | Size::new(240, 320) 79 | } 80 | } 81 | 82 | fn expected_rgb555() -> Framebuffer { 83 | Framebuffer::from_image(ImageRawLE::::new( 84 | include_bytes!("logo_rgb555.raw"), 85 | WIDTH as u32, 86 | )) 87 | } 88 | 89 | fn expected_rgb888() -> Framebuffer { 90 | Framebuffer::from_image(ImageRawBE::::new( 91 | include_bytes!("logo_rgb888.raw"), 92 | WIDTH as u32, 93 | )) 94 | } 95 | 96 | fn expected_gray8() -> Framebuffer { 97 | Framebuffer::from_image(ImageRawBE::::new( 98 | include_bytes!("logo_gray8.raw"), 99 | WIDTH as u32, 100 | )) 101 | } 102 | 103 | #[track_caller] 104 | fn assert_format( 105 | tga: &Tga, 106 | data_type: DataType, 107 | compression: Compression, 108 | pixel_depth: Bpp, 109 | image_origin: ImageOrigin, 110 | color_map_depth: Option, 111 | ) where 112 | C: PixelColor + From + From + From, 113 | { 114 | assert_eq!(tga.as_raw().header().data_type, data_type); 115 | assert_eq!(tga.as_raw().header().compression, compression); 116 | assert_eq!(tga.as_raw().header().pixel_depth, pixel_depth); 117 | assert_eq!(tga.as_raw().header().image_origin, image_origin); 118 | 119 | assert_eq!(tga.as_raw().header().color_map_depth, color_map_depth); 120 | assert_eq!(tga.as_raw().header().color_map_start, 0); 121 | if color_map_depth.is_some() { 122 | assert!(tga.as_raw().header().color_map_len > 0); 123 | } else { 124 | assert_eq!(tga.as_raw().header().color_map_len, 0); 125 | } 126 | } 127 | 128 | #[test] 129 | fn logo_type1_16bpp_tl() { 130 | let tga = Tga::::from_slice(include_bytes!("logo_type1_16bpp_tl.tga")).unwrap(); 131 | assert_format( 132 | &tga, 133 | DataType::ColorMapped, 134 | Compression::Uncompressed, 135 | Bpp::Bits8, 136 | ImageOrigin::TopLeft, 137 | Some(Bpp::Bits16), 138 | ); 139 | 140 | Framebuffer::from_image(tga).assert_eq(&expected_rgb555()); 141 | } 142 | 143 | #[test] 144 | fn logo_type1_16bpp_bl() { 145 | let tga = Tga::::from_slice(include_bytes!("logo_type1_16bpp_bl.tga")).unwrap(); 146 | assert_format( 147 | &tga, 148 | DataType::ColorMapped, 149 | Compression::Uncompressed, 150 | Bpp::Bits8, 151 | ImageOrigin::BottomLeft, 152 | Some(Bpp::Bits16), 153 | ); 154 | 155 | Framebuffer::from_image(tga).assert_eq(&expected_rgb555()); 156 | } 157 | 158 | #[test] 159 | fn logo_type1_24bpp_tl() { 160 | let tga = Tga::::from_slice(include_bytes!("logo_type1_24bpp_tl.tga")).unwrap(); 161 | assert_format( 162 | &tga, 163 | DataType::ColorMapped, 164 | Compression::Uncompressed, 165 | Bpp::Bits8, 166 | ImageOrigin::TopLeft, 167 | Some(Bpp::Bits24), 168 | ); 169 | 170 | Framebuffer::from_image(tga).assert_eq(&expected_rgb888()); 171 | } 172 | 173 | #[test] 174 | fn logo_type1_24bpp_bl() { 175 | let tga = Tga::::from_slice(include_bytes!("logo_type1_24bpp_bl.tga")).unwrap(); 176 | assert_format( 177 | &tga, 178 | DataType::ColorMapped, 179 | Compression::Uncompressed, 180 | Bpp::Bits8, 181 | ImageOrigin::BottomLeft, 182 | Some(Bpp::Bits24), 183 | ); 184 | 185 | Framebuffer::from_image(tga).assert_eq(&expected_rgb888()); 186 | } 187 | 188 | #[test] 189 | fn logo_type2_16bpp_tl() { 190 | let tga = Tga::::from_slice(include_bytes!("logo_type2_16bpp_tl.tga")).unwrap(); 191 | assert_format( 192 | &tga, 193 | DataType::TrueColor, 194 | Compression::Uncompressed, 195 | Bpp::Bits16, 196 | ImageOrigin::TopLeft, 197 | None, 198 | ); 199 | 200 | Framebuffer::from_image(tga).assert_eq(&expected_rgb555()); 201 | } 202 | 203 | #[test] 204 | fn logo_type2_16bpp_bl() { 205 | let tga = Tga::::from_slice(include_bytes!("logo_type2_16bpp_bl.tga")).unwrap(); 206 | assert_format( 207 | &tga, 208 | DataType::TrueColor, 209 | Compression::Uncompressed, 210 | Bpp::Bits16, 211 | ImageOrigin::BottomLeft, 212 | None, 213 | ); 214 | 215 | Framebuffer::from_image(tga).assert_eq(&expected_rgb555()); 216 | } 217 | 218 | #[test] 219 | fn logo_type2_24bpp_tl() { 220 | let tga = Tga::::from_slice(include_bytes!("logo_type2_24bpp_tl.tga")).unwrap(); 221 | assert_format( 222 | &tga, 223 | DataType::TrueColor, 224 | Compression::Uncompressed, 225 | Bpp::Bits24, 226 | ImageOrigin::TopLeft, 227 | None, 228 | ); 229 | 230 | Framebuffer::from_image(tga).assert_eq(&expected_rgb888()); 231 | } 232 | 233 | #[test] 234 | fn logo_type2_24bpp_bl() { 235 | let tga = Tga::::from_slice(include_bytes!("logo_type2_24bpp_bl.tga")).unwrap(); 236 | assert_format( 237 | &tga, 238 | DataType::TrueColor, 239 | Compression::Uncompressed, 240 | Bpp::Bits24, 241 | ImageOrigin::BottomLeft, 242 | None, 243 | ); 244 | 245 | Framebuffer::from_image(tga).assert_eq(&expected_rgb888()); 246 | } 247 | 248 | #[test] 249 | fn logo_type2_24bpp_tr() { 250 | let tga = Tga::::from_slice(include_bytes!("logo_type2_24bpp_tr.tga")).unwrap(); 251 | assert_format( 252 | &tga, 253 | DataType::TrueColor, 254 | Compression::Uncompressed, 255 | Bpp::Bits24, 256 | ImageOrigin::TopRight, 257 | None, 258 | ); 259 | 260 | Framebuffer::from_image(tga).assert_eq(&expected_rgb888()); 261 | } 262 | 263 | #[test] 264 | fn logo_type2_24bpp_br() { 265 | let tga = Tga::::from_slice(include_bytes!("logo_type2_24bpp_br.tga")).unwrap(); 266 | assert_format( 267 | &tga, 268 | DataType::TrueColor, 269 | Compression::Uncompressed, 270 | Bpp::Bits24, 271 | ImageOrigin::BottomRight, 272 | None, 273 | ); 274 | 275 | Framebuffer::from_image(tga).assert_eq(&expected_rgb888()); 276 | } 277 | 278 | #[test] 279 | fn logo_type3_tl() { 280 | let tga = Tga::::from_slice(include_bytes!("logo_type3_tl.tga")).unwrap(); 281 | assert_format( 282 | &tga, 283 | DataType::BlackAndWhite, 284 | Compression::Uncompressed, 285 | Bpp::Bits8, 286 | ImageOrigin::TopLeft, 287 | None, 288 | ); 289 | 290 | Framebuffer::from_image(tga).assert_eq(&expected_gray8()); 291 | } 292 | 293 | #[test] 294 | fn logo_type3_bl() { 295 | let tga = Tga::::from_slice(include_bytes!("logo_type3_bl.tga")).unwrap(); 296 | assert_format( 297 | &tga, 298 | DataType::BlackAndWhite, 299 | Compression::Uncompressed, 300 | Bpp::Bits8, 301 | ImageOrigin::BottomLeft, 302 | None, 303 | ); 304 | 305 | Framebuffer::from_image(tga).assert_eq(&expected_gray8()); 306 | } 307 | 308 | #[test] 309 | fn logo_type9_16bpp_tl() { 310 | let tga = Tga::::from_slice(include_bytes!("logo_type9_16bpp_tl.tga")).unwrap(); 311 | assert_format( 312 | &tga, 313 | DataType::ColorMapped, 314 | Compression::Rle, 315 | Bpp::Bits8, 316 | ImageOrigin::TopLeft, 317 | Some(Bpp::Bits16), 318 | ); 319 | 320 | Framebuffer::from_image(tga).assert_eq(&expected_rgb555()); 321 | } 322 | 323 | #[test] 324 | fn logo_type9_16bpp_bl() { 325 | let tga = Tga::::from_slice(include_bytes!("logo_type9_16bpp_bl.tga")).unwrap(); 326 | assert_format( 327 | &tga, 328 | DataType::ColorMapped, 329 | Compression::Rle, 330 | Bpp::Bits8, 331 | ImageOrigin::BottomLeft, 332 | Some(Bpp::Bits16), 333 | ); 334 | 335 | Framebuffer::from_image(tga).assert_eq(&expected_rgb555()); 336 | } 337 | 338 | #[test] 339 | fn logo_type9_24bpp_tl() { 340 | let tga = Tga::::from_slice(include_bytes!("logo_type9_24bpp_tl.tga")).unwrap(); 341 | assert_format( 342 | &tga, 343 | DataType::ColorMapped, 344 | Compression::Rle, 345 | Bpp::Bits8, 346 | ImageOrigin::TopLeft, 347 | Some(Bpp::Bits24), 348 | ); 349 | 350 | Framebuffer::from_image(tga).assert_eq(&expected_rgb888()); 351 | } 352 | 353 | #[test] 354 | fn logo_type9_24bpp_bl() { 355 | let tga = Tga::::from_slice(include_bytes!("logo_type9_24bpp_bl.tga")).unwrap(); 356 | assert_format( 357 | &tga, 358 | DataType::ColorMapped, 359 | Compression::Rle, 360 | Bpp::Bits8, 361 | ImageOrigin::BottomLeft, 362 | Some(Bpp::Bits24), 363 | ); 364 | 365 | Framebuffer::from_image(tga).assert_eq(&expected_rgb888()); 366 | } 367 | 368 | #[test] 369 | fn logo_type10_16bpp_tl() { 370 | let tga = Tga::::from_slice(include_bytes!("logo_type10_16bpp_tl.tga")).unwrap(); 371 | assert_format( 372 | &tga, 373 | DataType::TrueColor, 374 | Compression::Rle, 375 | Bpp::Bits16, 376 | ImageOrigin::TopLeft, 377 | None, 378 | ); 379 | 380 | Framebuffer::from_image(tga).assert_eq(&expected_rgb555()); 381 | } 382 | 383 | #[test] 384 | fn logo_type10_16bpp_bl() { 385 | let tga = Tga::::from_slice(include_bytes!("logo_type10_16bpp_bl.tga")).unwrap(); 386 | assert_format( 387 | &tga, 388 | DataType::TrueColor, 389 | Compression::Rle, 390 | Bpp::Bits16, 391 | ImageOrigin::BottomLeft, 392 | None, 393 | ); 394 | 395 | Framebuffer::from_image(tga).assert_eq(&expected_rgb555()); 396 | } 397 | 398 | #[test] 399 | fn logo_type10_24bpp_tl() { 400 | let tga = Tga::::from_slice(include_bytes!("logo_type10_24bpp_tl.tga")).unwrap(); 401 | assert_format( 402 | &tga, 403 | DataType::TrueColor, 404 | Compression::Rle, 405 | Bpp::Bits24, 406 | ImageOrigin::TopLeft, 407 | None, 408 | ); 409 | 410 | Framebuffer::from_image(tga).assert_eq(&expected_rgb888()); 411 | } 412 | 413 | #[test] 414 | fn logo_type10_24bpp_bl() { 415 | let tga = Tga::::from_slice(include_bytes!("logo_type10_24bpp_bl.tga")).unwrap(); 416 | assert_format( 417 | &tga, 418 | DataType::TrueColor, 419 | Compression::Rle, 420 | Bpp::Bits24, 421 | ImageOrigin::BottomLeft, 422 | None, 423 | ); 424 | 425 | Framebuffer::from_image(tga).assert_eq(&expected_rgb888()); 426 | } 427 | 428 | #[test] 429 | fn logo_type11_tl() { 430 | let tga = Tga::::from_slice(include_bytes!("logo_type11_tl.tga")).unwrap(); 431 | assert_format( 432 | &tga, 433 | DataType::BlackAndWhite, 434 | Compression::Rle, 435 | Bpp::Bits8, 436 | ImageOrigin::TopLeft, 437 | None, 438 | ); 439 | 440 | Framebuffer::from_image(tga).assert_eq(&expected_gray8()); 441 | } 442 | 443 | #[test] 444 | fn logo_type11_bl() { 445 | let tga = Tga::::from_slice(include_bytes!("logo_type11_bl.tga")).unwrap(); 446 | assert_format( 447 | &tga, 448 | DataType::BlackAndWhite, 449 | Compression::Rle, 450 | Bpp::Bits8, 451 | ImageOrigin::BottomLeft, 452 | None, 453 | ); 454 | 455 | Framebuffer::from_image(tga).assert_eq(&expected_gray8()); 456 | } 457 | -------------------------------------------------------------------------------- /tests/logo_gray8.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/logo_gray8.raw -------------------------------------------------------------------------------- /tests/logo_rgb555.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/logo_rgb555.raw -------------------------------------------------------------------------------- /tests/logo_rgb888.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/logo_rgb888.raw -------------------------------------------------------------------------------- /tests/logo_type10_16bpp_bl.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/logo_type10_16bpp_bl.tga -------------------------------------------------------------------------------- /tests/logo_type10_16bpp_tl.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/logo_type10_16bpp_tl.tga -------------------------------------------------------------------------------- /tests/logo_type10_24bpp_bl.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/logo_type10_24bpp_bl.tga -------------------------------------------------------------------------------- /tests/logo_type10_24bpp_tl.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/logo_type10_24bpp_tl.tga -------------------------------------------------------------------------------- /tests/logo_type11_bl.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/logo_type11_bl.tga -------------------------------------------------------------------------------- /tests/logo_type11_tl.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/logo_type11_tl.tga -------------------------------------------------------------------------------- /tests/logo_type1_16bpp_bl.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/logo_type1_16bpp_bl.tga -------------------------------------------------------------------------------- /tests/logo_type1_16bpp_tl.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/logo_type1_16bpp_tl.tga -------------------------------------------------------------------------------- /tests/logo_type1_24bpp_bl.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/logo_type1_24bpp_bl.tga -------------------------------------------------------------------------------- /tests/logo_type1_24bpp_tl.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/logo_type1_24bpp_tl.tga -------------------------------------------------------------------------------- /tests/logo_type2_16bpp_bl.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/logo_type2_16bpp_bl.tga -------------------------------------------------------------------------------- /tests/logo_type2_16bpp_tl.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/logo_type2_16bpp_tl.tga -------------------------------------------------------------------------------- /tests/logo_type2_24bpp_bl.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/logo_type2_24bpp_bl.tga -------------------------------------------------------------------------------- /tests/logo_type2_24bpp_br.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/logo_type2_24bpp_br.tga -------------------------------------------------------------------------------- /tests/logo_type2_24bpp_tl.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/logo_type2_24bpp_tl.tga -------------------------------------------------------------------------------- /tests/logo_type2_24bpp_tr.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/logo_type2_24bpp_tr.tga -------------------------------------------------------------------------------- /tests/logo_type3_bl.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/logo_type3_bl.tga -------------------------------------------------------------------------------- /tests/logo_type3_tl.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/logo_type3_tl.tga -------------------------------------------------------------------------------- /tests/logo_type9_16bpp_bl.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/logo_type9_16bpp_bl.tga -------------------------------------------------------------------------------- /tests/logo_type9_16bpp_tl.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/logo_type9_16bpp_tl.tga -------------------------------------------------------------------------------- /tests/logo_type9_24bpp_bl.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/logo_type9_24bpp_bl.tga -------------------------------------------------------------------------------- /tests/logo_type9_24bpp_tl.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/logo_type9_24bpp_tl.tga -------------------------------------------------------------------------------- /tests/rust-rle-bw-bottomleft.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/rust-rle-bw-bottomleft.tga -------------------------------------------------------------------------------- /tests/rust-rle-bw-topleft.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/rust-rle-bw-topleft.tga -------------------------------------------------------------------------------- /tests/rust-uncompressed-bw-bottomleft.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/rust-uncompressed-bw-bottomleft.tga -------------------------------------------------------------------------------- /tests/rust-uncompressed-bw-topleft.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/rust-uncompressed-bw-topleft.tga -------------------------------------------------------------------------------- /tests/type10_16bpp_bl.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/type10_16bpp_bl.tga -------------------------------------------------------------------------------- /tests/type10_16bpp_tl.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/type10_16bpp_tl.tga -------------------------------------------------------------------------------- /tests/type10_24bpp_bl.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/type10_24bpp_bl.tga -------------------------------------------------------------------------------- /tests/type10_24bpp_tl.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/type10_24bpp_tl.tga -------------------------------------------------------------------------------- /tests/type11_bl.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/type11_bl.tga -------------------------------------------------------------------------------- /tests/type11_tl.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/type11_tl.tga -------------------------------------------------------------------------------- /tests/type1_16bpp_bl.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/type1_16bpp_bl.tga -------------------------------------------------------------------------------- /tests/type1_16bpp_tl.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/type1_16bpp_tl.tga -------------------------------------------------------------------------------- /tests/type1_24bpp_bl.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/type1_24bpp_bl.tga -------------------------------------------------------------------------------- /tests/type1_24bpp_tl.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/type1_24bpp_tl.tga -------------------------------------------------------------------------------- /tests/type2_16bpp_bl.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/type2_16bpp_bl.tga -------------------------------------------------------------------------------- /tests/type2_16bpp_tl.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/type2_16bpp_tl.tga -------------------------------------------------------------------------------- /tests/type2_24bpp_bl.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/type2_24bpp_bl.tga -------------------------------------------------------------------------------- /tests/type2_24bpp_tl.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/type2_24bpp_tl.tga -------------------------------------------------------------------------------- /tests/type3_bl.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/type3_bl.tga -------------------------------------------------------------------------------- /tests/type3_tl.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/type3_tl.tga -------------------------------------------------------------------------------- /tests/type9_16bpp_bl.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/type9_16bpp_bl.tga -------------------------------------------------------------------------------- /tests/type9_16bpp_tl.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/type9_16bpp_tl.tga -------------------------------------------------------------------------------- /tests/type9_24bpp_bl.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/type9_24bpp_bl.tga -------------------------------------------------------------------------------- /tests/type9_24bpp_tl.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/type9_24bpp_tl.tga -------------------------------------------------------------------------------- /tests/types.rs: -------------------------------------------------------------------------------- 1 | use tinytga::{Bpp, Compression, DataType, ImageOrigin, RawTga, TgaHeader}; 2 | 3 | const HEADER_DEFAULT: TgaHeader = TgaHeader { 4 | id_len: 0, 5 | has_color_map: false, 6 | data_type: DataType::NoData, 7 | compression: Compression::Uncompressed, 8 | color_map_start: 0, 9 | color_map_len: 0, 10 | color_map_depth: None, 11 | x_origin: 0, 12 | y_origin: 0, 13 | width: 9, 14 | height: 5, 15 | pixel_depth: Bpp::Bits8, 16 | image_origin: ImageOrigin::BottomLeft, 17 | alpha_channel_depth: 0, 18 | }; 19 | 20 | #[test] 21 | fn type1_16bpp_bl() { 22 | let tga = RawTga::from_slice(include_bytes!("../tests/type1_16bpp_bl.tga")).unwrap(); 23 | 24 | assert_eq!( 25 | tga.header(), 26 | TgaHeader { 27 | has_color_map: true, 28 | data_type: DataType::ColorMapped, 29 | color_map_start: 0, 30 | color_map_len: 8, 31 | color_map_depth: Some(Bpp::Bits16), 32 | ..HEADER_DEFAULT 33 | } 34 | ); 35 | assert_eq!(tga.developer_directory(), None); 36 | assert_eq!(tga.extension_area(), None); 37 | 38 | assert_eq!(tga.color_bpp(), Bpp::Bits16); 39 | assert_eq!(tga.image_data_bpp(), Bpp::Bits8); 40 | } 41 | 42 | #[test] 43 | fn type1_24bpp_bl() { 44 | let tga = RawTga::from_slice(include_bytes!("../tests/type1_24bpp_bl.tga")).unwrap(); 45 | 46 | assert_eq!( 47 | tga.header(), 48 | TgaHeader { 49 | has_color_map: true, 50 | data_type: DataType::ColorMapped, 51 | color_map_start: 0, 52 | color_map_len: 8, 53 | color_map_depth: Some(Bpp::Bits24), 54 | ..HEADER_DEFAULT 55 | } 56 | ); 57 | assert_eq!(tga.developer_directory(), None); 58 | assert_eq!(tga.extension_area(), None); 59 | 60 | assert_eq!(tga.color_bpp(), Bpp::Bits24); 61 | assert_eq!(tga.image_data_bpp(), Bpp::Bits8); 62 | } 63 | 64 | #[test] 65 | fn type1_16bpp_tl() { 66 | let tga = RawTga::from_slice(include_bytes!("../tests/type1_16bpp_tl.tga")).unwrap(); 67 | 68 | assert_eq!( 69 | tga.header(), 70 | TgaHeader { 71 | has_color_map: true, 72 | data_type: DataType::ColorMapped, 73 | color_map_start: 0, 74 | color_map_len: 8, 75 | color_map_depth: Some(Bpp::Bits16), 76 | image_origin: ImageOrigin::TopLeft, 77 | ..HEADER_DEFAULT 78 | } 79 | ); 80 | assert_eq!(tga.developer_directory(), None); 81 | assert_eq!(tga.extension_area(), None); 82 | 83 | assert_eq!(tga.color_bpp(), Bpp::Bits16); 84 | assert_eq!(tga.image_data_bpp(), Bpp::Bits8); 85 | } 86 | 87 | #[test] 88 | fn type1_24bpp_tl() { 89 | let tga = RawTga::from_slice(include_bytes!("../tests/type1_24bpp_tl.tga")).unwrap(); 90 | 91 | assert_eq!( 92 | tga.header(), 93 | TgaHeader { 94 | has_color_map: true, 95 | data_type: DataType::ColorMapped, 96 | color_map_start: 0, 97 | color_map_len: 8, 98 | color_map_depth: Some(Bpp::Bits24), 99 | image_origin: ImageOrigin::TopLeft, 100 | ..HEADER_DEFAULT 101 | } 102 | ); 103 | assert_eq!(tga.developer_directory(), None); 104 | assert_eq!(tga.extension_area(), None); 105 | 106 | assert_eq!(tga.color_bpp(), Bpp::Bits24); 107 | assert_eq!(tga.image_data_bpp(), Bpp::Bits8); 108 | } 109 | 110 | #[test] 111 | fn type2_16bpp_bl() { 112 | let tga = RawTga::from_slice(include_bytes!("../tests/type2_16bpp_bl.tga")).unwrap(); 113 | 114 | assert_eq!( 115 | tga.header(), 116 | TgaHeader { 117 | data_type: DataType::TrueColor, 118 | pixel_depth: Bpp::Bits16, 119 | ..HEADER_DEFAULT 120 | } 121 | ); 122 | assert_eq!(tga.developer_directory(), None); 123 | assert_eq!(tga.extension_area(), None); 124 | 125 | assert_eq!(tga.color_bpp(), Bpp::Bits16); 126 | assert_eq!(tga.image_data_bpp(), Bpp::Bits16); 127 | } 128 | 129 | #[test] 130 | fn type2_24bpp_bl() { 131 | let tga = RawTga::from_slice(include_bytes!("../tests/type2_24bpp_bl.tga")).unwrap(); 132 | 133 | assert_eq!( 134 | tga.header(), 135 | TgaHeader { 136 | data_type: DataType::TrueColor, 137 | pixel_depth: Bpp::Bits24, 138 | ..HEADER_DEFAULT 139 | } 140 | ); 141 | assert_eq!(tga.developer_directory(), None); 142 | assert_eq!(tga.extension_area(), None); 143 | 144 | assert_eq!(tga.color_bpp(), Bpp::Bits24); 145 | assert_eq!(tga.image_data_bpp(), Bpp::Bits24); 146 | } 147 | 148 | #[test] 149 | fn type2_16bpp_tl() { 150 | let tga = RawTga::from_slice(include_bytes!("../tests/type2_16bpp_tl.tga")).unwrap(); 151 | 152 | assert_eq!( 153 | tga.header(), 154 | TgaHeader { 155 | data_type: DataType::TrueColor, 156 | pixel_depth: Bpp::Bits16, 157 | image_origin: ImageOrigin::TopLeft, 158 | ..HEADER_DEFAULT 159 | } 160 | ); 161 | assert_eq!(tga.developer_directory(), None); 162 | assert_eq!(tga.extension_area(), None); 163 | 164 | assert_eq!(tga.color_bpp(), Bpp::Bits16); 165 | assert_eq!(tga.image_data_bpp(), Bpp::Bits16); 166 | } 167 | 168 | #[test] 169 | fn type2_24bpp_tl() { 170 | let tga = RawTga::from_slice(include_bytes!("../tests/type2_24bpp_tl.tga")).unwrap(); 171 | 172 | assert_eq!( 173 | tga.header(), 174 | TgaHeader { 175 | data_type: DataType::TrueColor, 176 | pixel_depth: Bpp::Bits24, 177 | image_origin: ImageOrigin::TopLeft, 178 | ..HEADER_DEFAULT 179 | } 180 | ); 181 | assert_eq!(tga.developer_directory(), None); 182 | assert_eq!(tga.extension_area(), None); 183 | 184 | assert_eq!(tga.color_bpp(), Bpp::Bits24); 185 | assert_eq!(tga.image_data_bpp(), Bpp::Bits24); 186 | } 187 | 188 | #[test] 189 | fn type3_bl() { 190 | let tga = RawTga::from_slice(include_bytes!("../tests/type3_bl.tga")).unwrap(); 191 | 192 | assert_eq!( 193 | tga.header(), 194 | TgaHeader { 195 | data_type: DataType::BlackAndWhite, 196 | ..HEADER_DEFAULT 197 | } 198 | ); 199 | assert_eq!(tga.developer_directory(), None); 200 | assert_eq!(tga.extension_area(), None); 201 | 202 | assert_eq!(tga.color_bpp(), Bpp::Bits8); 203 | assert_eq!(tga.image_data_bpp(), Bpp::Bits8); 204 | } 205 | 206 | #[test] 207 | fn type3_tl() { 208 | let tga = RawTga::from_slice(include_bytes!("../tests/type3_tl.tga")).unwrap(); 209 | 210 | assert_eq!( 211 | tga.header(), 212 | TgaHeader { 213 | data_type: DataType::BlackAndWhite, 214 | image_origin: ImageOrigin::TopLeft, 215 | ..HEADER_DEFAULT 216 | } 217 | ); 218 | assert_eq!(tga.developer_directory(), None); 219 | assert_eq!(tga.extension_area(), None); 220 | 221 | assert_eq!(tga.color_bpp(), Bpp::Bits8); 222 | assert_eq!(tga.image_data_bpp(), Bpp::Bits8); 223 | } 224 | 225 | #[test] 226 | fn type9_16bpp() { 227 | let tga = RawTga::from_slice(include_bytes!("../tests/type9_16bpp_bl.tga")).unwrap(); 228 | 229 | assert_eq!( 230 | tga.header(), 231 | TgaHeader { 232 | has_color_map: true, 233 | data_type: DataType::ColorMapped, 234 | compression: Compression::Rle, 235 | color_map_start: 0, 236 | color_map_len: 8, 237 | color_map_depth: Some(Bpp::Bits16), 238 | ..HEADER_DEFAULT 239 | } 240 | ); 241 | assert_eq!(tga.developer_directory(), None); 242 | assert_eq!(tga.extension_area(), None); 243 | 244 | assert_eq!(tga.color_bpp(), Bpp::Bits16); 245 | assert_eq!(tga.image_data_bpp(), Bpp::Bits8); 246 | } 247 | 248 | #[test] 249 | fn type9_24bpp_bl() { 250 | let tga = RawTga::from_slice(include_bytes!("../tests/type9_24bpp_bl.tga")).unwrap(); 251 | 252 | assert_eq!( 253 | tga.header(), 254 | TgaHeader { 255 | has_color_map: true, 256 | data_type: DataType::ColorMapped, 257 | compression: Compression::Rle, 258 | color_map_start: 0, 259 | color_map_len: 8, 260 | color_map_depth: Some(Bpp::Bits24), 261 | ..HEADER_DEFAULT 262 | } 263 | ); 264 | assert_eq!(tga.developer_directory(), None); 265 | assert_eq!(tga.extension_area(), None); 266 | 267 | assert_eq!(tga.color_bpp(), Bpp::Bits24); 268 | assert_eq!(tga.image_data_bpp(), Bpp::Bits8); 269 | } 270 | 271 | #[test] 272 | fn type9_16bpp_tl() { 273 | let tga = RawTga::from_slice(include_bytes!("../tests/type9_16bpp_tl.tga")).unwrap(); 274 | 275 | assert_eq!( 276 | tga.header(), 277 | TgaHeader { 278 | has_color_map: true, 279 | data_type: DataType::ColorMapped, 280 | compression: Compression::Rle, 281 | color_map_start: 0, 282 | color_map_len: 8, 283 | color_map_depth: Some(Bpp::Bits16), 284 | image_origin: ImageOrigin::TopLeft, 285 | ..HEADER_DEFAULT 286 | } 287 | ); 288 | assert_eq!(tga.developer_directory(), None); 289 | assert_eq!(tga.extension_area(), None); 290 | 291 | assert_eq!(tga.color_bpp(), Bpp::Bits16); 292 | assert_eq!(tga.image_data_bpp(), Bpp::Bits8); 293 | } 294 | 295 | #[test] 296 | fn type9_24bpp_tl() { 297 | let tga = RawTga::from_slice(include_bytes!("../tests/type9_24bpp_tl.tga")).unwrap(); 298 | 299 | assert_eq!( 300 | tga.header(), 301 | TgaHeader { 302 | has_color_map: true, 303 | data_type: DataType::ColorMapped, 304 | compression: Compression::Rle, 305 | color_map_start: 0, 306 | color_map_len: 8, 307 | color_map_depth: Some(Bpp::Bits24), 308 | image_origin: ImageOrigin::TopLeft, 309 | ..HEADER_DEFAULT 310 | } 311 | ); 312 | assert_eq!(tga.developer_directory(), None); 313 | assert_eq!(tga.extension_area(), None); 314 | 315 | assert_eq!(tga.color_bpp(), Bpp::Bits24); 316 | assert_eq!(tga.image_data_bpp(), Bpp::Bits8); 317 | } 318 | 319 | #[test] 320 | fn type10_16bpp_bl() { 321 | let tga = RawTga::from_slice(include_bytes!("../tests/type10_16bpp_bl.tga")).unwrap(); 322 | 323 | assert_eq!( 324 | tga.header(), 325 | TgaHeader { 326 | data_type: DataType::TrueColor, 327 | compression: Compression::Rle, 328 | pixel_depth: Bpp::Bits16, 329 | ..HEADER_DEFAULT 330 | } 331 | ); 332 | assert_eq!(tga.developer_directory(), None); 333 | assert_eq!(tga.extension_area(), None); 334 | 335 | assert_eq!(tga.color_bpp(), Bpp::Bits16); 336 | assert_eq!(tga.image_data_bpp(), Bpp::Bits16); 337 | } 338 | 339 | #[test] 340 | fn type10_24bpp_bl() { 341 | let tga = RawTga::from_slice(include_bytes!("../tests/type10_24bpp_bl.tga")).unwrap(); 342 | 343 | assert_eq!( 344 | tga.header(), 345 | TgaHeader { 346 | data_type: DataType::TrueColor, 347 | compression: Compression::Rle, 348 | pixel_depth: Bpp::Bits24, 349 | ..HEADER_DEFAULT 350 | } 351 | ); 352 | assert_eq!(tga.developer_directory(), None); 353 | assert_eq!(tga.extension_area(), None); 354 | 355 | assert_eq!(tga.color_bpp(), Bpp::Bits24); 356 | assert_eq!(tga.image_data_bpp(), Bpp::Bits24); 357 | } 358 | 359 | #[test] 360 | fn type10_16bpp_tl() { 361 | let tga = RawTga::from_slice(include_bytes!("../tests/type10_16bpp_tl.tga")).unwrap(); 362 | 363 | assert_eq!( 364 | tga.header(), 365 | TgaHeader { 366 | data_type: DataType::TrueColor, 367 | compression: Compression::Rle, 368 | pixel_depth: Bpp::Bits16, 369 | image_origin: ImageOrigin::TopLeft, 370 | ..HEADER_DEFAULT 371 | } 372 | ); 373 | assert_eq!(tga.developer_directory(), None); 374 | assert_eq!(tga.extension_area(), None); 375 | 376 | assert_eq!(tga.color_bpp(), Bpp::Bits16); 377 | assert_eq!(tga.image_data_bpp(), Bpp::Bits16); 378 | } 379 | 380 | #[test] 381 | fn type10_24bpp_tl() { 382 | let tga = RawTga::from_slice(include_bytes!("../tests/type10_24bpp_tl.tga")).unwrap(); 383 | 384 | assert_eq!( 385 | tga.header(), 386 | TgaHeader { 387 | data_type: DataType::TrueColor, 388 | compression: Compression::Rle, 389 | pixel_depth: Bpp::Bits24, 390 | image_origin: ImageOrigin::TopLeft, 391 | ..HEADER_DEFAULT 392 | } 393 | ); 394 | assert_eq!(tga.developer_directory(), None); 395 | assert_eq!(tga.extension_area(), None); 396 | 397 | assert_eq!(tga.color_bpp(), Bpp::Bits24); 398 | assert_eq!(tga.image_data_bpp(), Bpp::Bits24); 399 | } 400 | 401 | #[test] 402 | fn type11_bl() { 403 | let tga = RawTga::from_slice(include_bytes!("../tests/type11_bl.tga")).unwrap(); 404 | 405 | assert_eq!( 406 | tga.header(), 407 | TgaHeader { 408 | data_type: DataType::BlackAndWhite, 409 | compression: Compression::Rle, 410 | ..HEADER_DEFAULT 411 | } 412 | ); 413 | assert_eq!(tga.developer_directory(), None); 414 | assert_eq!(tga.extension_area(), None); 415 | 416 | assert_eq!(tga.color_bpp(), Bpp::Bits8); 417 | assert_eq!(tga.image_data_bpp(), Bpp::Bits8); 418 | } 419 | 420 | #[test] 421 | fn type11_tl() { 422 | let tga = RawTga::from_slice(include_bytes!("../tests/type11_tl.tga")).unwrap(); 423 | 424 | assert_eq!( 425 | tga.header(), 426 | TgaHeader { 427 | data_type: DataType::BlackAndWhite, 428 | compression: Compression::Rle, 429 | image_origin: ImageOrigin::TopLeft, 430 | ..HEADER_DEFAULT 431 | } 432 | ); 433 | assert_eq!(tga.developer_directory(), None); 434 | assert_eq!(tga.extension_area(), None); 435 | 436 | assert_eq!(tga.color_bpp(), Bpp::Bits8); 437 | assert_eq!(tga.image_data_bpp(), Bpp::Bits8); 438 | } 439 | -------------------------------------------------------------------------------- /tests/ubw8.rs: -------------------------------------------------------------------------------- 1 | use tinytga::{Bpp, Compression, DataType, ImageOrigin, RawTga, TgaHeader}; 2 | 3 | #[test] 4 | fn ubw8() { 5 | let data = include_bytes!("./ubw8.tga"); 6 | 7 | let img = RawTga::from_slice(data).unwrap(); 8 | 9 | println!("{:#?}", img.header()); 10 | println!("Raw image data len {:#?}", img.image_data().len()); 11 | 12 | assert_eq!( 13 | img.header(), 14 | TgaHeader { 15 | id_len: 26, 16 | has_color_map: false, 17 | data_type: DataType::BlackAndWhite, 18 | compression: Compression::Uncompressed, 19 | color_map_start: 0, 20 | color_map_len: 0, 21 | color_map_depth: None, 22 | x_origin: 0, 23 | y_origin: 0, 24 | width: 128, 25 | height: 128, 26 | pixel_depth: Bpp::Bits8, 27 | image_origin: ImageOrigin::BottomLeft, 28 | alpha_channel_depth: 0 29 | } 30 | ); 31 | 32 | const TGA_FOOTER_LENGTH: usize = 26; 33 | assert_eq!( 34 | img.extension_area(), 35 | Some(&data[20526..data.len() - TGA_FOOTER_LENGTH]) 36 | ); 37 | assert_eq!(img.developer_directory(), None); 38 | 39 | let pixels: Vec<_> = img.pixels().collect(); 40 | 41 | assert_eq!(pixels.len(), 128 * 128); 42 | } 43 | -------------------------------------------------------------------------------- /tests/ubw8.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedded-graphics/tinytga/31908762b598e0a5feb6544474276ac80c7e9bef/tests/ubw8.tga --------------------------------------------------------------------------------