1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364
//! A simple solution for encoding common icon file-formats, such as `.ico` and `.icns`. //! //! This crate is mostly a wrapper for other libraries, unifying existing APIs into a single, cohesive //! interface. //! //! **IcoWriter** serves as **[Iconiic's](https://github.com/warrengalyen/iconiic)** internal library. //! //! # Overview //! //! An _icon_ consists of a set of _entries_. An _entry_ is simply an image that has a particular size. //! **IcoWriter** simply automates the process of re-scaling pictures and combining them into an _icon_. //! //! Pictures are scaled using resampling filters, which are represented by _functions that take a source_ //! _image and a size and return a re-scaled image_. //! //! This allows the users of this crate to provide their custom resampling filters. Common resampling //! filters are provided in the //! [`resample`](https://docs.rs/iconwriter/1.6.0/iconwriter/resample/index.html) module. //! //! # Examples //! //! ## General Usage //! //! ```rust, ignore //! use iconwriter::{Ico, SourceImage, Icon}; //! use iconwriter::Error as IconError; //! //! fn example() -> Result<(), IconError> { //! let icon = Ico::new(); //! //! match SourceImage::from_path("image.svg") { //! Some(img) => icon.add_entry(resample::linear, &img, 32), //! None => Ok(()) //! } //! } //! ``` //! //! ## Writing to a File //! //! ```rust, ignore //! use iconwriter::*; //! use std::{io, fs::File}; //! //! fn example() -> io::Result<()> { //! let icon = PngSequence::new(); //! //! /* Process the icon */ //! //! let file = File::create("out.icns")?; //! icon.write(file) //! } //! ``` //! pub extern crate image; pub extern crate resvg; pub use resvg::{usvg, raqote}; use std::{error, convert::From, path::{Path, PathBuf}, io::{self, Write}, fs::File, fmt::{self, Display, Debug}}; use image::{DynamicImage, GenericImageView}; use crate::usvg::Tree; pub use crate::ico::Ico; pub use crate::icns::Icns; pub use crate::png_sequence::PngSequence; #[cfg(test)] mod test; mod ico; mod icns; mod png_sequence; pub mod resample; const STD_CAPACITY: usize = 7; const INVALID_DIM_ERR: &str = "a resampling filter returned images of invalid resolution"; /// A generic representation of an icon encoder. pub trait Icon<E: AsRef<u32> + Debug + Eq> { /// Creates a new icon. /// /// # Example /// ```rust, ignore /// let icon = Ico::new(); /// ``` fn new() -> Self; /// Adds an individual entry to the icon. /// /// # Arguments /// * `filter` The resampling filter that will be used to re-scale `source`. /// * `source` A reference to the source image this entry will be based on. /// * `size` The target size of the entry in pixels. /// /// # Return Value /// * Returns `Err(Error::InvalidSize(_))` if the dimensions provided in the /// `size` argument are not supported. /// * Returns `Err(Error::Image(ImageError::DimensionError))` /// if the resampling filter provided in the `filter` argument produces /// results of dimensions other than the ones specified by `size`. /// * Otherwise return `Ok(())`. /// /// # Example /// /// ```rust, ignore /// use iconwriter::{Ico, SourceImage, Icon}; /// use iconwriter::Error as IconError; /// /// fn example() -> Result<(), IconError> { /// let icon = Ico::new(); /// /// match SourceImage::from_path("image.svg") { /// Some(img) => icon.add_entry(resample::linear, &img, 32), /// None => Ok(()) /// } /// } /// ``` fn add_entry<F: FnMut(&SourceImage, u32) -> DynamicImage>( &mut self, filter: F, source: &SourceImage, entry: E ) -> Result<(), Error<E>>; /// Adds a series of entries to the icon. /// # Arguments /// * `filter` The resampling filter that will be used to re-scale `source`. /// * `source` A reference to the source image this entry will be based on. /// * `size` A container for the target sizes of the entries in pixels. /// /// # Return Value /// * Returns `Err(Error::InvalidSize(_))` if the dimensions provided in the /// `size` argument are not supported. /// * Returns `Err(Error::Image(ImageError::DimensionError))` /// if the resampling filter provided in the `filter` argument produces /// results of dimensions other than the ones specified by `size`. /// * Otherwise return `Ok(())`. /// /// # Example /// /// ```rust, ignore /// use iconwriter::{Icns, SourceImage, Icon}; /// use iconwriter::Error as IconError; /// /// fn example() -> Result<(), IconError> { /// let icon = Icns::new(); /// /// match SourceImage::from_path("image.svg") { /// Some(img) => icon.add_entries( /// resample::linear, /// &img, /// vec![32, 64, 128] /// ), /// None => Ok(()) /// } /// } /// ``` fn add_entries<F: FnMut(&SourceImage, u32) -> DynamicImage, I: IntoIterator<Item = E>>( &mut self, mut filter: F, source: &SourceImage, entries: I ) -> Result<(), Error<E>> { for entry in entries { self.add_entry(|src, size| filter(src, size), source, entry)?; } Ok(()) } /// Writes the contents of the icon to `w`. /// /// # Example /// /// ```rust, ignore /// use iconwriter::*; /// use std::{io, fs::File}; /// /// fn example() -> io::Result<()> { /// let icon = PngSequence::new(); /// /// /* Process the icon */ /// /// let file = File::create("out.icns")?; /// icon.write(file) /// } /// ``` fn write<W: Write>(&mut self, w: &mut W) -> io::Result<()>; /// Writes the contents of the icon to a file on disk. /// /// # Example /// /// ```rust /// use iconwriter::*; /// use std::{io, fs::File}; /// /// fn example() -> io::Result<()> { /// let icon = Ico::new(); /// /// /* Process the icon */ /// /// icon.save("./output/out.ico") /// } /// ``` fn save<P: AsRef<Path>>(&mut self, path: &P) -> io::Result<()> { let mut file = File::create(path.as_ref())?; self.write(&mut file) } } #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub struct Entry(u32); #[derive(Clone, Debug, Eq, Hash)] pub struct NamedEntry(u32, PathBuf); #[derive(Clone)] /// A representation of a source image. pub enum SourceImage { /// A generic raster image. Raster(DynamicImage), /// A svg-encoded vector image. Svg(Tree) } #[derive(Debug)] /// The error type for operations of the `Icon` trait. pub enum Error<E: AsRef<u32> + Debug + Eq> { /// The `Icon` instance already includes this entry. AlreadyIncluded(E), /// The return value of a resampling filter has invalid /// dimensions: the dimensions do not match the ones /// specified in the application of the filter. InvalidDimensions(u32, (u32, u32)), /// An unsupported size was suplied to an `Icon` operation. InvalidSize(u32), /// Generic I/O error. Io(io::Error) } impl AsRef<u32> for Entry { fn as_ref(&self) -> &u32 { &self.0 } } impl NamedEntry { /// Creates a `NamedEntry` from a reference to a `Path`. /// # Example /// ```rust /// let entry = NamedEntry::from(32, &"icons/32/icon.png"); /// ``` pub fn from<P: AsRef<Path>>(size: u32, path: &P) -> Self { NamedEntry(size, PathBuf::from(path.as_ref())) } } impl AsRef<u32> for NamedEntry { fn as_ref(&self) -> &u32 { &self.0 } } impl PartialEq for NamedEntry { fn eq(&self, other: &NamedEntry) -> bool { self.1 == other.1 } } impl SourceImage { /// Attempts to create a `SourceImage` from a given path. /// /// The `SourceImage::from::<image::DynamicImage>` and `SourceImage::from::<usvg::Tree>` /// methods should always be preferred. /// /// # Return Value /// * Returns `Some(src)` if the file indicated by the `path` argument could be /// successfully parsed into an image. /// * Returns `None` otherwise. /// /// # Example /// ```rust, ignore /// let img = SourceImage::open("source.png")?; /// ``` pub fn open<P: AsRef<Path>>(path: P) -> Option<Self> { if let Ok(ras) = image::open(&path) { return Some(SourceImage::from(ras)); } Tree::from_file(&path, &usvg::Options::default()) .ok().map(|svg| SourceImage::from(svg)) } /// Returns the width of the original image in pixels. pub fn width(&self) -> f64 { match self { SourceImage::Raster(ras) => ras.width() as f64, SourceImage::Svg(svg) => svg.svg_node().view_box.rect.width() } } /// Returns the height of the original image in pixels. pub fn height(&self) -> f64 { match self { SourceImage::Raster(ras) => ras.height() as f64, SourceImage::Svg(svg) => svg.svg_node().view_box.rect.height() } } /// Returns the dimensions of the original image in pixels. pub fn dimensions(&self) -> (f64, f64) { (self.width(), self.height()) } } impl From<Tree> for SourceImage { fn from(svg: Tree) -> Self { SourceImage::Svg(svg) } } impl From<DynamicImage> for SourceImage { fn from(bit: DynamicImage) -> Self { SourceImage::Raster(bit) } } impl<E: AsRef<u32> + Debug + Eq> Display for Error<E> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Error::AlreadyIncluded(_) => write!(f, "the icon already includes this entry"), Error::InvalidSize(s) => write!(f, "{0}x{0} icons are not supported", s), Error::Io(err) => write!(f, "{}", err), Error::InvalidDimensions(s, (w, h)) => write!(f, "{0}: expected {1}x{1} and got {2}x{3}", INVALID_DIM_ERR, s, w, h) } } } impl<E: AsRef<u32> + Debug + Eq> error::Error for Error<E> { fn source(&self) -> Option<&(dyn error::Error + 'static)> { if let Error::Io(ref err) = self { Some(err) } else { None } } } impl<E: AsRef<u32> + Debug + Eq> From<io::Error> for Error<E> { fn from(err: io::Error) -> Self { Error::Io(err) } } impl<E: AsRef<u32> + Debug + Eq> Into<io::Error> for Error<E> { fn into(self) -> io::Error { match self { Error::Io(err) => err, Error::InvalidDimensions(_, _) => io::Error::from(io::ErrorKind::InvalidData), _ => io::Error::from(io::ErrorKind::InvalidInput) } } }