├── .github └── workflows │ └── rust.yml ├── .gitignore ├── COPYRIGHT ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── assets └── lake.jpg ├── examples ├── animation.rs ├── animation_decode.rs ├── convert.rs └── convert_by_args.rs └── src ├── animation_decoder.rs ├── animation_encoder.rs ├── decoder.rs ├── encoder.rs ├── lib.rs └── shared.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Build 20 | run: cargo build --verbose 21 | - name: Run tests 22 | run: cargo test --verbose 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | .idea 5 | main.rs 6 | -------------------------------------------------------------------------------- /COPYRIGHT: -------------------------------------------------------------------------------- 1 | Copyright 2020 Jared Forth. 2 | 3 | Except as otherwise noted (below and/or in individual files), fsutils is 4 | licensed under the Apache License, Version 2.0 or 5 | or the MIT license 6 | or , at your option. -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "webp" 3 | version = "0.3.0" 4 | authors = ["Jared Forth "] 5 | edition = "2018" 6 | 7 | description = "WebP conversion library." 8 | 9 | homepage = "https://github.com/jaredforth/webp" 10 | repository = "https://github.com/jaredforth/webp.git" 11 | license = "MIT OR Apache-2.0" 12 | readme = "README.md" 13 | documentation = "https://docs.rs/webp" 14 | keywords = ["image", "webp", "conversion"] 15 | categories = ["external-ffi-bindings"] 16 | 17 | [dependencies] 18 | libwebp-sys = "0.9.3" 19 | image = { version = "^0.25.0", default-features = false, optional = true } 20 | 21 | [features] 22 | default = ["img"] 23 | img = [ "image" ] 24 | 25 | [dev-dependencies] 26 | image = "0.25" 27 | -------------------------------------------------------------------------------- /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 | MIT License 2 | 3 | Copyright (c) 2020 Jared Forth 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Build Status](https://github.com/jaredforth/webp/actions/workflows/rust.yml/badge.svg) 2 | [![Crate](https://img.shields.io/crates/v/webp.svg)](https://crates.io/crates/webp) 3 | [![API](https://docs.rs/webp/badge.svg)](https://docs.rs/webp) 4 | ![Crates.io](https://img.shields.io/crates/d/webp) 5 | 6 | # webp 7 | 8 | A WebP conversion library 9 | 10 | Documentation: 11 | - [API Reference](https://docs.rs/webp) 12 | 13 | 14 | ## Usage 15 | 16 | Add this to your `Cargo.toml`: 17 | 18 | ```toml 19 | [dependencies] 20 | webp = "0.2" 21 | ``` 22 | 23 | ## Examples 24 | 25 | An example for converting an image between JPEG and WebP formats is provided in the 26 | `examples` directory. It can be run using 27 | ```sh 28 | cargo run --release --example convert 29 | ``` 30 | 31 | ## License 32 | 33 | **webp** is distributed under the terms of both the MIT license and the 34 | Apache License (Version 2.0). 35 | 36 | See [LICENSE-APACHE](LICENSE-APACHE) and [LICENSE-MIT](LICENSE-MIT), and 37 | [COPYRIGHT](COPYRIGHT) for details. 38 | 39 | The photo `lake.jpg` included in the `assets/` directory is licensed under 40 | [CC0](https://creativecommons.org/publicdomain/zero/1.0/)/"Public Domain Dedication". 41 | -------------------------------------------------------------------------------- /assets/lake.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredforth/webp/378cb1067657c4d58736b35f075e97c77bd27ac1/assets/lake.jpg -------------------------------------------------------------------------------- /examples/animation.rs: -------------------------------------------------------------------------------- 1 | use webp::AnimEncoder; 2 | use webp::AnimFrame; 3 | use webp::WebPConfig; 4 | fn main() { 5 | let width = 32u32; 6 | let height = 32u32; 7 | fn dumy_image(width: u32, height: u32, color: [u8; 4]) -> Vec { 8 | let mut pixels = Vec::with_capacity(width as usize * height as usize * 4); 9 | for _ in 0..(width * height) { 10 | pixels.push(color[0]); //red 11 | pixels.push(color[1]); //green 12 | pixels.push(color[2]); //blue 13 | pixels.push(color[3]); //alpha 14 | } 15 | pixels 16 | } 17 | let mut config = WebPConfig::new().unwrap(); 18 | config.lossless = 1; 19 | config.alpha_compression = 0; 20 | config.quality = 75f32; 21 | let mut encoder = AnimEncoder::new(width as u32, height as u32, &config); 22 | encoder.set_bgcolor([255, 0, 0, 255]); 23 | encoder.set_loop_count(3); 24 | let mut time_ms = 1000; 25 | 26 | let v = dumy_image(width, height, [255, 0, 0, 255]); 27 | encoder.add_frame(AnimFrame::from_rgba(&v, width, height, time_ms)); 28 | time_ms += 750; 29 | 30 | let v = dumy_image(width, height, [0, 255, 0, 255]); 31 | encoder.add_frame(AnimFrame::from_rgba(&v, width, height, time_ms)); 32 | time_ms += 500; 33 | 34 | let v = dumy_image(width, height, [0, 0, 255, 255]); 35 | encoder.add_frame(AnimFrame::from_rgba(&v, width, height, time_ms)); 36 | time_ms += 250; 37 | 38 | let v = dumy_image(width, height, [0, 0, 0, 0]); 39 | encoder.add_frame(AnimFrame::from_rgba(&v, width, height, time_ms)); 40 | 41 | let webp = encoder.encode(); 42 | let output_path = std::path::Path::new("assets") 43 | .join("dumy_anim") 44 | .with_extension("webp"); 45 | std::fs::write(&output_path, &*webp).unwrap(); 46 | } 47 | -------------------------------------------------------------------------------- /examples/animation_decode.rs: -------------------------------------------------------------------------------- 1 | /* 2 | This is free and unencumbered software released into the public domain. 3 | 4 | Anyone is free to copy, modify, publish, use, compile, sell, or 5 | distribute this software, either in source code form or as a compiled 6 | binary, for any purpose, commercial or non-commercial, and by any 7 | means. 8 | 9 | In jurisdictions that recognize copyright laws, the author or authors 10 | of this software dedicate any and all copyright interest in the 11 | software to the public domain. We make this dedication for the benefit 12 | of the public at large and to the detriment of our heirs and 13 | successors. We intend this dedication to be an overt act of 14 | relinquishment in perpetuity of all present and future rights to this 15 | software under copyright law. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 21 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | For more information, please refer to 26 | */ 27 | 28 | use webp::AnimDecoder; 29 | use webp::Encoder; 30 | //animation webp to webp demo 31 | fn main() { 32 | let src = "dumy_anim"; 33 | let input = std::path::Path::new("assets") 34 | .join(src) 35 | .with_extension("webp"); 36 | let webp = std::fs::read(input).unwrap(); 37 | match AnimDecoder::new(&webp).decode() { 38 | Ok(frames) => { 39 | let mut file_number = 0; 40 | println!("has_animation {}", frames.has_animation()); 41 | println!("loop_count {}", frames.loop_count); 42 | println!("bg_color {}", frames.bg_color); 43 | let mut last_ms = 0; 44 | for f in frames.into_iter() { 45 | let delay_ms = f.get_time_ms() - last_ms; 46 | println!( 47 | "{}x{} {:?} time{}ms delay{}ms", 48 | f.width(), 49 | f.height(), 50 | f.get_layout(), 51 | f.get_time_ms(), 52 | delay_ms 53 | ); 54 | last_ms += delay_ms; 55 | let webp = Encoder::from(&f).encode_simple(true, 100f32); 56 | let output = std::path::Path::new("assets") 57 | .join(format!("{}{}", src, file_number)) 58 | .with_extension("webp"); 59 | file_number += 1; 60 | std::fs::write(&output, &*webp.unwrap()).unwrap(); 61 | } 62 | } 63 | Err(mes) => { 64 | println!("{}", mes); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /examples/convert.rs: -------------------------------------------------------------------------------- 1 | use image::*; 2 | use std::path::Path; 3 | use webp::*; 4 | 5 | fn main() { 6 | // Using `image` crate, open the included .jpg file 7 | let img = image::open("assets/lake.jpg").unwrap(); 8 | let (w, h) = img.dimensions(); 9 | // Optionally, resize the existing photo and convert back into DynamicImage 10 | let size_factor = 1.0; 11 | let img: DynamicImage = image::DynamicImage::ImageRgba8(imageops::resize( 12 | &img, 13 | (w as f64 * size_factor) as u32, 14 | (h as f64 * size_factor) as u32, 15 | imageops::FilterType::Triangle, 16 | )); 17 | 18 | // Create the WebP encoder for the above image 19 | let encoder: Encoder = Encoder::from_image(&img).unwrap(); 20 | // Encode the image at a specified quality 0-100 21 | let webp: WebPMemory = encoder.encode(90f32); 22 | // Define and write the WebP-encoded file to a given path 23 | let output_path = Path::new("assets").join("lake").with_extension("webp"); 24 | std::fs::write(&output_path, &*webp).unwrap(); 25 | } 26 | -------------------------------------------------------------------------------- /examples/convert_by_args.rs: -------------------------------------------------------------------------------- 1 | use image::*; 2 | use std::{env::args, fmt::format, path::Path}; 3 | use webp::*; 4 | 5 | 6 | /// cargo run --example convert_by_args lake.jpg. 7 | fn main() { 8 | 9 | //Add a get args functions 10 | let arg: Vec = args().collect(); 11 | if arg.len() != 2 { 12 | eprintln!("Usage: command "); 13 | return; 14 | } 15 | 16 | //Add a format functions for a new Path by String 17 | let path = format(format_args!("assets/{}", arg[1])); 18 | let path = Path::new(&path); 19 | 20 | 21 | // Using `image` crate, open the included .jpg file 22 | let img = image::open(path).unwrap(); 23 | let (w, h) = img.dimensions(); 24 | // Optionally, resize the existing photo and convert back into DynamicImage 25 | let size_factor = 1.0; 26 | let img: DynamicImage = image::DynamicImage::ImageRgba8(imageops::resize( 27 | &img, 28 | (w as f64 * size_factor) as u32, 29 | (h as f64 * size_factor) as u32, 30 | imageops::FilterType::Triangle, 31 | )); 32 | 33 | // Create the WebP encoder for the above image 34 | let encoder: Encoder = Encoder::from_image(&img).unwrap(); 35 | // Encode the image at a specified quality 0-100 36 | let webp: WebPMemory = encoder.encode(90f32); 37 | // Define and write the WebP-encoded file to a given path 38 | let output_path = Path::new("assets").join("lake").with_extension("webp"); 39 | std::fs::write(&output_path, &*webp).unwrap(); 40 | } 41 | 42 | #[test] 43 | fn test_convert() { 44 | let path = format(format_args!("assets/{}", "lake.jpg")); 45 | let path = Path::new(&path); 46 | // Using `image` crate, open the included .jpg file 47 | let img = image::open(path).unwrap(); 48 | let (w, h) = img.dimensions(); 49 | // Optionally, resize the existing photo and convert back into DynamicImage 50 | let size_factor = 1.0; 51 | let img: DynamicImage = image::DynamicImage::ImageRgba8(imageops::resize( 52 | &img, 53 | (w as f64 * size_factor) as u32, 54 | (h as f64 * size_factor) as u32, 55 | imageops::FilterType::Triangle, 56 | )); 57 | 58 | // Create the WebP encoder for the above image 59 | let encoder: Encoder = Encoder::from_image(&img).unwrap(); 60 | // Encode the image at a specified quality 0-100 61 | let webp: WebPMemory = encoder.encode(90f32); 62 | // Define and write the WebP-encoded file to a given path 63 | let output_path = Path::new("assets").join("lake").with_extension("webp"); 64 | std::fs::write(&output_path, &*webp).unwrap(); 65 | } -------------------------------------------------------------------------------- /src/animation_decoder.rs: -------------------------------------------------------------------------------- 1 | use libwebp_sys::*; 2 | 3 | use crate::shared::PixelLayout; 4 | use crate::AnimFrame; 5 | 6 | pub struct AnimDecoder<'a> { 7 | data: &'a [u8], 8 | } 9 | impl<'a> AnimDecoder<'a> { 10 | pub fn new(data: &'a [u8]) -> Self { 11 | Self { data } 12 | } 13 | pub fn decode(&self) -> Result { 14 | unsafe { self.decode_internal(true) } 15 | } 16 | unsafe fn decode_internal(&self, mut has_alpha: bool) -> Result { 17 | let mut dec_options: WebPAnimDecoderOptions = std::mem::zeroed(); 18 | dec_options.color_mode = if has_alpha { 19 | WEBP_CSP_MODE::MODE_RGBA 20 | } else { 21 | WEBP_CSP_MODE::MODE_RGB 22 | }; 23 | let ok = WebPAnimDecoderOptionsInitInternal(&mut dec_options, WebPGetDemuxABIVersion()); 24 | if ok == 0 { 25 | return Err(String::from("option init error")); 26 | } 27 | match dec_options.color_mode { 28 | WEBP_CSP_MODE::MODE_RGBA | WEBP_CSP_MODE::MODE_RGB => {} 29 | _ => return Err(String::from("unsupport color mode")), 30 | } 31 | has_alpha = dec_options.color_mode == WEBP_CSP_MODE::MODE_RGBA; 32 | let webp_data = WebPData { 33 | bytes: self.data.as_ptr(), 34 | size: self.data.len(), 35 | }; 36 | let dec = WebPAnimDecoderNewInternal(&webp_data, &dec_options, WebPGetDemuxABIVersion()); 37 | if dec.is_null() { 38 | return Err(String::from("null_decoder")); 39 | } 40 | let mut anim_info: WebPAnimInfo = std::mem::zeroed(); 41 | let ok = WebPAnimDecoderGetInfo(dec, &mut anim_info); 42 | if ok == 0 { 43 | return Err(String::from("null info")); 44 | } 45 | let width = anim_info.canvas_width; 46 | let height = anim_info.canvas_height; 47 | let mut list: Vec = vec![]; 48 | while WebPAnimDecoderHasMoreFrames(dec) > 0 { 49 | let mut buf: *mut u8 = std::ptr::null_mut(); 50 | let mut timestamp: std::os::raw::c_int = 0; 51 | let ok = WebPAnimDecoderGetNext(dec, &mut buf, &mut timestamp); 52 | if ok != 0 { 53 | let len = (if has_alpha { 4 } else { 3 } * width * height) as usize; 54 | let mut img = Vec::with_capacity(len); 55 | img.set_len(len); 56 | buf.copy_to(img.as_mut_ptr(), len); 57 | let layout = if has_alpha { 58 | PixelLayout::Rgba 59 | } else { 60 | PixelLayout::Rgb 61 | }; 62 | let frame = DecodeAnimFrame { 63 | img, 64 | width, 65 | height, 66 | layout, 67 | timestamp, 68 | }; 69 | list.push(frame); 70 | } 71 | } 72 | WebPAnimDecoderReset(dec); 73 | //let demuxer:WebPDemuxer=WebPAnimDecoderGetDemuxer(dec); 74 | // ... (Do something using 'demuxer'; e.g. get EXIF/XMP/ICC data). 75 | WebPAnimDecoderDelete(dec); 76 | let mut anim = DecodeAnimImage::from(list); 77 | anim.loop_count = anim_info.loop_count; 78 | anim.bg_color = anim_info.bgcolor; 79 | Ok(anim) 80 | } 81 | } 82 | struct DecodeAnimFrame { 83 | img: Vec, 84 | width: u32, 85 | height: u32, 86 | layout: PixelLayout, 87 | timestamp: i32, 88 | } 89 | pub struct DecodeAnimImage { 90 | frames: Vec, 91 | pub loop_count: u32, 92 | pub bg_color: u32, 93 | } 94 | impl From> for DecodeAnimImage { 95 | fn from(frames: Vec) -> Self { 96 | DecodeAnimImage { 97 | frames, 98 | loop_count: 0, 99 | bg_color: 0, 100 | } 101 | } 102 | } 103 | impl DecodeAnimImage { 104 | #[inline] 105 | pub fn get_frame(&self, index: usize) -> Option { 106 | let f = self.frames.get(index)?; 107 | Some(AnimFrame::new( 108 | &f.img, 109 | f.layout, 110 | f.width, 111 | f.height, 112 | f.timestamp, 113 | None, 114 | )) 115 | } 116 | #[inline] 117 | pub fn get_frames(&self, index: core::ops::Range) -> Option> { 118 | let dec_frames = self.frames.get(index)?; 119 | let mut frames = Vec::with_capacity(dec_frames.len()); 120 | for f in dec_frames { 121 | frames.push(AnimFrame::new( 122 | &f.img, 123 | f.layout, 124 | f.width, 125 | f.height, 126 | f.timestamp, 127 | None, 128 | )); 129 | } 130 | Some(frames) 131 | } 132 | pub fn len(&self) -> usize { 133 | self.frames.len() 134 | } 135 | pub fn has_animation(&self) -> bool { 136 | self.len() > 1 137 | } 138 | pub fn sort_by_time_stamp(&mut self) { 139 | self.frames.sort_by(|a, b| a.timestamp.cmp(&b.timestamp)); 140 | } 141 | } 142 | impl<'a> IntoIterator for &'a DecodeAnimImage { 143 | type Item = AnimFrame<'a>; 144 | type IntoIter = std::vec::IntoIter; 145 | 146 | fn into_iter(self) -> Self::IntoIter { 147 | let fs = self.get_frames(0..self.frames.len()); 148 | if let Some(v) = fs { 149 | v.into_iter() 150 | } else { 151 | vec![].into_iter() 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/animation_encoder.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::CString; 2 | 3 | #[cfg(feature = "img")] 4 | use image::DynamicImage; 5 | use libwebp_sys::*; 6 | 7 | #[cfg(feature = "img")] 8 | use image::*; 9 | 10 | use crate::{shared::*, Encoder}; 11 | 12 | pub struct AnimFrame<'a> { 13 | image: &'a [u8], 14 | layout: PixelLayout, 15 | width: u32, 16 | height: u32, 17 | timestamp: i32, 18 | config: Option<&'a WebPConfig>, 19 | } 20 | impl<'a> AnimFrame<'a> { 21 | pub fn new( 22 | image: &'a [u8], 23 | layout: PixelLayout, 24 | width: u32, 25 | height: u32, 26 | timestamp: i32, 27 | config: Option<&'a WebPConfig>, 28 | ) -> Self { 29 | Self { 30 | image, 31 | layout, 32 | width, 33 | height, 34 | timestamp, 35 | config, 36 | } 37 | } 38 | #[cfg(feature = "img")] 39 | pub fn from_image(image: &'a DynamicImage, timestamp: i32) -> Result { 40 | match image { 41 | DynamicImage::ImageLuma8(_) => Err("Unimplemented"), 42 | DynamicImage::ImageLumaA8(_) => Err("Unimplemented"), 43 | DynamicImage::ImageRgb8(image) => Ok(Self::from_rgb( 44 | image.as_ref(), 45 | image.width(), 46 | image.height(), 47 | timestamp, 48 | )), 49 | DynamicImage::ImageRgba8(image) => Ok(Self::from_rgba( 50 | image.as_ref(), 51 | image.width(), 52 | image.height(), 53 | timestamp, 54 | )), 55 | _ => Err("Unimplemented"), 56 | } 57 | } 58 | /// Creates a new encoder from the given image data in the RGB pixel layout. 59 | pub fn from_rgb(image: &'a [u8], width: u32, height: u32, timestamp: i32) -> Self { 60 | Self::new(image, PixelLayout::Rgb, width, height, timestamp, None) 61 | } 62 | /// Creates a new encoder from the given image data in the RGBA pixel layout. 63 | pub fn from_rgba(image: &'a [u8], width: u32, height: u32, timestamp: i32) -> Self { 64 | Self::new(image, PixelLayout::Rgba, width, height, timestamp, None) 65 | } 66 | pub fn get_image(&self) -> &[u8] { 67 | &self.image 68 | } 69 | pub fn get_layout(&self) -> PixelLayout { 70 | self.layout 71 | } 72 | pub fn get_time_ms(&self) -> i32 { 73 | self.timestamp 74 | } 75 | pub fn width(&self) -> u32 { 76 | self.width 77 | } 78 | pub fn height(&self) -> u32 { 79 | self.height 80 | } 81 | } 82 | impl<'a> From<&'a AnimFrame<'a>> for Encoder<'a> { 83 | fn from(f: &'a AnimFrame) -> Self { 84 | Encoder::new(f.get_image(), f.layout, f.width, f.height) 85 | } 86 | } 87 | #[cfg(feature = "img")] 88 | impl Into for &AnimFrame<'_> { 89 | fn into(self) -> DynamicImage { 90 | if self.layout.is_alpha() { 91 | let image = 92 | ImageBuffer::from_raw(self.width(), self.height(), self.get_image().to_owned()) 93 | .expect("ImageBuffer couldn't be created"); 94 | DynamicImage::ImageRgba8(image) 95 | } else { 96 | let image = 97 | ImageBuffer::from_raw(self.width(), self.height(), self.get_image().to_owned()) 98 | .expect("ImageBuffer couldn't be created"); 99 | DynamicImage::ImageRgb8(image) 100 | } 101 | } 102 | } 103 | pub struct AnimEncoder<'a> { 104 | frames: Vec>, 105 | width: u32, 106 | height: u32, 107 | config: &'a WebPConfig, 108 | muxparams: WebPMuxAnimParams, 109 | } 110 | impl<'a> AnimEncoder<'a> { 111 | pub fn new(width: u32, height: u32, config: &'a WebPConfig) -> Self { 112 | Self { 113 | frames: vec![], 114 | width, 115 | height, 116 | config, 117 | muxparams: WebPMuxAnimParams { 118 | bgcolor: 0, 119 | loop_count: 0, 120 | }, 121 | } 122 | } 123 | pub fn set_bgcolor(&mut self, rgba: [u8; 4]) { 124 | let bgcolor = (u32::from(rgba[3]) << 24) 125 | + (u32::from(rgba[2]) << 16) 126 | + (u32::from(rgba[1]) << 8) 127 | + (u32::from(rgba[0])); 128 | self.muxparams.bgcolor = bgcolor; 129 | } 130 | pub fn set_loop_count(&mut self, loop_count: i32) { 131 | self.muxparams.loop_count = loop_count; 132 | } 133 | pub fn add_frame(&mut self, frame: AnimFrame<'a>) { 134 | self.frames.push(frame); 135 | } 136 | pub fn encode(&self) -> WebPMemory { 137 | self.try_encode().unwrap() 138 | } 139 | pub fn try_encode(&self) -> Result { 140 | unsafe { anim_encode(&self) } 141 | } 142 | } 143 | 144 | #[derive(Debug)] 145 | pub enum AnimEncodeError { 146 | WebPEncodingError(WebPEncodingError), 147 | WebPMuxError(WebPMuxError), 148 | WebPAnimEncoderGetError(String), 149 | } 150 | unsafe fn anim_encode(all_frame: &AnimEncoder) -> Result { 151 | let width = all_frame.width; 152 | let height = all_frame.height; 153 | let mut uninit = std::mem::MaybeUninit::::uninit(); 154 | 155 | let mux_abi_version = WebPGetMuxABIVersion(); 156 | WebPAnimEncoderOptionsInitInternal(uninit.as_mut_ptr(), mux_abi_version); 157 | let encoder = WebPAnimEncoderNewInternal( 158 | width as i32, 159 | height as i32, 160 | uninit.as_ptr(), 161 | mux_abi_version, 162 | ); 163 | let mut frame_pictures = vec![]; 164 | for frame in all_frame.frames.iter() { 165 | let mut pic = crate::new_picture(frame.image, frame.layout, width, height); 166 | let config = frame.config.unwrap_or(all_frame.config); 167 | let ok = WebPAnimEncoderAdd( 168 | encoder, 169 | &mut *pic as *mut _, 170 | frame.timestamp as std::os::raw::c_int, 171 | config, 172 | ); 173 | if ok == 0 { 174 | //ok == false 175 | WebPAnimEncoderDelete(encoder); 176 | return Err(AnimEncodeError::WebPEncodingError(pic.error_code)); 177 | } 178 | frame_pictures.push(pic); 179 | } 180 | WebPAnimEncoderAdd(encoder, std::ptr::null_mut(), 0, std::ptr::null()); 181 | 182 | let mut webp_data = std::mem::MaybeUninit::::uninit(); 183 | let ok = WebPAnimEncoderAssemble(encoder, webp_data.as_mut_ptr()); 184 | if ok == 0 { 185 | //ok == false 186 | let cstring = WebPAnimEncoderGetError(encoder); 187 | let cstring = CString::from_raw(cstring as *mut _); 188 | let string = cstring.to_string_lossy().to_string(); 189 | WebPAnimEncoderDelete(encoder); 190 | return Err(AnimEncodeError::WebPAnimEncoderGetError(string)); 191 | } 192 | WebPAnimEncoderDelete(encoder); 193 | let mux = WebPMuxCreateInternal(webp_data.as_ptr(), 1, mux_abi_version); 194 | let mux_error = WebPMuxSetAnimationParams(mux, &all_frame.muxparams); 195 | if mux_error != WebPMuxError::WEBP_MUX_OK { 196 | return Err(AnimEncodeError::WebPMuxError(mux_error)); 197 | } 198 | let mut raw_data: WebPData = webp_data.assume_init(); 199 | WebPDataClear(&mut raw_data); 200 | let mut webp_data = std::mem::MaybeUninit::::uninit(); 201 | WebPMuxAssemble(mux, webp_data.as_mut_ptr()); 202 | WebPMuxDelete(mux); 203 | let raw_data: WebPData = webp_data.assume_init(); 204 | Ok(WebPMemory(raw_data.bytes as *mut u8, raw_data.size)) 205 | } 206 | -------------------------------------------------------------------------------- /src/decoder.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Debug, Error, Formatter}; 2 | 3 | use libwebp_sys::*; 4 | 5 | use crate::shared::{PixelLayout, WebPImage, WebPMemory}; 6 | 7 | /// A decoder for WebP images. It uses the default configuration of libwebp. 8 | /// Currently, animated images are not supported. 9 | pub struct Decoder<'a> { 10 | data: &'a [u8], 11 | } 12 | 13 | impl<'a> Decoder<'a> { 14 | /// Creates a new decoder from the given image data. 15 | pub fn new(data: &'a [u8]) -> Self { 16 | Self { data } 17 | } 18 | 19 | /// Decodes the image data. If the image contains a valid WebP image, a [WebPImage](../shared/struct.WebPImage.html) is returned. 20 | pub fn decode(&self) -> Option { 21 | let features = BitstreamFeatures::new(self.data)?; 22 | 23 | if features.has_animation() { 24 | return None; 25 | } 26 | 27 | let width = features.width(); 28 | let height = features.height(); 29 | let pixel_count = width * height; 30 | 31 | let image_ptr = unsafe { 32 | let mut width = width as i32; 33 | let mut height = height as i32; 34 | 35 | if features.has_alpha() { 36 | WebPDecodeRGBA( 37 | self.data.as_ptr(), 38 | self.data.len(), 39 | &mut width as *mut _, 40 | &mut height as *mut _, 41 | ) 42 | } else { 43 | WebPDecodeRGB( 44 | self.data.as_ptr(), 45 | self.data.len(), 46 | &mut width as *mut _, 47 | &mut height as *mut _, 48 | ) 49 | } 50 | }; 51 | 52 | if image_ptr.is_null() { 53 | return None; 54 | } 55 | 56 | let image = if features.has_alpha() { 57 | let len = 4 * pixel_count as usize; 58 | 59 | WebPImage::new(WebPMemory(image_ptr, len), PixelLayout::Rgba, width, height) 60 | } else { 61 | let len = 3 * pixel_count as usize; 62 | 63 | WebPImage::new(WebPMemory(image_ptr, len), PixelLayout::Rgb, width, height) 64 | }; 65 | 66 | Some(image) 67 | } 68 | } 69 | 70 | /// A wrapper around libwebp-sys::WebPBitstreamFeatures which allows to get information about the image. 71 | pub struct BitstreamFeatures(WebPBitstreamFeatures); 72 | 73 | impl BitstreamFeatures { 74 | pub fn new(data: &[u8]) -> Option { 75 | unsafe { 76 | let mut features: WebPBitstreamFeatures = std::mem::zeroed(); 77 | 78 | let result = WebPGetFeatures(data.as_ptr(), data.len(), &mut features as *mut _); 79 | 80 | if result == VP8StatusCode::VP8_STATUS_OK { 81 | return Some(Self(features)); 82 | } 83 | } 84 | 85 | None 86 | } 87 | 88 | /// Returns the width of the image as described by the bitstream in pixels. 89 | pub fn width(&self) -> u32 { 90 | self.0.width as u32 91 | } 92 | 93 | /// Returns the height of the image as described by the bitstream in pixels. 94 | pub fn height(&self) -> u32 { 95 | self.0.height as u32 96 | } 97 | 98 | /// Returns true if the image as described by the bitstream has an alpha channel. 99 | pub fn has_alpha(&self) -> bool { 100 | self.0.has_alpha == 1 101 | } 102 | 103 | /// Returns true if the image as described by the bitstream is animated. 104 | pub fn has_animation(&self) -> bool { 105 | self.0.has_animation == 1 106 | } 107 | 108 | /// Returns the format of the image as described by image bitstream. 109 | pub fn format(&self) -> Option { 110 | match self.0.format { 111 | 0 => Some(BitstreamFormat::Undefined), 112 | 1 => Some(BitstreamFormat::Lossy), 113 | 2 => Some(BitstreamFormat::Lossless), 114 | _ => None, 115 | } 116 | } 117 | } 118 | 119 | impl Debug for BitstreamFeatures { 120 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { 121 | let mut debug_struct = f.debug_struct("BitstreamFeatures"); 122 | 123 | debug_struct 124 | .field("width", &self.width()) 125 | .field("height", &self.height()) 126 | .field("has_alpha", &self.has_alpha()) 127 | .field("has_animation", &self.has_animation()); 128 | 129 | match self.format() { 130 | Some(BitstreamFormat::Undefined) => debug_struct.field("format", &"Undefined"), 131 | Some(BitstreamFormat::Lossy) => debug_struct.field("format", &"Lossy"), 132 | Some(BitstreamFormat::Lossless) => debug_struct.field("format", &"Lossless"), 133 | None => debug_struct.field("format", &"Error"), 134 | }; 135 | 136 | debug_struct.finish() 137 | } 138 | } 139 | 140 | #[derive(Debug)] 141 | /// The format of the image bitstream which is either lossy, lossless or something else. 142 | pub enum BitstreamFormat { 143 | Undefined = 0, 144 | Lossy = 1, 145 | Lossless = 2, 146 | } 147 | -------------------------------------------------------------------------------- /src/encoder.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "img")] 2 | use image::DynamicImage; 3 | use libwebp_sys::*; 4 | 5 | use crate::shared::*; 6 | 7 | /// An encoder for WebP images. It uses the default configuration of libwebp. 8 | pub struct Encoder<'a> { 9 | image: &'a [u8], 10 | layout: PixelLayout, 11 | width: u32, 12 | height: u32, 13 | } 14 | 15 | impl<'a> Encoder<'a> { 16 | /// Creates a new encoder from the given image data. 17 | /// The image data must be in the pixel layout of the color parameter. 18 | pub fn new(image: &'a [u8], layout: PixelLayout, width: u32, height: u32) -> Self { 19 | Self { 20 | image, 21 | layout, 22 | width, 23 | height, 24 | } 25 | } 26 | 27 | #[cfg(feature = "img")] 28 | /// Creates a new encoder from the given image. 29 | pub fn from_image(image: &'a DynamicImage) -> Result { 30 | match image { 31 | DynamicImage::ImageLuma8(_) => Err("Unimplemented"), 32 | DynamicImage::ImageLumaA8(_) => Err("Unimplemented"), 33 | DynamicImage::ImageRgb8(image) => Ok(Self::from_rgb( 34 | image.as_ref(), 35 | image.width(), 36 | image.height(), 37 | )), 38 | DynamicImage::ImageRgba8(image) => Ok(Self::from_rgba( 39 | image.as_ref(), 40 | image.width(), 41 | image.height(), 42 | )), 43 | _ => Err("Unimplemented"), 44 | } 45 | } 46 | 47 | /// Creates a new encoder from the given image data in the RGB pixel layout. 48 | pub fn from_rgb(image: &'a [u8], width: u32, height: u32) -> Self { 49 | Self { 50 | image, 51 | layout: PixelLayout::Rgb, 52 | width, 53 | height, 54 | } 55 | } 56 | 57 | /// Creates a new encoder from the given image data in the RGBA pixel layout. 58 | pub fn from_rgba(image: &'a [u8], width: u32, height: u32) -> Self { 59 | Self { 60 | image, 61 | layout: PixelLayout::Rgba, 62 | width, 63 | height, 64 | } 65 | } 66 | 67 | /// Encode the image with the given quality. 68 | /// The image quality must be between 0.0 and 100.0 inclusive for minimal and maximal quality respectively. 69 | pub fn encode(&self, quality: f32) -> WebPMemory { 70 | self.encode_simple(false, quality).unwrap() 71 | } 72 | 73 | /// Encode the image losslessly. 74 | pub fn encode_lossless(&self) -> WebPMemory { 75 | self.encode_simple(true, 75.0).unwrap() 76 | } 77 | 78 | pub fn encode_simple( 79 | &self, 80 | lossless: bool, 81 | quality: f32, 82 | ) -> Result { 83 | let mut config = WebPConfig::new().unwrap(); 84 | config.lossless = if lossless { 1 } else { 0 }; 85 | config.alpha_compression = if lossless { 0 } else { 1 }; 86 | config.quality = quality; 87 | self.encode_advanced(&config) 88 | } 89 | 90 | pub fn encode_advanced(&self, config: &WebPConfig) -> Result { 91 | unsafe { 92 | let mut picture = new_picture(self.image, self.layout, self.width, self.height); 93 | let res = encode(&mut *picture, config); 94 | res 95 | } 96 | } 97 | } 98 | 99 | pub(crate) unsafe fn new_picture( 100 | image: &[u8], 101 | layout: PixelLayout, 102 | width: u32, 103 | height: u32, 104 | ) -> ManageedPicture { 105 | let mut picture = WebPPicture::new().unwrap(); 106 | picture.use_argb = 1; 107 | picture.width = width as i32; 108 | picture.height = height as i32; 109 | match layout { 110 | PixelLayout::Rgba => { 111 | WebPPictureImportRGBA(&mut picture, image.as_ptr(), width as i32 * 4); 112 | } 113 | PixelLayout::Rgb => { 114 | WebPPictureImportRGB(&mut picture, image.as_ptr(), width as i32 * 3); 115 | } 116 | } 117 | ManageedPicture(picture) 118 | } 119 | unsafe fn encode( 120 | picture: &mut WebPPicture, 121 | config: &WebPConfig, 122 | ) -> Result { 123 | if WebPValidateConfig(config) == 0 { 124 | return Err(WebPEncodingError::VP8_ENC_ERROR_INVALID_CONFIGURATION); 125 | } 126 | let mut ww = std::mem::MaybeUninit::uninit(); 127 | WebPMemoryWriterInit(ww.as_mut_ptr()); 128 | picture.writer = Some(WebPMemoryWrite); 129 | picture.custom_ptr = ww.as_mut_ptr() as *mut std::ffi::c_void; 130 | let status = libwebp_sys::WebPEncode(config, picture); 131 | let ww = ww.assume_init(); 132 | let mem = WebPMemory(ww.mem, ww.size as usize); 133 | if status != VP8StatusCode::VP8_STATUS_OK as i32 { 134 | Ok(mem) 135 | } else { 136 | Err(picture.error_code) 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate provides provides functionality for encoding and decoding images from or into the webp format. 2 | //! It is implemented as a safe wrapper around the libwebp-sys crate. 3 | //! Currently only a subset of the features supported by libwebp are available. 4 | //! The simple encoding and decoding apis are implemented which use the default configuration of libwebp. 5 | 6 | mod animation_encoder; 7 | #[doc(inline)] 8 | pub use animation_encoder::*; 9 | 10 | mod decoder; 11 | #[doc(inline)] 12 | pub use decoder::*; 13 | 14 | mod animation_decoder; 15 | #[doc(inline)] 16 | pub use animation_decoder::*; 17 | 18 | mod encoder; 19 | #[doc(inline)] 20 | pub use encoder::*; 21 | pub use libwebp_sys::WebPEncodingError; 22 | 23 | mod shared; 24 | #[doc(inline)] 25 | pub use shared::*; 26 | 27 | pub use libwebp_sys::WebPConfig; 28 | 29 | #[cfg(test)] 30 | mod tests { 31 | use std::ops::Deref; 32 | 33 | use image::*; 34 | 35 | use crate::*; 36 | 37 | fn hsv_to_rgb(h: f64, s: f64, v: f64) -> [u8; 3] { 38 | let h = (h - h.floor()) * 6.0; 39 | let f = h - h.floor(); 40 | let u = (v * 255.0) as u8; 41 | let p = (v * (1.0 - s) * 255.0) as u8; 42 | let q = (v * (1.0 - s * f) * 255.0) as u8; 43 | let t = (v * (1.0 - s * (1.0 - f)) * 255.0) as u8; 44 | 45 | match h as u8 { 46 | 0 => [u, t, p], 47 | 1 => [q, u, p], 48 | 2 => [p, u, t], 49 | 3 => [p, q, u], 50 | 4 => [t, p, u], 51 | 5 => [u, p, q], 52 | _ => unreachable!("h must be between 0.0 and 1.0"), 53 | } 54 | } 55 | 56 | fn generate_color_wheel(width: u32, height: u32, background_alpha: bool) -> DynamicImage { 57 | let f = |x, y| { 58 | let width = width as f64; 59 | let height = height as f64; 60 | 61 | let x = x as f64 - width / 2.0; 62 | let y = y as f64 - height / 2.0; 63 | 64 | let theta = y.atan2(x); 65 | let tau = 2.0 * std::f64::consts::PI; 66 | let h = (theta + std::f64::consts::PI) / tau; 67 | let s = (4.0 * (x * x + y * y) / width / height).sqrt(); 68 | let v = 1.0; 69 | 70 | if s > 1.0 { 71 | Rgba([0, 0, 0, if background_alpha { 0 } else { 255 }]) 72 | } else { 73 | let [r, g, b] = hsv_to_rgb(h, s, v); 74 | Rgba([r, g, b, 255]) 75 | } 76 | }; 77 | 78 | DynamicImage::ImageRgba8(ImageBuffer::from_fn(width, height, f)) 79 | } 80 | 81 | const SIZE: u32 = 96; 82 | 83 | #[test] 84 | fn encode_decode() { 85 | let test_image_no_alpha = generate_color_wheel(SIZE, SIZE, false); 86 | let encoded = Encoder::from_image(&test_image_no_alpha) 87 | .unwrap() 88 | .encode_lossless(); 89 | 90 | let decoded = Decoder::new(encoded.deref()) 91 | .decode() 92 | .unwrap() 93 | .to_image() 94 | .to_rgb8(); 95 | assert_eq!(test_image_no_alpha.to_rgb8().deref(), decoded.deref()); 96 | 97 | let test_image_alpha = generate_color_wheel(SIZE, SIZE, true); 98 | let encoded = Encoder::from_image(&test_image_alpha) 99 | .unwrap() 100 | .encode_lossless(); 101 | 102 | let decoded = Decoder::new(encoded.deref()) 103 | .decode() 104 | .unwrap() 105 | .to_image() 106 | .to_rgba8(); 107 | 108 | // To achieve better compression, the webp library changes the rgb values in transparent regions 109 | // This means we have to exclusively compare the opaque regions 110 | // See the note for WebPEncodeLossless* at https://developers.google.com/speed/webp/docs/api#simple_encoding_api 111 | fn compare(p1: &Rgba, p2: &Rgba) -> bool { 112 | // two pixels are equal if they are fully transparent 113 | if p1.channels()[3] == 0 && p2.channels()[3] == 0 { 114 | true 115 | } else { 116 | // or if they otherwise equal 117 | p1 == p2 118 | } 119 | } 120 | 121 | for (p1, p2) in test_image_alpha.to_rgba8().pixels().zip(decoded.pixels()) { 122 | assert!(compare(p1, p2)) 123 | } 124 | } 125 | 126 | #[test] 127 | fn get_info() { 128 | let test_image_no_alpha = generate_color_wheel(SIZE, SIZE, false); 129 | let encoded = Encoder::from_image(&test_image_no_alpha) 130 | .unwrap() 131 | .encode_lossless(); 132 | 133 | let features = BitstreamFeatures::new(encoded.deref()).unwrap(); 134 | assert_eq!(features.width(), SIZE); 135 | assert_eq!(features.height(), SIZE); 136 | assert!(!features.has_alpha()); 137 | assert!(!features.has_animation()); 138 | 139 | let test_image_alpha = generate_color_wheel(SIZE, SIZE, true); 140 | let encoded = Encoder::from_image(&test_image_alpha) 141 | .unwrap() 142 | .encode_lossless(); 143 | 144 | let features = BitstreamFeatures::new(encoded.deref()).unwrap(); 145 | assert_eq!(features.width(), SIZE); 146 | assert_eq!(features.height(), SIZE); 147 | assert!(features.has_alpha()); 148 | assert!(!features.has_animation()); 149 | } 150 | 151 | #[test] 152 | fn anim_encode_decode() { 153 | let width = 32u32; 154 | let height = 32u32; 155 | let mut encode_images = vec![]; 156 | let mut config = WebPConfig::new().unwrap(); 157 | config.lossless = 1; 158 | config.alpha_compression = 0; 159 | config.alpha_filtering = 0; 160 | config.quality = 75f32; 161 | 162 | let mut encoder = AnimEncoder::new(width, height, &config); 163 | encoder.set_bgcolor([255, 0, 0, 255]); 164 | encoder.set_loop_count(3); 165 | 166 | let v = generate_color_wheel(width, height, true); 167 | encode_images.push(v); 168 | 169 | let v = generate_color_wheel(width, height, true); 170 | encode_images.push(v); 171 | 172 | let v = generate_color_wheel(width, height, true); 173 | encode_images.push(v); 174 | 175 | let v = generate_color_wheel(width, height, true); 176 | encode_images.push(v); 177 | 178 | let mut t = 1000; 179 | for v in encode_images.iter() { 180 | encoder.add_frame(AnimFrame::from_image(v, t).unwrap()); 181 | t += 250; 182 | } 183 | let webp = encoder.encode(); 184 | let mut decode_images: Vec = vec![]; 185 | match AnimDecoder::new(&webp).decode() { 186 | Ok(frames) => { 187 | decode_images.extend((&frames).into_iter().map(|a| (&a).into())); 188 | } 189 | Err(mes) => { 190 | println!("{}", mes); 191 | } 192 | } 193 | let mut encode_rgba = vec![]; 194 | for v in encode_images.into_iter() { 195 | let value = DynamicImage::ImageRgba8(v.to_rgba8()); 196 | encode_rgba.push(value); 197 | } 198 | fn compare(p1: &Rgba, p2: &Rgba) -> bool { 199 | // two pixels are equal if they are fully transparent 200 | if p1.channels()[3] == 0 && p2.channels()[3] == 0 { 201 | true 202 | } else { 203 | // or if they otherwise equal 204 | p1 == p2 205 | } 206 | } 207 | for (i1, i2) in encode_rgba.iter().zip(decode_images.iter()) { 208 | for (p1, p2) in i1.to_rgba8().pixels().zip(i2.to_rgba8().pixels()) { 209 | assert!(compare(p1, p2)) 210 | } 211 | } 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /src/shared.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Debug, Error, Formatter}; 2 | use std::ops::{Deref, DerefMut}; 3 | 4 | #[cfg(feature = "img")] 5 | use image::*; 6 | use libwebp_sys::{WebPFree, WebPPicture, WebPPictureFree}; 7 | 8 | /// This struct represents a safe wrapper around memory owned by libwebp. 9 | /// Its data contents can be accessed through the Deref and DerefMut traits. 10 | pub struct WebPMemory(pub(crate) *mut u8, pub(crate) usize); 11 | 12 | impl Debug for WebPMemory { 13 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { 14 | f.debug_struct("WebpMemory").finish() 15 | } 16 | } 17 | 18 | impl Drop for WebPMemory { 19 | fn drop(&mut self) { 20 | unsafe { WebPFree(self.0 as _) } 21 | } 22 | } 23 | 24 | impl Deref for WebPMemory { 25 | type Target = [u8]; 26 | 27 | fn deref(&self) -> &Self::Target { 28 | unsafe { std::slice::from_raw_parts(self.0, self.1) } 29 | } 30 | } 31 | 32 | impl DerefMut for WebPMemory { 33 | fn deref_mut(&mut self) -> &mut Self::Target { 34 | unsafe { std::slice::from_raw_parts_mut(self.0, self.1) } 35 | } 36 | } 37 | 38 | #[derive(Debug)] 39 | pub(crate) struct ManageedPicture(pub(crate) WebPPicture); 40 | 41 | impl Drop for ManageedPicture { 42 | fn drop(&mut self) { 43 | unsafe { WebPPictureFree(&mut self.0 as _) } 44 | } 45 | } 46 | 47 | impl Deref for ManageedPicture { 48 | type Target = WebPPicture; 49 | 50 | fn deref(&self) -> &Self::Target { 51 | &self.0 52 | } 53 | } 54 | 55 | impl DerefMut for ManageedPicture { 56 | fn deref_mut(&mut self) -> &mut Self::Target { 57 | &mut self.0 58 | } 59 | } 60 | 61 | /// This struct represents a decoded image. 62 | /// Its data contents can be accessed through the Deref and DerefMut traits. 63 | /// It is also possible to create an image::DynamicImage from this struct. 64 | pub struct WebPImage { 65 | data: WebPMemory, 66 | layout: PixelLayout, 67 | width: u32, 68 | height: u32, 69 | } 70 | 71 | impl WebPImage { 72 | pub(crate) fn new(data: WebPMemory, layout: PixelLayout, width: u32, height: u32) -> Self { 73 | Self { 74 | data, 75 | layout, 76 | width, 77 | height, 78 | } 79 | } 80 | 81 | /// Creates a DynamicImage from this WebPImage. 82 | #[cfg(feature = "img")] 83 | pub fn to_image(&self) -> DynamicImage { 84 | if self.layout.is_alpha() { 85 | let image = ImageBuffer::from_raw(self.width, self.height, self.data.to_owned()) 86 | .expect("ImageBuffer couldn't be created"); 87 | 88 | DynamicImage::ImageRgba8(image) 89 | } else { 90 | let image = ImageBuffer::from_raw(self.width, self.height, self.data.to_owned()) 91 | .expect("ImageBuffer couldn't be created"); 92 | 93 | DynamicImage::ImageRgb8(image) 94 | } 95 | } 96 | 97 | /// Returns the width of the image in pixels. 98 | pub fn width(&self) -> u32 { 99 | self.width 100 | } 101 | 102 | /// Returns the height of the image in pixels. 103 | pub fn height(&self) -> u32 { 104 | self.height 105 | } 106 | 107 | pub fn is_alpha(&self) -> bool { 108 | self.layout.is_alpha() 109 | } 110 | } 111 | 112 | impl Deref for WebPImage { 113 | type Target = [u8]; 114 | 115 | fn deref(&self) -> &Self::Target { 116 | self.data.deref() 117 | } 118 | } 119 | 120 | impl DerefMut for WebPImage { 121 | fn deref_mut(&mut self) -> &mut Self::Target { 122 | self.data.deref_mut() 123 | } 124 | } 125 | 126 | /// Describes the pixel layout (the order of the color channels) of an image. 127 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 128 | pub enum PixelLayout { 129 | Rgb, 130 | Rgba, 131 | } 132 | 133 | impl PixelLayout { 134 | /// Returns true if the pixel contains an alpha channel. 135 | pub fn is_alpha(self) -> bool { 136 | self == PixelLayout::Rgba 137 | } 138 | } 139 | --------------------------------------------------------------------------------