I want a function to convert PNG and JPEG images to WebP. This by simply giving the path of the image to function as parameter.
To achieve this I am using the Image and WebP crates.
The WebP-crate is poorly documented.
My code:
// Module to convert images to webp
use image::io::Reader as ImageReader;
use image::{DynamicImage, GenericImage, GenericImageView, ImageFormat}; // Using image crate: https://github.com/image-rs/image
use webp::{Encoder, PixelLayout, WebPImage, WebPMemory}; // Using webp crate: https://github.com/jaredforth/webp
/*
Function which converts an image in PNG or JPEG format to WEBP.
:param file_path: &String with the path to the image to convert.
:return Option<String>: Return the path of the WEBP-image as String when succesfull, returns None if function fails.
*/
pub async fn image_to_webp(file_path: &String) -> Option<String> {
// Open path as DynamicImage
let image: DynamicImage = ImageReader::open(file_path).unwrap().decode().unwrap();
// Guess image format (JPEG or PNG)
let image_format: Option<ImageFormat> = match image::guess_format(image.as_bytes()).unwrap() {
ImageFormat::Png => Some(ImageFormat::Png),
ImageFormat::Jpeg => Some(ImageFormat::Jpeg),
_ => None,
};
// Make webp::Encoder from DynamicImage
let encoder: Encoder = Encoder::from_image(&image).unwrap();
// Encode image into WebPMemory
let encoded_webp: WebPMemory = encoder.encode(65f32);
// Set pixellayout
let pixel_layout: PixelLayout = match image_format {
Some(ImageFormat::Png) => PixelLayout::Rgba,
Some(ImageFormat::Jpeg) => PixelLayout::Rgb,
None => {
return None;
}
_ => {
return None;
}
};
// WebPMemory to WebpImage
let webp_image: WebPImage =
WebPImage::new(encoded_webp, pixel_layout, image.width(), image.height());
unimplemented!()
}
Error:
associated function `new` is private private associated function (42:44)
I am able to encode my image into webp::WebPMemory, see the variable encoded_webp. The problem is that webp::WebPMemory does not contain a function to save the file or to convert to image::DynamicImage... image::DynamicImage does contain a function like pub fn save<Q>(&self, path: Q) -> ImageResult<()> which would allow me to save the image to a path.
So I want to convert encoded_webp of type webp::WebPMemory to a variable with type webp::WebPImage. Once I have converted it to webp::WebPImage I can convert it to image::DynamicImage and save the file.
The problem is that I have no clue on how to convert webp::WebPMemory to webp::WebPImage! The documentation does not explain this at all!!
I took a look at the source code of the WebP-crate and saw that webp::WebPImage has new() function taking a webp::WebPMemory as parameter, but that new() function is private...
// Module to convert images to webp
use image::io::Reader as ImageReader;
use image::{DynamicImage, EncodableLayout, GenericImage, GenericImageView, ImageFormat}; // Using image crate: https://github.com/image-rs/image
use webp::{Encoder, PixelLayout, WebPImage, WebPMemory}; // Using webp crate: https://github.com/jaredforth/webp
use std::fs::File;
use std::io::Write;
/*
Function which converts an image in PNG or JPEG format to WEBP.
:param file_path: &String with the path to the image to convert.
:return Option<String>: Return the path of the WEBP-image as String when succesfull, returns None if function fails.
*/
pub async fn image_to_webp(file_path: &String) -> Option<String> {
// Open path as DynamicImage
let image: DynamicImage = ImageReader::open(file_path).unwrap().decode().unwrap();
// Guess image format (JPEG or PNG)
let image_format: Option<ImageFormat> = match image::guess_format(image.as_bytes()).unwrap() {
ImageFormat::Png => Some(ImageFormat::Png),
ImageFormat::Jpeg => Some(ImageFormat::Jpeg),
_ => None,
};
// Make webp::Encoder from DynamicImage
let encoder: Encoder = Encoder::from_image(&image).unwrap();
// Encode image into WebPMemory
let encoded_webp: WebPMemory = encoder.encode(65f32);
// Make File-stream for WebP-result and write bytes into it, and save to path "output.webp"
let mut webp_image = File::create("output.webp").unwrap();
webp_image.write_all(encoded_webp.as_bytes()).unwrap();
unimplemented!()
}
Issues in the Git-repository of WebP contained an example.
Because there is not a lot of documentation about this problem I decided to share the module I am using to solve this. This will save some time for the next sucker Googling this. The function image_to_webp(file_path: &String) -> Option<String> allows you to give the path of a png or jpeg image as parameter, the function will make a subdirectory in the parent-directory of the file you are compressing to put the final WebP-image. So /some/folder/path/test.png will result in /some/folder/path/webp/test.webp.
I am using this module in a web-project so I can automatically convert images uploaded by users to WebP. This way I can also serve the WebP-version of 'non-static' images. Resulting in drastically increasing loading speed and efficiency.
// main.rs
mod webp;
fn main() {
let file_name: String = "./some/folder/path/example.png".to_string();
webp::image_to_webp(&file_name).await;
// Returns an Option<String>. None on failure, or path of the final WebP-image as String on success. Use match to handle errors.
// https://doc.rust-lang.org/std/option/
//
// New WebP-image: "./some/folder/path/webp/example.webp"
}
Things to consider.
I'm not fluent yet in Rust. Don't blindly trust this code.
My code needs to be asynchronous. If yours not remove .await and async keywords.
Module is well documented. You can improve and edit it. Add error-handling. Or you could make an extra parameter to set the quality of the WebP compression. Now it's standard 65f32.
Check that you have the correct chmod access to the file/folders you are trying to manipulate if the code fails.
Module (webp.rs)
// Module to convert images to webp
use image::io::Reader as ImageReader;
use image::{DynamicImage, EncodableLayout}; // Using image crate: https://github.com/image-rs/image
use webp::{Encoder, WebPMemory}; // Using webp crate: https://github.com/jaredforth/webp
use std::fs::File;
use std::io::Write;
use std::path::Path;
/*
Function which converts an image in PNG or JPEG format to WEBP.
:param file_path: &String with the path to the image to convert.
:return Option<String>: Return the path of the WEBP-image as String when succesfull, returns None if function fails.
*/
pub async fn image_to_webp(file_path: &String) -> Option<String> {
// Open path as DynamicImage
//let image: DynamicImage = ImageReader::open(file_path).unwrap().decode().unwrap();
let image = ImageReader::open(file_path);
let image: DynamicImage = match image {
Ok(img) => img.with_guessed_format().unwrap().decode().unwrap(), //ImageReader::with_guessed_format() function guesses if image needs to be opened in JPEG or PNG format.
Err(e) => {
println!("Error: {}", e);
return None;
}
};
// Make webp::Encoder from DynamicImage.
let encoder: Encoder = Encoder::from_image(&image).unwrap();
// Encode image into WebPMemory.
let encoded_webp: WebPMemory = encoder.encode(65f32);
// Put webp-image in a separate webp-folder in the location of the original image.
let path: &Path = Path::new(file_path);
let parent_directory: &Path = path.parent().unwrap();
let webp_folder_path = format!("{}/webp", parent_directory.to_str().unwrap());
match std::fs::create_dir_all(webp_folder_path.to_string()) {
Ok(_) => {}
Err(e) => {
println!("Error {}", e);
return None;
}
}
// Get filename of original image.
let filename_original_image = path.file_stem().unwrap().to_str().unwrap();
// Make full output path for webp-image.
let webp_image_path = format!(
"{}/{}.webp",
webp_folder_path.to_string(),
filename_original_image
);
// Make File-stream for WebP-result and write bytes into it, and save to path "output.webp".
let mut webp_image = File::create(webp_image_path.to_string()).unwrap();
match webp_image.write_all(encoded_webp.as_bytes()) {
Err(e) => {
println!("Error: {}", e);
return None;
}
Ok(_) => return Some(webp_image_path),
}
}
Just copy this code in a file called webp.rs in your /src folder. That way it automatically becomes a module.
You almost always want to take a &str instead of a &String
But here you want a &Path
Or the generic version I used, which is less typing for users of the function, at the cost of more monomorphizing. Many libraries, including std, use this convention
Even if you're only ever going to unwrap returned errors, you can see how much the ability to use ? can clean up your code
You could always have a wrapper that unwraps if it's a bother