zune-core-0.5.1/.cargo_vcs_info.json0000644000000001560000000000100127500ustar { "git": { "sha1": "bf30e1ecb9034964bfa251189173328a8a1380a8" }, "path_in_vcs": "crates/zune-core" }zune-core-0.5.1/CHANGELOG.md000064400000000000000000000006071046102023000133520ustar 00000000000000## 0.2.14 - Fixed building with no-std - Add `peek_at` and `pos` for writer - Make serde non default - Add option to make PNG add an alpha channel ## 0.2.12 - Add endianness conversion - Hide exposed values for EncoderOptions - Add Float32 bit depth - Remove support for BitDepth 10 and 12 - Add bit_size method ## 0.2.1 Improve documentation on various parts ## 0.2.0 Initial versionzune-core-0.5.1/Cargo.lock0000644000000037130000000000100107250ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "log" version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "proc-macro2" version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] [[package]] name = "serde" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", ] [[package]] name = "serde_core" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "syn" version = "2.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "unicode-ident" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "zune-core" version = "0.5.1" dependencies = [ "log", "serde", ] zune-core-0.5.1/Cargo.toml0000644000000021670000000000100107520ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.75.0" name = "zune-core" version = "0.5.1" build = false exclude = ["tests/"] autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "Core utilities for image processing in the zune family of crates" readme = "README.md" keywords = ["image"] categories = [ "multimedia::images", "multimedia::encoding", ] license = "MIT OR Apache-2.0 OR Zlib" repository = "https://github.com/etemesi254/zune-image" [features] std = [] [lib] name = "zune_core" path = "src/lib.rs" [dependencies.log] version = "0.4" optional = true [dependencies.serde] version = "1.0" optional = true zune-core-0.5.1/Cargo.toml.orig000064400000000000000000000014031046102023000144230ustar 00000000000000[package] name = "zune-core" version = "0.5.1" rust-version = "1.75.0" edition = "2021" description = "Core utilities for image processing in the zune family of crates" exclude = ["tests/"] repository = "https://github.com/etemesi254/zune-image" keywords = ["image"] categories = ["multimedia::images", "multimedia::encoding"] license = "MIT OR Apache-2.0 OR Zlib" [features] # When present, we can use std facilities to detect # if a specific feature exists # Not enabled by default. Other zune crates can enable dep:zune-core/std by default. # But if we enable it here, they can't disable it anymore. # See: https://github.com/rust-lang/cargo/issues/8366 std = [] [dependencies] log = { version = "0.4", optional = true } serde = { version = "1.0", optional = true } zune-core-0.5.1/LICENSE-APACHE000064400000000000000000000261351046102023000134710ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. zune-core-0.5.1/LICENSE-MIT000064400000000000000000000020611046102023000131710ustar 00000000000000MIT License Copyright (c) zune-image developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. zune-core-0.5.1/LICENSE-ZLIB000064400000000000000000000015331046102023000133030ustar 00000000000000zlib License (C) zune-image developers This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. zune-core-0.5.1/README.md000064400000000000000000000005211046102023000130130ustar 00000000000000## Zune core Core primitives necessary for image manipulations This crate contains small set of primitives necessary for image manipulations which are shared among most `zune-` family of decoders and encoders. ### Items present Currently,it contains. - Colorspace definitions - Bit depth definitions. - Decoder and encoder optionszune-core-0.5.1/src/bit_depth.rs000064400000000000000000000117401046102023000146400ustar 00000000000000/* * Copyright (c) 2023. * * This software is free software; * * You can redistribute it or modify it under terms of the MIT, Apache License or Zlib license */ //! Image bit depth, information and manipulations /// The image bit depth. /// /// The library successfully supports depths up to /// 16 bits, as the underlying storage is usually a `u16`. /// /// This allows us to comfortably support a wide variety of images /// e.g 10 bit av1, 16 bit png and ppm. #[derive(Copy, Clone, Debug, Eq, PartialEq)] #[non_exhaustive] pub enum BitDepth { /// U8 bit depth. /// /// Images with such bit depth use [`u8`] to store /// pixels and use the whole range from 0-255. /// /// It is currently the smallest supported bit depth /// by the library. /// /// For images with bit depths lower than this, they will be scaled /// to this bit depth Eight, /// U16 bit depth /// /// Images with such bit depths use [`u16`] to store values and use the whole range /// i.e 0-65535 /// /// Data is stored and processed in native endian. Sixteen, /// Floating point 32 bit data, range is 0.0 to 1.0 /// /// Uses f32 to store data Float32, /// Bit depth information is unknown Unknown } /// The underlying bit representation of the image /// /// This represents the minimum rust type that /// can be used to represent image data, required /// by `Channel` struct in zune-image #[derive(Copy, Clone, Debug, Eq, PartialEq)] #[non_exhaustive] pub enum BitType { /// Images represented using a [`u8`] as their /// underlying pixel storage U8, /// Images represented using a [`u16`] as their /// underlying pixel storage. U16, /// Images represented using a [`f32`] as their /// underlying pixel storage F32 } impl BitType { /// Return the equivalent of the image bit type's depth pub fn to_depth(self) -> BitDepth { match self { BitType::U8 => BitDepth::Eight, BitType::U16 => BitDepth::Sixteen, BitType::F32 => BitDepth::Float32 } } } impl Default for BitDepth { fn default() -> Self { Self::Unknown } } impl BitDepth { /// Get the max value supported by the bit depth /// /// During conversion from one bit depth to another /// /// larger values should be clamped to this bit depth #[rustfmt::skip] #[allow(clippy::zero_prefixed_literal)] pub const fn max_value(self) -> u16 { match self { Self::Eight => (1 << 08) - 1, Self::Sixteen => u16::MAX, Self::Float32 => 1, Self::Unknown => 0, } } /// Return the minimum number of bits that can be used to represent /// each pixel in the image /// /// All bit depths below 8 return a bit type of `BitType::U8`. /// and all those above 8 and below 16 return a bit type of `BitType::SixTeen` /// /// # Returns /// An enum whose variants represent the minimum size for an unsigned integer /// which can store the image pixels without overflow /// /// # Example /// /// ``` /// use zune_core::bit_depth::{BitDepth, BitType}; /// assert_eq!(BitDepth::Eight.bit_type(),BitType::U8); /// /// assert_eq!(BitDepth::Sixteen.bit_type(),BitType::U16); /// ``` /// /// See also [size_of](BitDepth::size_of) pub const fn bit_type(self) -> BitType { match self { Self::Eight => BitType::U8, Self::Sixteen => BitType::U16, Self::Float32 => BitType::F32, Self::Unknown => panic!("Unknown bit type") } } /// Get the number of bytes needed to store a specific bit depth /// /// /// # Example /// For images less than or equal to 8 bits(1 byte), we can use a [`u8`] to store /// the pixels, and a size_of [`u8`] is 1 /// /// For images greater than 8 bits and less than 16 bits(2 bytes), we can use a [`u16`] to /// store the pixels, a size_of [`u16`] is 2. /// ``` /// use zune_core::bit_depth::BitDepth; /// let depth = BitDepth::Sixteen; /// // greater 12 bits is greater than 8 and less than 16 /// assert_eq!(depth.size_of(),2); /// ``` pub const fn size_of(self) -> usize { match self { Self::Eight => core::mem::size_of::(), Self::Sixteen => core::mem::size_of::(), Self::Float32 => core::mem::size_of::(), Self::Unknown => panic!("Unknown bit type") } } pub const fn bit_size(&self) -> usize { self.size_of() * 8 } } /// Byte endianness of returned samples /// this is useful when the decoder returns samples which span more /// than one byte yet the type returned is `&[u8]` /// /// This helps you interpret how those bytes should be reconstructed /// to a higher order type #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum ByteEndian { /// Little Endian byte-order LE, /// Big Endian byte-order BE } zune-core-0.5.1/src/bytestream/reader/no_std_readers.rs000064400000000000000000000133641046102023000213160ustar 00000000000000#![allow(dead_code)] use crate::bytestream::{ZByteIoError, ZByteReaderTrait, ZSeekFrom}; /// Wraps an in memory buffer providing it with a `Seek` method /// but works in `no_std` environments /// /// `std::io::Cursor` is available in std environments, but we also need support /// for `no_std` environments so this serves as a drop in replacement pub struct ZCursor { pub(crate) stream: T, pub(crate) position: usize } impl> ZCursor { pub fn new(buffer: T) -> ZCursor { ZCursor { stream: buffer, position: 0 } } } impl> ZCursor { /// Move forward `num` bytes from /// the current position. /// /// It doesn't check that position overflowed, new position /// may point past the internal buffer, all subsequent reads will /// either return an error or zero depending on the method called #[inline] pub fn skip(&mut self, num: usize) { // Can this overflow ?? self.position = self.position.wrapping_add(num); } /// Move back `num` bytes from the current position /// /// /// This saturates at zero, it can never be negative or wraparound /// when the value becomes too small #[inline] pub fn rewind(&mut self, num: usize) { self.position = self.position.saturating_sub(num); } pub fn split(&self) -> (&[u8], &[u8]) { let slice = self.stream.as_ref(); let pos = self.position.min(slice.len()); slice.split_at(pos) } } impl> ZCursor { #[inline(always)] pub (crate) fn read_byte_no_error_impl(&mut self) -> u8 { let byte = self.stream.as_ref().get(self.position).unwrap_or(&0); self.position += 1; *byte } #[inline(always)] pub (crate) fn read_exact_bytes_impl(&mut self, buf: &mut [u8]) -> Result<(), ZByteIoError> { let bytes_read = self.read_bytes(buf)?; if bytes_read != buf.len() { // restore read to initial position it was in. self.rewind(bytes_read); // not all bytes were read. return Err(ZByteIoError::NotEnoughBytes(bytes_read, buf.len())); } Ok(()) } pub (crate) fn read_const_bytes_impl(&mut self, buf: &mut [u8; N]) -> Result<(), ZByteIoError> { if self.position + N <= self.stream.as_ref().len() { // we are in bounds let reference = self.stream.as_ref(); let position = self.position; if let Some(buf_ref) = reference.get(position..position + N) { self.position += N; buf.copy_from_slice(buf_ref); return Ok(()); } } Err(ZByteIoError::Generic("Cannot satisfy read")) } pub (crate) fn read_const_bytes_no_error_impl(&mut self, buf: &mut [u8; N]) { if self.position + N <= self.stream.as_ref().len() { // we are in bounds let reference = self.stream.as_ref(); let position = self.position; if let Some(buf_ref) = reference.get(position..position + N) { self.position += N; buf.copy_from_slice(buf_ref); } } } #[inline(always)] pub (crate) fn read_bytes_impl(&mut self, buf: &mut [u8]) -> Result { let len = self.peek_bytes_impl(buf)?; self.skip(len); Ok(len) } #[inline(always)] pub (crate) fn peek_bytes_impl(&mut self, buf: &mut [u8]) -> Result { let stream_end = self.stream.as_ref().len(); let start = core::cmp::min(self.position, stream_end); let end = core::cmp::min(self.position + buf.len(), stream_end); let slice = self.stream.as_ref().get(start..end).unwrap(); buf[..slice.len()].copy_from_slice(slice); let len = slice.len(); Ok(len) } #[inline(always)] pub (crate) fn peek_exact_bytes_impl(&mut self, buf: &mut [u8]) -> Result<(), ZByteIoError> { self.read_exact_bytes_impl(buf)?; self.rewind(buf.len()); Ok(()) } #[inline(always)] pub (crate) fn z_seek_impl(&mut self, from: ZSeekFrom) -> Result { let (base_pos, offset) = match from { ZSeekFrom::Start(n) => { self.position = n as usize; return Ok(n); } ZSeekFrom::End(n) => (self.stream.as_ref().len(), n as isize), ZSeekFrom::Current(n) => (self.position, n as isize) }; match base_pos.checked_add_signed(offset) { Some(n) => { self.position = n; Ok(self.position as u64) } None => Err(ZByteIoError::SeekError("Negative seek")) } } #[inline(always)] pub (crate) fn is_eof_impl(&mut self) -> Result { Ok(self.position >= self.stream.as_ref().len()) } #[inline(always)] pub (crate) fn z_position_impl(&mut self) -> Result { Ok(self.position as u64) } pub (crate) fn read_remaining_impl(&mut self, sink: &mut alloc::vec::Vec) -> Result { let start = self.position; let end = self.stream.as_ref().len(); match self.stream.as_ref().get(start..end) { None => { return Err(ZByteIoError::Generic( "Somehow read remaining couldn't satisfy it's invariants" )) } Some(e) => { sink.extend_from_slice(e); } } self.skip(end - start); Ok(end - start) } } impl> From for ZCursor { fn from(value: T) -> Self { ZCursor::new(value) } } zune-core-0.5.1/src/bytestream/reader/std_readers.rs000064400000000000000000000112261046102023000206150ustar 00000000000000#![cfg(feature = "std")] use std::io; use std::io::SeekFrom; use crate::bytestream::reader::{ZByteIoError, ZSeekFrom}; use crate::bytestream::{ZByteReaderTrait, ZCursor}; // note (cae): If Rust ever stabilizes trait specialization, specialize this for Cursor impl ZByteReaderTrait for T { #[inline(always)] fn read_byte_no_error(&mut self) -> u8 { let mut buf = [0]; let _ = self.read(&mut buf); buf[0] } #[inline(always)] fn read_exact_bytes(&mut self, buf: &mut [u8]) -> Result<(), ZByteIoError> { let mut bytes_read = 0; while bytes_read < buf.len() { match self.read(&mut buf[bytes_read..]) { Ok(0) => { // if a read returns zero bytes read, it means it encountered an EOF so we seek // back to where we started because some paths may aggressively read forward and // ZCursor maintains the position. // NB: (cae) [tag=perf] This adds a branch on every read, and will slow down every function // resting on it. Sorry self.seek(SeekFrom::Current(-(bytes_read as i64))) .map_err(ZByteIoError::from)?; return Err(ZByteIoError::NotEnoughBytes(bytes_read, buf.len())); } Ok(bytes) => { bytes_read += bytes; } Err(e) => return Err(ZByteIoError::from(e)) } } Ok(()) } #[inline] fn read_const_bytes(&mut self, buf: &mut [u8; N]) -> Result<(), ZByteIoError> { self.read_exact_bytes(buf) } fn read_const_bytes_no_error(&mut self, buf: &mut [u8; N]) { let _ = self.read_const_bytes(buf); } #[inline(always)] fn read_bytes(&mut self, buf: &mut [u8]) -> Result { self.read(buf).map_err(ZByteIoError::from) } #[inline(always)] fn peek_bytes(&mut self, buf: &mut [u8]) -> Result { // first read bytes to the buffer let bytes_read = self.read_bytes(buf)?; let converted = -i64::try_from(bytes_read).map_err(ZByteIoError::from)?; self.seek(std::io::SeekFrom::Current(converted)) .map_err(ZByteIoError::from)?; Ok(bytes_read) } #[inline(always)] fn peek_exact_bytes(&mut self, buf: &mut [u8]) -> Result<(), ZByteIoError> { // first read bytes to the buffer self.read_exact_bytes(buf)?; let converted = -i64::try_from(buf.len()).map_err(ZByteIoError::from)?; self.seek(std::io::SeekFrom::Current(converted)) .map_err(ZByteIoError::from)?; Ok(()) } #[inline(always)] fn z_seek(&mut self, from: ZSeekFrom) -> Result { self.seek(from.to_std_seek()).map_err(ZByteIoError::from) } #[inline(always)] fn is_eof(&mut self) -> Result { self.fill_buf() .map(|b| b.is_empty()) .map_err(ZByteIoError::from) } #[inline(always)] fn z_position(&mut self) -> Result { self.stream_position().map_err(ZByteIoError::from) } #[inline(always)] fn read_remaining(&mut self, sink: &mut Vec) -> Result { self.read_to_end(sink).map_err(ZByteIoError::from) } } impl> std::io::Read for ZCursor { fn read(&mut self, buf: &mut [u8]) -> std::io::Result { self.read_bytes_impl(buf) .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("{:?}", e))) } } impl> std::io::BufRead for ZCursor { fn fill_buf(&mut self) -> std::io::Result<&[u8]> { Ok(ZCursor::split(self).1) } fn consume(&mut self, amount: usize) { self.position += amount; } } impl> std::io::Seek for ZCursor { fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result { let (base_pos, offset) = match pos { std::io::SeekFrom::Start(n) => { self.position = n as usize; return Ok(n); } std::io::SeekFrom::End(n) => (self.stream.as_ref().len(), n as isize), std::io::SeekFrom::Current(n) => (self.position, n as isize) }; match base_pos.checked_add_signed(offset) { Some(n) => { self.position = n; Ok(self.position as u64) } None => Err(std::io::Error::new( std::io::ErrorKind::Other, "Negative seek" )) } } } zune-core-0.5.1/src/bytestream/reader/zcursor_no_std.rs000064400000000000000000000031751046102023000213770ustar 00000000000000#![cfg(not(feature = "std"))] use crate::bytestream::{ZByteIoError, ZByteReaderTrait, ZCursor, ZSeekFrom}; impl> ZByteReaderTrait for ZCursor { #[inline(always)] fn read_byte_no_error(&mut self) -> u8 { self.read_byte_no_error_impl() } #[inline(always)] fn read_exact_bytes(&mut self, buf: &mut [u8]) -> Result<(), ZByteIoError> { self.read_exact_bytes_impl(buf) } fn read_const_bytes(&mut self, buf: &mut [u8; N]) -> Result<(), ZByteIoError> { self.read_const_bytes_impl::(buf) } fn read_const_bytes_no_error(&mut self, buf: &mut [u8; N]) { self.read_const_bytes_no_error_impl::(buf) } #[inline(always)] fn read_bytes(&mut self, buf: &mut [u8]) -> Result { self.read_bytes_impl(buf) } #[inline(always)] fn peek_bytes(&mut self, buf: &mut [u8]) -> Result { self.peek_bytes_impl(buf) } #[inline(always)] fn peek_exact_bytes(&mut self, buf: &mut [u8]) -> Result<(), ZByteIoError> { self.peek_exact_bytes_impl(buf) } #[inline(always)] fn z_seek(&mut self, from: ZSeekFrom) -> Result { self.z_seek_impl(from) } #[inline(always)] fn is_eof(&mut self) -> Result { self.is_eof_impl() } #[inline(always)] fn z_position(&mut self) -> Result { self.z_position_impl() } fn read_remaining(&mut self, sink: &mut alloc::vec::Vec) -> Result { self.read_remaining_impl(sink) } } zune-core-0.5.1/src/bytestream/reader.rs000064400000000000000000000362611046102023000163240ustar 00000000000000use alloc::string::String; use alloc::vec; use alloc::vec::Vec; use core::fmt::Formatter; pub(crate) mod no_std_readers; pub(crate) mod std_readers; pub(crate) mod zcursor_no_std; use crate::bytestream::ZByteReaderTrait; /// Enumeration of possible methods to seek within an I/O object. /// /// It is analogous to the [SeekFrom](std::io::SeekFrom) in the std library but /// it's here to allow this to work in no-std crates #[derive(Copy, PartialEq, Eq, Clone, Debug)] pub enum ZSeekFrom { /// Sets the offset to the provided number of bytes. Start(u64), /// Sets the offset to the size of this object plus the specified number of /// bytes. /// /// It is possible to seek beyond the end of an object, but it's an error to /// seek before byte 0. End(i64), /// Sets the offset to the current position plus the specified number of /// bytes. /// /// It is possible to seek beyond the end of an object, but it's an error to /// seek before byte 0. Current(i64) } impl ZSeekFrom { /// Convert to [SeekFrom](std::io::SeekFrom) from the `std::io` library /// /// This is only present when std feature is present #[cfg(feature = "std")] pub(crate) fn to_std_seek(self) -> std::io::SeekFrom { match self { ZSeekFrom::Start(pos) => std::io::SeekFrom::Start(pos), ZSeekFrom::End(pos) => std::io::SeekFrom::End(pos), ZSeekFrom::Current(pos) => std::io::SeekFrom::Current(pos) } } } pub enum ZByteIoError { /// A standard library error /// Only available with the `std` feature #[cfg(feature = "std")] StdIoError(std::io::Error), /// An error converting from one type to another TryFromIntError(core::num::TryFromIntError), /// Not enough bytes to satisfy a read // requested, read NotEnoughBytes(usize, usize), /// The output buffer is too small to write the bytes NotEnoughBuffer(usize, usize), /// An error that may occur randomly Generic(&'static str), /// An error that occurred during a seek operation SeekError(&'static str), /// An error that occurred during a seek operation SeekErrorOwned(String) } impl core::fmt::Debug for ZByteIoError { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { match self { #[cfg(feature = "std")] ZByteIoError::StdIoError(err) => { writeln!(f, "Underlying I/O error {}", err) } ZByteIoError::TryFromIntError(err) => { writeln!(f, "Cannot convert to int {}", err) } ZByteIoError::NotEnoughBytes(found, expected) => { writeln!(f, "Not enough bytes, expected {expected} but found {found}") } ZByteIoError::NotEnoughBuffer(expected, found) => { writeln!( f, "Not enough buffer to write {expected} bytes, buffer size is {found}" ) } ZByteIoError::Generic(err) => { writeln!(f, "Generic I/O error: {err}") } ZByteIoError::SeekError(err) => { writeln!(f, "Seek error: {err}") } ZByteIoError::SeekErrorOwned(err) => { writeln!(f, "Seek error {err}") } } } } #[cfg(feature = "std")] impl From for ZByteIoError { fn from(value: std::io::Error) -> Self { ZByteIoError::StdIoError(value) } } impl From for ZByteIoError { fn from(value: core::num::TryFromIntError) -> Self { ZByteIoError::TryFromIntError(value) } } impl From<&'static str> for ZByteIoError { fn from(value: &'static str) -> Self { ZByteIoError::Generic(value) } } /// The image reader wrapper /// /// This wraps anything that implements [ZByteReaderTrait] and /// extends the ability of the core trait methods by providing /// utilities like endian aware byte functions. /// /// This prevents each implementation from providing its own pub struct ZReader { inner: T, temp_buffer: Vec } impl ZReader { /// Create a new reader from a source /// that implements the [ZByteReaderTrait] pub fn new(source: T) -> ZReader { ZReader { inner: source, temp_buffer: vec![] } } /// Destroy this reader returning /// the underlying source of the bytes /// from which we were decoding #[inline(always)] pub fn consume(self) -> T { self.inner } /// Skip ahead ignoring `num` bytes /// /// For more advanced seek methods see [Self::seek] that allows /// moving around via more advanced ways /// /// # Arguments /// - num: The number of bytes to skip. /// /// # Returns /// - `Ok(u64)`: The new position from the start of the stream. /// - `Error` If something went wrong #[inline(always)] pub fn skip(&mut self, num: usize) -> Result { self.inner.z_seek(ZSeekFrom::Current(num as i64)) } /// Move back from current position to a previous /// position /// /// For more advanced seek methods see [Self::seek] that allows /// moving around via more advanced ways /// /// # Arguments /// - `num`: Positions to move before the current cursor /// /// # Returns /// - `Ok(u64)`: The new position from the start of the stream. /// - `Error` If something went wrong #[inline(always)] pub fn rewind(&mut self, num: usize) -> Result { self.inner.z_seek(ZSeekFrom::Current(-(num as i64))) } /// Move around a stream of bytes /// /// This is analogous to the [std::io::Seek] trait with the same ergonomics /// only implemented to allow use in a `no_std` environment /// /// # Arguments /// - `from`: The seek operation type. /// /// # Returns /// - `Ok(u64)`: The new position from the start of the stream. /// - Error if something went wrong. #[inline(always)] pub fn seek(&mut self, from: ZSeekFrom) -> Result { self.inner.z_seek(from) } /// Read a single byte from the underlying stream /// /// If an error occurs, it will return `0` as default output /// hence it may be difficult to distinguish a `0` from the underlying source /// and a `0` from an error. /// For that there is [Self::read_u8_err] /// /// # Returns. /// - The next byte on the stream. /// #[inline(always)] pub fn read_u8(&mut self) -> u8 { self.inner.read_byte_no_error() } /// Read a single byte returning an error if the read cannot be satisfied /// /// # Returns /// - `Ok(u8)`: The next byte /// - Error if the byte read could not be satisfied #[inline(always)] pub fn read_u8_err(&mut self) -> Result { let mut buf = [0]; self.inner.read_const_bytes(&mut buf)?; Ok(buf[0]) } /// Look ahead position bytes and return a reference /// to num_bytes from that position, or an error if the /// peek would be out of bounds. /// /// This doesn't increment the position, bytes would have to be discarded /// at a later point. #[inline] pub fn peek_at(&mut self, position: usize, num_bytes: usize) -> Result<&[u8], ZByteIoError> { // short circuit for zero // important since implementations like File will // cause a syscall on skip if position != 0 { // skip position bytes from start self.skip(position)?; } if num_bytes > 20 * 1024 * 1024 { // resize of 20 MBs, skipping too much, so panic return Err(ZByteIoError::Generic("Too many bytes skipped")); } // resize buffer self.temp_buffer.resize(num_bytes, 0); // read bytes match self.inner.peek_exact_bytes(&mut self.temp_buffer[..]) { Ok(_) => { // rewind back to where we were if position != 0 { self.rewind(position)?; } Ok(&self.temp_buffer) } Err(e) => Err(e) } } /// Read a fixed number of known bytes to a buffer and return the bytes or an error /// if it occurred. /// /// The size of the `N` value must be small enough to fit the stack space otherwise /// this will cause a stack overflow :) /// /// If you can ignore errors, you can use [Self::read_fixed_bytes_or_zero] /// /// # Returns /// - `Ok([u8;N])`: The bytes read from the source /// - An error if it occurred. #[inline(always)] pub fn read_fixed_bytes_or_error(&mut self) -> Result<[u8; N], ZByteIoError> { let mut byte_store: [u8; N] = [0; N]; match self.inner.read_const_bytes(&mut byte_store) { Ok(_) => Ok(byte_store), Err(e) => Err(e) } } /// Read a fixed bytes to an array and if that is impossible, return an array containing /// zeros /// /// If you want to handle errors, use [Self::read_fixed_bytes_or_error] #[inline(always)] pub fn read_fixed_bytes_or_zero(&mut self) -> [u8; N] { let mut byte_store: [u8; N] = [0; N]; self.inner.read_const_bytes_no_error(&mut byte_store); byte_store } /// Move the cursor to a fixed position in the stream /// /// This will move the cursor to exacltly `position` bytes from the start of the buffer /// /// # Arguments /// - `position`: The current position to move the cursor. #[inline] pub fn set_position(&mut self, position: usize) -> Result<(), ZByteIoError> { self.seek(ZSeekFrom::Start(position as u64))?; Ok(()) } /// Return true if the underlying buffer can no longer produce bytes /// /// This call may be expensive depending on the underlying buffer type, e.g if /// it's a file, we have to ask the os whether we have more contents, or in other words make a syscall. /// /// Use that wisely /// /// # Returns /// - `Ok(bool)`: True if we are in `EOF`, false if we can produce more bytes /// - Error if something went wrong #[inline(always)] pub fn eof(&mut self) -> Result { self.inner.is_eof() } /// Return the current position of the inner reader or an error /// if that occurred when reading. /// /// Like [eof](Self::eof), the perf characteristics may vary depending on underlying reader /// /// # Returns /// - `Ok(u64)`: The current position of the inner reader #[inline(always)] pub fn position(&mut self) -> Result { self.inner.z_position() } /// Read a fixed number of bytes from the underlying reader returning /// an error if that can't be satisfied /// /// Similar to [std::io::Read::read_exact] /// /// # Returns /// - `Ok(())`: If the read was successful /// - An error if the read was unsuccessful including failure to fill the whole bytes pub fn read_exact_bytes(&mut self, buf: &mut [u8]) -> Result<(), ZByteIoError> { self.inner.read_exact_bytes(buf) } /// Read some bytes from the inner reader, and return number of bytes read /// /// The implementation may not read bytes enough to fill the buffer /// /// Similar to [std::io::Read::read] /// /// # Returns /// - `Ok(usize)`: Number of bytes actually read to the buffer /// - An error if something went wrong pub fn read_bytes(&mut self, buf: &mut [u8]) -> Result { self.inner.read_bytes(buf) } } enum Mode { // Big endian BE, // Little Endian LE } macro_rules! get_single_type { ($name:tt,$name2:tt,$name3:tt,$name4:tt,$name5:tt,$name6:tt,$int_type:tt) => { impl ZReader { #[inline(always)] fn $name(&mut self, mode: Mode) -> $int_type { const SIZE_OF_VAL: usize = core::mem::size_of::<$int_type>(); let mut space = [0; SIZE_OF_VAL]; self.inner.read_const_bytes_no_error(&mut space); match mode { Mode::BE => $int_type::from_be_bytes(space), Mode::LE => $int_type::from_le_bytes(space) } } #[inline(always)] fn $name2(&mut self, mode: Mode) -> Result<$int_type, ZByteIoError> { const SIZE_OF_VAL: usize = core::mem::size_of::<$int_type>(); let mut space = [0; SIZE_OF_VAL]; match self.inner.read_const_bytes(&mut space) { Ok(_) => match mode { Mode::BE => Ok($int_type::from_be_bytes(space)), Mode::LE => Ok($int_type::from_le_bytes(space)) }, Err(e) => Err(e) } } #[doc=concat!("Read ",stringify!($int_type)," as a big endian integer")] #[doc=concat!("Returning an error if the underlying buffer cannot support a ",stringify!($int_type)," read.")] #[inline] pub fn $name3(&mut self) -> Result<$int_type, ZByteIoError> { self.$name2(Mode::BE) } #[doc=concat!("Read ",stringify!($int_type)," as a little endian integer")] #[doc=concat!("Returning an error if the underlying buffer cannot support a ",stringify!($int_type)," read.")] #[inline] pub fn $name4(&mut self) -> Result<$int_type, ZByteIoError> { self.$name2(Mode::LE) } #[doc=concat!("Read ",stringify!($int_type)," as a big endian integer")] #[doc=concat!("Returning 0 if the underlying buffer does not have enough bytes for a ",stringify!($int_type)," read.")] #[inline(always)] pub fn $name5(&mut self) -> $int_type { self.$name(Mode::BE) } #[doc=concat!("Read ",stringify!($int_type)," as a little endian integer")] #[doc=concat!("Returning 0 if the underlying buffer does not have enough bytes for a ",stringify!($int_type)," read.")] #[inline(always)] pub fn $name6(&mut self) -> $int_type { self.$name(Mode::LE) } } }; } get_single_type!( get_u16_inner_or_default, get_u16_inner_or_die, get_u16_be_err, get_u16_le_err, get_u16_be, get_u16_le, u16 ); get_single_type!( get_u32_inner_or_default, get_u32_inner_or_die, get_u32_be_err, get_u32_le_err, get_u32_be, get_u32_le, u32 ); get_single_type!( get_u64_inner_or_default, get_u64_inner_or_die, get_u64_be_err, get_u64_le_err, get_u64_be, get_u64_le, u64 ); #[cfg(feature = "std")] impl std::io::Read for ZReader where T: ZByteReaderTrait { fn read(&mut self, buf: &mut [u8]) -> std::io::Result { use std::io::ErrorKind; self.read_bytes(buf) .map_err(|e| std::io::Error::new(ErrorKind::Other, format!("{:?}", e))) } } zune-core-0.5.1/src/bytestream/traits.rs000064400000000000000000000160121046102023000163600ustar 00000000000000/* * Copyright (c) 2023. * * This software is free software; * * You can redistribute it or modify it under terms of the MIT, Apache License or Zlib license */ //! Traits for reading and writing images in zune //! //! //! This exposes the traits and implementations for readers //! and writers in the zune family of decoders and encoders. use crate::bytestream::reader::{ZByteIoError, ZSeekFrom}; /// The de-facto Input trait implemented for readers. /// /// This provides the basic functions needed to quick and sometimes /// heap free I/O for the zune image decoders with easy support for extending it /// to multiple implementations. /// /// # Considerations /// /// If you have an in memory buffer, prefer [`ZCursor`](crate::bytestream::ZCursor) over [`Cursor`](std::io::Cursor). /// We implement this trait for two types, `ZCursor`, and any thing that implements `BufRead`+`Seek`, `Cursor` falls in the latter /// and since Rust doesn't have specialization for traits, we can only implement it once. This means functions like /// [`read_byte_no_error`](crate::bytestream::ZByteReaderTrait::read_byte_no_error) are slower than they should be for `Cursor`. /// pub trait ZByteReaderTrait { /// Read a single byte from the decoder and return /// `0` if we can't read the byte, e.g because of EOF /// /// The implementation should try to be as fast as possible as this is called /// from some hot loops where it may become the bottleneck fn read_byte_no_error(&mut self) -> u8; /// Read exact bytes required to fill `buf` or return an error if that isn't possible /// /// ## Arguments /// - `buf`: Buffer to fill with bytes from the underlying reader /// ## Errors /// In case of an error, the implementation should not increment the internal position fn read_exact_bytes(&mut self, buf: &mut [u8]) -> Result<(), ZByteIoError>; /// Read exact bytes required to fill `buf` or return an error if that isn't possible /// /// This is the same as [`read_exact_bytes`](Self::read_exact_bytes) but implemented as a separate /// method to allow some implementations to optimize it to cost fewer instructions /// /// ## Arguments /// - `buf`: Buffer to fill with bytes from the underlying reader /// ## Errors /// In case of an error, the implementation should not increment the internal position fn read_const_bytes(&mut self, buf: &mut [u8; N]) -> Result<(), ZByteIoError>; /// Read exact bytes required to fill `buf` or ignore buf entirely if you can't fill it /// due to an error like the inability to fill the buffer completely /// ## Arguments /// - `buf`: Buffer to fill with bytes from the underlying reader /// ## Errors /// In case of an error, the implementation should not increment the internal position fn read_const_bytes_no_error(&mut self, buf: &mut [u8; N]); /// Read bytes into `buf` returning how many bytes you have read or an error if one occurred /// /// This doesn't guarantee that buf will be filled with bytes for such a guarantee see /// [`read_exact_bytes`](Self::read_exact_bytes) /// /// ## Arguments /// - `buf`: The buffer to fill with bytes /// /// ## Returns /// - `Ok(usize)` - Actual bytes read into the buffer /// - `Err()` - The error encountered when reading bytes for which we couldn't recover fn read_bytes(&mut self, buf: &mut [u8]) -> Result; /// Reads data into provided buffer but does not advance read position. /// /// fn peek_bytes(&mut self, buf: &mut [u8]) -> Result; fn peek_exact_bytes(&mut self, buf: &mut [u8]) -> Result<(), ZByteIoError>; /// Seek into a new position from the buffer /// /// This is similar to the [seek](std::io::Seek::seek) function in the [Seek](std::io::Seek) trait /// but implemented to work for no-std environments fn z_seek(&mut self, from: ZSeekFrom) -> Result; /// Report whether we are at the end of a stream. /// /// ## Warning /// This may cause an additional syscall e.g when we are reading from a file, we must query the file /// multiple times to check if we really are at the end of the file and the user didn't sneakily /// add more contents to it hence use it with care /// /// ## Returns /// - `Ok(bool)` - The answer to whether or not we are at end of file /// - `Err()` - The error that occurred when we queried the underlying reader if we were at EOF fn is_eof(&mut self) -> Result; /// Return the current position of the inner cursor. /// /// This can be used to check the advancement of the cursor fn z_position(&mut self) -> Result; /// Read all bytes remaining in this input to `sink` until we hit eof /// /// # Returns /// - `Ok(usize)` The actual number of bytes added to the sink /// - `Err()` An error that occurred when reading bytes fn read_remaining(&mut self, sink: &mut alloc::vec::Vec) -> Result; } /// The writer trait implemented for zune-image library of encoders /// /// Anything that implements this trait can be used as a sink /// for writing encoded images pub trait ZByteWriterTrait { /// Write some bytes into the sink returning number of bytes written or /// an error if something bad happened /// /// An implementation is free to write less bytes that are in buf, so the bytes written /// cannot be guaranteed to be fully written fn write_bytes(&mut self, buf: &[u8]) -> Result; /// Write all bytes to the buffer or return an error if something occurred /// /// This will always write all bytes, if it can't fully write all bytes, it will /// error out fn write_all_bytes(&mut self, buf: &[u8]) -> Result<(), ZByteIoError>; /// Write a fixed number of bytes and error out if we can't write the bytes /// /// This is provided to allow for optimized writes where possible. (when the compiler can const fold them) fn write_const_bytes(&mut self, buf: &[u8; N]) -> Result<(), ZByteIoError>; /// Ensure bytes are written to the sink. /// /// Implementations should treat this like linux `fsync`, and should implement /// whatever writer's implementation of fsync should look like /// /// After this, the encoder should be able to guarantee that all in-core data is synced with the /// storage decive fn flush_bytes(&mut self) -> Result<(), ZByteIoError>; /// A hint to tell the implementation how big of a size we expect the image to be /// An implementation like in memory `Vec` can use this to reserve additional memory to /// prevent reallocation when encoding /// /// This is just a hint, akin to calling `Vec::reserve` and should be treated as such. /// If your implementation doesn't support such, e.g file or mutable slices, it's okay to return /// `Ok(())` fn reserve_capacity(&mut self, size: usize) -> Result<(), ZByteIoError>; } zune-core-0.5.1/src/bytestream/writer/no_std_writer.rs000064400000000000000000000044501046102023000212530ustar 00000000000000// We cannot use the below impls and std ones because we'll re-implement the // same trait fot &[u8] which is blanketed by write. Ending up with two separate implementations #![cfg(not(feature = "std"))] use crate::bytestream::{ZByteIoError, ZByteWriterTrait}; impl ZByteWriterTrait for &mut [u8] { fn write_bytes(&mut self, buf: &[u8]) -> Result { // got from the write of std let amt = core::cmp::min(buf.len(), self.len()); let (a, b) = core::mem::take(self).split_at_mut(amt); a.copy_from_slice(&buf[..amt]); *self = b; Ok(amt) } fn write_all_bytes(&mut self, buf: &[u8]) -> Result<(), ZByteIoError> { if buf.len() > self.len() { return Err(ZByteIoError::NotEnoughBuffer(self.len(), buf.len())); } let amt = core::cmp::min(buf.len(), self.len()); let (a, b) = core::mem::take(self).split_at_mut(amt); a.copy_from_slice(&buf[..amt]); *self = b; Ok(()) } fn write_const_bytes(&mut self, buf: &[u8; N]) -> Result<(), ZByteIoError> { if N > self.len() { return Err(ZByteIoError::NotEnoughBuffer(self.len(), N)); } let amt = core::cmp::min(buf.len(), self.len()); let (a, b) = core::mem::take(self).split_at_mut(amt); a.copy_from_slice(&buf[..amt]); *self = b; Ok(()) } fn flush_bytes(&mut self) -> Result<(), ZByteIoError> { Ok(()) } fn reserve_capacity(&mut self, _: usize) -> Result<(), ZByteIoError> { // can't really pre-allocate anything here Ok(()) } } impl ZByteWriterTrait for &mut alloc::vec::Vec { fn write_bytes(&mut self, buf: &[u8]) -> Result { self.extend_from_slice(buf); Ok(buf.len()) } fn write_all_bytes(&mut self, buf: &[u8]) -> Result<(), ZByteIoError> { self.extend_from_slice(buf); Ok(()) } fn write_const_bytes(&mut self, buf: &[u8; N]) -> Result<(), ZByteIoError> { self.extend_from_slice(buf); Ok(()) } fn flush_bytes(&mut self) -> Result<(), ZByteIoError> { Ok(()) } fn reserve_capacity(&mut self, size: usize) -> Result<(), ZByteIoError> { self.reserve(size); Ok(()) } } zune-core-0.5.1/src/bytestream/writer/std_writer.rs000064400000000000000000000015561046102023000205630ustar 00000000000000#![cfg(feature = "std")] use std::io::Write; use crate::bytestream::ZByteIoError; impl crate::bytestream::ZByteWriterTrait for T { fn write_bytes(&mut self, buf: &[u8]) -> Result { self.write(buf).map_err(ZByteIoError::StdIoError) } fn write_all_bytes(&mut self, buf: &[u8]) -> Result<(), ZByteIoError> { self.write_all(buf).map_err(ZByteIoError::StdIoError) } fn write_const_bytes(&mut self, buf: &[u8; N]) -> Result<(), ZByteIoError> { self.write_all_bytes(buf) } fn flush_bytes(&mut self) -> Result<(), ZByteIoError> { self.flush().map_err(ZByteIoError::StdIoError) } fn reserve_capacity(&mut self, _: usize) -> Result<(), ZByteIoError> { // we can't reserve capacity, sorry to implementations where this // matters Ok(()) } } zune-core-0.5.1/src/bytestream/writer.rs000064400000000000000000000204211046102023000163650ustar 00000000000000/* * Copyright (c) 2023. * * This software is free software; * * You can redistribute it or modify it under terms of the MIT, Apache License or Zlib license */ use crate::bytestream::{ZByteIoError, ZByteWriterTrait}; mod no_std_writer; mod std_writer; enum Mode { // Big endian BE, // Little Endian LE } /// Encapsulates a simple Byte writer with /// support for Endian aware writes pub struct ZWriter { buffer: T, bytes_written: usize } impl ZWriter { /// Write bytes from the buf into the bytestream /// and return how many bytes were written /// /// # Arguments /// - `buf`: The bytes to be written to the bytestream /// /// # Returns /// - `Ok(usize)` - Number of bytes written /// This number may be less than `buf.len()` if the length of the buffer is greater /// than the internal bytestream length /// /// If you want to be sure that all bytes were written, see [`write_all`](Self::write_all) /// #[inline] pub fn write(&mut self, buf: &[u8]) -> Result { let bytes_written = self.buffer.write_bytes(buf)?; self.bytes_written += bytes_written; Ok(bytes_written) } /// Write all bytes from `buf` into the bytestream and return /// and panic if not all bytes were written to the bytestream /// /// # Arguments /// - `buf`: The bytes to be written into the bytestream /// ///# Returns /// - `Ok(())`: Indicates all bytes were written into the bytestream /// - `Err(&static str)`: In case all the bytes could not be written /// to the stream pub fn write_all(&mut self, buf: &[u8]) -> Result<(), ZByteIoError> { self.buffer.write_all_bytes(buf)?; self.bytes_written += buf.len(); Ok(()) } /// Create a new bytestream writer /// Bytes are written from the start to the end and not assumptions /// are made of the nature of the underlying stream /// /// # Arguments pub fn new(data: T) -> ZWriter { ZWriter { buffer: data, bytes_written: 0 } } /// Write a single byte into the bytestream or error out /// if there is not enough space /// /// # Example /// ``` /// use zune_core::bytestream::ZWriter; /// let mut buf = [0;10]; /// let mut stream = ZWriter::new(&mut buf[..]); /// assert!(stream.write_u8_err(34).is_ok()); /// ``` /// No space /// ``` /// use zune_core::bytestream::ZWriter; /// let mut no_space = []; /// let mut stream = ZWriter::new(&mut no_space[..]); /// assert!(stream.write_u8_err(32).is_err()); /// ``` /// #[inline] pub fn write_u8_err(&mut self, byte: u8) -> Result<(), ZByteIoError> { self.write_const_bytes(&[byte]) } /// Write a fixed compile time known number of bytes to the sink /// /// This is provided since some implementations can optimize such writes by eliminating /// some redundant code. #[inline] pub fn write_const_bytes( &mut self, byte: &[u8; N] ) -> Result<(), ZByteIoError> { self.buffer.write_const_bytes(byte)?; self.bytes_written += N; Ok(()) } /// Write a single byte in the stream or don't write /// anything if the buffer is full and cannot support the byte read /// #[inline] pub fn write_u8(&mut self, byte: u8) { let _ = self.write_const_bytes(&[byte]); } /// Return the number of bytes written by this encoder /// /// The encoder keeps information of how many bytes were written and this method /// returns that value. /// /// # Returns /// Number of bytes written pub fn bytes_written(&self) -> usize { self.bytes_written } /// Reserve some additional space to write. /// /// Some sinks like `Vec` allow reallocation and to prevent too much reallocation /// one can use this to reserve additional space to encode /// /// # Example /// /// ``` /// use zune_core::bytestream::ZWriter; /// let space_needed = 10; // Assume the image will fit into 10 bytes /// let mut output = Vec::new(); /// let mut sink = ZWriter::new(&mut output); /// // now reserve some space ///sink.reserve(space_needed).unwrap(); /// // at this point, we can assume that ZWriter allocated space for output /// ``` pub fn reserve(&mut self, additional: usize) -> Result<(), ZByteIoError> { self.buffer.reserve_capacity(additional) } /// Consume the writer and return the inner sink /// we were writing to. /// /// After this, the writer can no longer be used pub fn inner(self) -> T { self.buffer } /// Return an immutable reference to the inner sink pub fn inner_ref(&self) -> &T { &self.buffer } /// Return a mutable reference to the inner sink pub fn inner_mut(&mut self) -> &mut T { &mut self.buffer } } macro_rules! write_single_type { ($name:tt,$name2:tt,$name3:tt,$name4:tt,$name5:tt,$name6:tt,$int_type:tt) => { impl ZWriter { #[inline(always)] fn $name(&mut self, byte: $int_type, mode: Mode) -> Result<(), ZByteIoError> { // get bits, depending on mode. // This should be inlined and not visible in // the generated binary since mode is a compile // time constant. let bytes = match mode { Mode::BE => byte.to_be_bytes(), Mode::LE => byte.to_le_bytes() }; self.write_const_bytes(&bytes) } #[inline(always)] fn $name2(&mut self, byte: $int_type, mode: Mode) { // get bits, depending on mode. // This should be inlined and not visible in // the generated binary since mode is a compile // time constant. let bytes = match mode { Mode::BE => byte.to_be_bytes(), Mode::LE => byte.to_le_bytes() }; let _ = self.write_const_bytes(&bytes); } #[doc=concat!("Write ",stringify!($int_type)," as a big endian integer")] #[doc=concat!("Returning an error if the underlying buffer cannot support a ",stringify!($int_type)," write.")] #[inline] pub fn $name3(&mut self, byte: $int_type) -> Result<(), ZByteIoError> { self.$name(byte, Mode::BE) } #[doc=concat!("Write ",stringify!($int_type)," as a little endian integer")] #[doc=concat!("Returning an error if the underlying buffer cannot support a ",stringify!($int_type)," write.")] #[inline] pub fn $name4(&mut self, byte: $int_type) -> Result<(), ZByteIoError> { self.$name(byte, Mode::LE) } #[doc=concat!("Write ",stringify!($int_type)," as a big endian integer")] #[doc=concat!("Or don't write anything if the reader cannot support a ",stringify!($int_type)," write.")] #[inline] pub fn $name5(&mut self, byte: $int_type) { self.$name2(byte, Mode::BE) } #[doc=concat!("Write ",stringify!($int_type)," as a little endian integer")] #[doc=concat!("Or don't write anything if the reader cannot support a ",stringify!($int_type)," write.")] #[inline] pub fn $name6(&mut self, byte: $int_type) { self.$name2(byte, Mode::LE) } } }; } write_single_type!( write_u64_inner_or_die, write_u64_inner_or_none, write_u64_be_err, write_u64_le_err, write_u64_be, write_u64_le, u64 ); write_single_type!( write_u32_inner_or_die, write_u32_inner_or_none, write_u32_be_err, write_u32_le_err, write_u32_be, write_u32_le, u32 ); write_single_type!( write_u16_inner_or_die, write_u16_inner_or_none, write_u16_be_err, write_u16_le_err, write_u16_be, write_u16_le, u16 ); zune-core-0.5.1/src/bytestream.rs000064400000000000000000000012671046102023000150600ustar 00000000000000/* * Copyright (c) 2023. * * This software is free software; * * You can redistribute it or modify it under terms of the MIT, Apache License or Zlib license */ //! A simple implementation of a bytestream reader //! and writer. //! //! This module contains two main structs that help in //! byte reading and byte writing //! //! Useful for a lot of image readers and writers, it's put //! here to minimize code reuse pub use reader::{ZReader, ZSeekFrom}; pub use traits::*; pub use writer::ZWriter; pub use crate::bytestream::reader::no_std_readers::*; //use crate::bytestream::reader::std_readers::*; pub use crate::bytestream::reader::ZByteIoError; mod reader; mod traits; mod writer; zune-core-0.5.1/src/colorspace.rs000064400000000000000000000131761046102023000150350ustar 00000000000000/* * Copyright (c) 2023. * * This software is free software; * * You can redistribute it or modify it under terms of the MIT, Apache License or Zlib license */ //! Image Colorspace information and manipulation utilities. /// All possible image colorspaces /// Some of them aren't yet supported exist here. #[allow(clippy::upper_case_acronyms)] #[derive(Copy, Clone, Debug, Eq, PartialEq)] #[non_exhaustive] pub enum ColorSpace { /// Red, Green , Blue RGB, /// Red, Green, Blue, Alpha RGBA, /// YUV colorspace YCbCr, /// Grayscale colorspace Luma, /// Grayscale with alpha colorspace LumaA, YCCK, /// Cyan , Magenta, Yellow, Black CMYK, /// Blue, Green, Red BGR, /// Blue, Green, Red, Alpha BGRA, /// The colorspace is unknown Unknown, /// Alpha Red Green Blue ARGB, /// Hue,Saturation,Lightness /// Conversion from RGB to HSL and back matches that of Python [colorsys](https://docs.python.org/3/library/colorsys.html) module /// Color type is expected to be in floating point HSL, /// Hue, Saturation,Value /// /// Conversion from RGB to HSV and back matches that of Python [colorsys](https://docs.python.org/3/library/colorsys.html) module /// Color type is expected to be in floating point HSV, /// Multiple arbitrary image channels. /// /// This introduces **limited** support for multi-band/multichannel images /// that can have n channels. /// /// The library contains optimized and well-supported routines for up to 4 image channels as is /// more common out there, but some pipelines may require multi-band image support. /// /// For operations, multi-band images are assumed to be n-channel images with no alpha /// to allow for generic processing, without necessarily caring for the underlying interpretation MultiBand(core::num::NonZeroU32) } impl ColorSpace { /// Number of color channels present for a certain colorspace /// /// E.g. RGB returns 3 since it contains R,G and B colors to make up a pixel pub const fn num_components(&self) -> usize { match self { Self::RGB | Self::YCbCr | Self::BGR | Self::HSV | Self::HSL => 3, Self::RGBA | Self::YCCK | Self::CMYK | Self::BGRA | Self::ARGB => 4, Self::Luma => 1, Self::LumaA => 2, Self::Unknown => 0, Self::MultiBand(n) => n.get() as usize } } pub const fn has_alpha(&self) -> bool { matches!(self, Self::RGBA | Self::LumaA | Self::BGRA | Self::ARGB) } pub const fn is_grayscale(&self) -> bool { matches!(self, Self::LumaA | Self::Luma) } /// Returns the position of the alpha pixel in a pixel /// /// /// That is for an array of color components say `[0,1,2,3]` if the image has an alpha channel /// and is in RGBA format, this will return `Some(3)`, indicating alpha is found in the third index /// but if the image is in `ARGB` format, it will return `Some(0)` indicating alpha is found in /// index 0 /// /// If an image doesn't have an alpha channel returns `None` /// pub const fn alpha_position(&self) -> Option { match self { ColorSpace::RGBA => Some(3), ColorSpace::LumaA => Some(1), ColorSpace::BGRA => Some(3), ColorSpace::ARGB => Some(0), _ => None } } } /// Encapsulates all colorspaces supported by /// the library /// /// This explicitly leaves out multi-band images pub static ALL_COLORSPACES: [ColorSpace; 12] = [ ColorSpace::RGB, ColorSpace::RGBA, ColorSpace::LumaA, ColorSpace::Luma, ColorSpace::CMYK, ColorSpace::BGRA, ColorSpace::BGR, ColorSpace::YCCK, ColorSpace::YCbCr, ColorSpace::ARGB, ColorSpace::HSL, ColorSpace::HSV ]; /// Color characteristics /// /// Gives more information about values in a certain /// colorspace #[allow(non_camel_case_types)] #[derive(Copy, Clone, Debug, PartialEq)] pub enum ColorCharacteristics { /// sRGB Transfer function sRGB, /// Rec.709 Transfer function Rec709, /// Pure gamma 2.2 Transfer function, ITU-R 470M Gamma2p2, /// Pure gamma 2.8 Transfer function, ITU-R 470BG Gamma2p8, /// Smpte 428 Transfer function Smpte428, /// Log100 Transfer function Log100, /// Log100Sqrt10 Transfer function Log100Sqrt10, /// Bt1361 Transfer function Bt1361, /// Smpte 240 Transfer function Smpte240, /// IEC 61966 Transfer function Iec61966, /// Linear transfer function Linear } /// Represents a single channel color primary. /// /// This can be viewed as a 3D coordinate of the color primary /// for a given colorspace #[derive(Default, Debug, Copy, Clone)] pub struct SingleColorPrimary { pub x: f64, pub y: f64, pub z: f64 } /// A collection of red,green and blue color primaries placed /// in one struct for easy manipulation #[derive(Default, Debug, Copy, Clone)] pub struct ColorPrimaries { /// Red color primaries pub red: SingleColorPrimary, /// Green color primaries pub green: SingleColorPrimary, /// Blue color primaries pub blue: SingleColorPrimary } /// Rendering intents indicate what one may want to do with colors outside of it's gamut /// /// /// Further reading /// - [IBM Rendering Intent](https://www.ibm.com/docs/en/i/7.5?topic=management-rendering-intents) /// - [ColorGate Blog](https://blog.colorgate.com/en/rendering-intent-explained) #[derive(Eq, PartialEq, Clone, Copy, Debug)] pub enum RenderingIntent { AbsoluteColorimetric, Saturation, RelativeColorimetric, Perceptual } zune-core-0.5.1/src/lib.rs000064400000000000000000000034611046102023000134450ustar 00000000000000/* * Copyright (c) 2023. * * This software is free software; You can redistribute it or modify it under terms of the MIT, Apache License or Zlib license */ //! Core routines shared by all libraries //! //! This crate provides a set of core routines shared //! by the decoders and encoders under `zune` umbrella //! //! It currently contains //! //! - A bytestream reader and writer with endian aware reads and writes //! - Colorspace and bit depth information shared by images //! - Image decoder and encoder options //! - A simple enum type to hold image decoding results. //! //! This library is `#[no_std]` with `alloc` feature needed for defining `Vec` //! which we need for storing decoded bytes. //! //! //! # Features //! - `no_std`: Enables `#[no_std]` compilation support. //! //! - `serde`: Enables serializing of some of the data structures //! present in the crate //! //! //! # Input/Output //! //! zune-image supports many different input and output devices. For input readers //! we can read anything that implements `BufRead` + `Seek` and provide an optimized routine for //! handling in memory buffers by using [`ZCursor`](crate::bytestream::ZCursor). //! //! For output, we support anything that implements `Write` trait, this includes files, standard io streams //! network sockets, etc //! //! In a `no_std` environment. We can write to in memory buffers `&mut [u8]` and `&mut Vec` //! //! If you have an in memory buffer, use [`ZCursor`](crate::bytestream::ZCursor), //! it's optimized for in memory buffers. //! //! //! #![cfg_attr(not(feature = "std"), no_std)] #![macro_use] extern crate alloc; extern crate core; #[cfg(not(feature = "log"))] pub mod log; #[cfg(feature = "log")] pub use log; pub mod bit_depth; pub mod bytestream; pub mod colorspace; pub mod options; pub mod result; mod serde; zune-core-0.5.1/src/log.rs000064400000000000000000000025151046102023000134570ustar 00000000000000/* * Copyright (c) 2023. * * This software is free software; * * You can redistribute it or modify it under terms of the MIT, Apache License or Zlib license */ // #[macro_export] is required to make macros works across crates // but it always put the macro in the crate root. // #[doc(hidden)] + "pub use" is a workaround to namespace a macro. pub use crate::{ __debug as debug, __error as error, __info as info, __log_enabled as log_enabled, __trace as trace, __warn as warn }; #[repr(usize)] #[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)] pub enum Level { Error = 1, Warn, Info, Debug, Trace } #[doc(hidden)] #[macro_export] macro_rules! __log_enabled { ($lvl:expr) => {{ let _ = $lvl; false }}; } #[doc(hidden)] #[macro_export] macro_rules! __error { ($($arg:tt)+) => { #[cfg(feature = "std")] { //eprintln!($($arg)+); } }; } #[doc(hidden)] #[macro_export] macro_rules! __warn { ($($arg:tt)+) => { #[cfg(feature = "std")] { //eprintln!($($arg)+); } }; } #[doc(hidden)] #[macro_export] macro_rules! __info { ($($arg:tt)+) => {}; } #[doc(hidden)] #[macro_export] macro_rules! __debug { ($($arg:tt)+) => {}; } #[doc(hidden)] #[macro_export] macro_rules! __trace { ($($arg:tt)+) => {}; } zune-core-0.5.1/src/options/decoder.rs000064400000000000000000000530461046102023000160030ustar 00000000000000/* * Copyright (c) 2023. * * This software is free software; * * You can redistribute it or modify it under terms of the MIT, Apache License or Zlib license */ //! Global Decoder options #![allow(clippy::zero_prefixed_literal)] use crate::bit_depth::ByteEndian; use crate::colorspace::ColorSpace; /// A decoder that can handle errors fn decoder_error_tolerance_mode() -> DecoderFlags { // similar to fast options currently, so no need to write a new one fast_options() } /// Fast decoder options /// /// Enables all intrinsics + unsafe routines /// /// Disables png adler and crc checking. fn fast_options() -> DecoderFlags { DecoderFlags { inflate_confirm_adler: false, png_confirm_crc: false, jpg_error_on_non_conformance: false, zune_use_unsafe: true, zune_use_neon: true, zune_use_avx: true, zune_use_avx2: true, zune_use_sse2: true, zune_use_sse3: true, zune_use_sse41: true, png_add_alpha_channel: false, png_strip_16_bit_to_8_bit: false, png_decode_animated: true, jxl_decode_animated: true } } /// Command line options error resilient and fast /// /// Features /// - Ignore CRC and Adler in png /// - Do not error out on non-conformance in jpg /// - Use unsafe paths fn cmd_options() -> DecoderFlags { DecoderFlags { inflate_confirm_adler: false, png_confirm_crc: false, jpg_error_on_non_conformance: false, zune_use_unsafe: true, zune_use_neon: true, zune_use_avx: true, zune_use_avx2: true, zune_use_sse2: true, zune_use_sse3: true, zune_use_sse41: true, png_add_alpha_channel: false, png_strip_16_bit_to_8_bit: false, png_decode_animated: true, jxl_decode_animated: true } } /// Decoder options that are flags /// /// NOTE: When you extend this, add true or false to /// all options above that return a `DecoderFlag` #[derive(Copy, Debug, Clone, Default)] pub struct DecoderFlags { /// Whether the decoder should confirm and report adler mismatch inflate_confirm_adler: bool, /// Whether the PNG decoder should confirm crc png_confirm_crc: bool, /// Whether the png decoder should error out on image non-conformance jpg_error_on_non_conformance: bool, /// Whether the decoder should use unsafe platform specific intrinsics /// /// This will also shut down platform specific intrinsics `(ZUNE_USE_{EXT})` value zune_use_unsafe: bool, /// Whether we should use SSE2. /// /// This should be enabled for all x64 platforms but can be turned off if /// `ZUNE_USE_UNSAFE` is false zune_use_sse2: bool, /// Whether we should use SSE3 instructions where possible. zune_use_sse3: bool, /// Whether we should use sse4.1 instructions where possible. zune_use_sse41: bool, /// Whether we should use avx instructions where possible. zune_use_avx: bool, /// Whether we should use avx2 instructions where possible. zune_use_avx2: bool, /// Whether the png decoder should add alpha channel where possible. png_add_alpha_channel: bool, /// Whether we should use neon instructions where possible. zune_use_neon: bool, /// Whether the png decoder should strip 16 bit to 8 bit png_strip_16_bit_to_8_bit: bool, /// Decode all frames for an animated images png_decode_animated: bool, jxl_decode_animated: bool } /// Decoder options /// /// Not all options are respected by decoders all decoders #[derive(Debug, Copy, Clone)] pub struct DecoderOptions { /// Maximum width for which decoders will /// not try to decode images larger than /// the specified width. /// /// - Default value: 16384 /// - Respected by: `all decoders` max_width: usize, /// Maximum height for which decoders will not /// try to decode images larger than the /// specified height /// /// - Default value: 16384 /// - Respected by: `all decoders` max_height: usize, /// Output colorspace /// /// The jpeg decoder allows conversion to a separate colorspace /// than the input. /// /// I.e you can convert a RGB jpeg image to grayscale without /// first decoding it to RGB to get /// /// - Default value: `ColorSpace::RGB` /// - Respected by: `jpeg` out_colorspace: ColorSpace, /// Maximum number of scans allowed /// for progressive jpeg images /// /// Progressive jpegs have scans /// /// - Default value:100 /// - Respected by: `jpeg` max_scans: usize, /// Maximum size for deflate. /// Respected by all decoders that use inflate/deflate deflate_limit: usize, /// Boolean flags that influence decoding flags: DecoderFlags, /// The byte endian of the returned bytes will be stored in /// in case a single pixel spans more than a byte endianness: ByteEndian } /// Initializers impl DecoderOptions { /// Create the decoder with options setting most configurable /// options to be their safe counterparts /// /// This is the same as `default` option as default initializes /// options to the safe variant. /// /// Note, decoders running on this will be slower as it disables /// platform specific intrinsics pub fn new_safe() -> DecoderOptions { DecoderOptions::default() } /// Create the decoder with options setting the configurable options /// to the fast counterparts /// /// This enables platform specific code paths and enable use of unsafe pub fn new_fast() -> DecoderOptions { let flag = fast_options(); DecoderOptions::default().set_decoder_flags(flag) } /// Create the decoder options with the following characteristics /// /// - Use unsafe paths. /// - Ignore error checksuming, e.g in png we do not confirm adler and crc in this mode /// - Enable fast intrinsics paths pub fn new_cmd() -> DecoderOptions { let flag = cmd_options(); DecoderOptions::default().set_decoder_flags(flag) } } /// Global options respected by all decoders impl DecoderOptions { /// Get maximum width configured for which the decoder /// should not try to decode images greater than this width pub const fn max_width(&self) -> usize { self.max_width } /// Get maximum height configured for which the decoder should /// not try to decode images greater than this height pub const fn max_height(&self) -> usize { self.max_height } /// Return true whether the decoder should be in strict mode /// And reject most errors pub fn strict_mode(&self) -> bool { self.flags.jpg_error_on_non_conformance | self.flags.png_confirm_crc | self.flags.inflate_confirm_adler } /// Return true if the decoder should use unsafe /// routines where possible pub const fn use_unsafe(&self) -> bool { self.flags.zune_use_unsafe } /// Set maximum width for which the decoder should not try /// decoding images greater than that width /// /// # Arguments /// /// * `width`: The maximum width allowed /// /// returns: DecoderOptions pub fn set_max_width(mut self, width: usize) -> Self { self.max_width = width; self } /// Set maximum height for which the decoder should not try /// decoding images greater than that height /// # Arguments /// /// * `height`: The maximum height allowed /// /// returns: DecoderOptions /// pub fn set_max_height(mut self, height: usize) -> Self { self.max_height = height; self } /// Whether the routines can use unsafe platform specific /// intrinsics when necessary /// /// Platform intrinsics are implemented for operations which /// the compiler can't auto-vectorize, or we can do a marginably /// better job at it /// /// All decoders with unsafe routines respect it. /// /// Treat this with caution, disabling it will cause slowdowns but /// it's provided for mainly for debugging use. /// /// - Respected by: `png` and `jpeg`(decoders with unsafe routines) pub fn set_use_unsafe(mut self, yes: bool) -> Self { // first clear the flag self.flags.zune_use_unsafe = yes; self } fn set_decoder_flags(mut self, flags: DecoderFlags) -> Self { self.flags = flags; self } /// Set whether the decoder should be in standards conforming/ /// strict mode /// /// This reduces the error tolerance level for the decoders and invalid /// samples will be rejected by the decoder /// /// # Arguments /// /// * `yes`: /// /// returns: DecoderOptions /// pub fn set_strict_mode(mut self, yes: bool) -> Self { self.flags.jpg_error_on_non_conformance = yes; self.flags.png_confirm_crc = yes; self.flags.inflate_confirm_adler = yes; self } /// Set the byte endian for which raw samples will be stored in /// in case a single pixel sample spans more than a byte. /// /// The default is usually native endian hence big endian values /// will be converted to little endian on little endian systems, /// /// and little endian values will be converted to big endian on big endian systems /// /// # Arguments /// /// * `endian`: The endianness to which to set the bytes to /// /// returns: DecoderOptions pub fn set_byte_endian(mut self, endian: ByteEndian) -> Self { self.endianness = endian; self } /// Get the byte endian for which samples that span more than one byte will /// be treated pub const fn byte_endian(&self) -> ByteEndian { self.endianness } } /// PNG specific options impl DecoderOptions { /// Whether the inflate decoder should confirm /// adler checksums pub const fn inflate_get_confirm_adler(&self) -> bool { self.flags.inflate_confirm_adler } /// Set whether the inflate decoder should confirm /// adler checksums pub fn inflate_set_confirm_adler(mut self, yes: bool) -> Self { self.flags.inflate_confirm_adler = yes; self } /// Get default inflate limit for which the decoder /// will not try to decompress further pub const fn inflate_get_limit(&self) -> usize { self.deflate_limit } /// Set the default inflate limit for which decompressors /// relying on inflate won't surpass this limit #[must_use] pub fn inflate_set_limit(mut self, limit: usize) -> Self { self.deflate_limit = limit; self } /// Whether the inflate decoder should confirm /// crc 32 checksums pub const fn png_get_confirm_crc(&self) -> bool { self.flags.png_confirm_crc } /// Set whether the png decoder should confirm /// CRC 32 checksums #[must_use] pub fn png_set_confirm_crc(mut self, yes: bool) -> Self { self.flags.png_confirm_crc = yes; self } /// Set whether the png decoder should add an alpha channel to /// images where possible. /// /// For Luma images, it converts it to Luma+Alpha /// /// For RGB images it converts it to RGB+Alpha pub fn png_set_add_alpha_channel(mut self, yes: bool) -> Self { self.flags.png_add_alpha_channel = yes; self } /// Return true whether the png decoder should add an alpha /// channel to images where possible pub const fn png_get_add_alpha_channel(&self) -> bool { self.flags.png_add_alpha_channel } /// Whether the png decoder should reduce 16 bit images to 8 bit /// images implicitly. /// /// Equivalent to [png::Transformations::STRIP_16](https://docs.rs/png/latest/png/struct.Transformations.html#associatedconstant.STRIP_16) pub fn png_set_strip_to_8bit(mut self, yes: bool) -> Self { self.flags.png_strip_16_bit_to_8_bit = yes; self } /// Return a boolean indicating whether the png decoder should reduce /// 16 bit images to 8 bit images implicitly pub const fn png_get_strip_to_8bit(&self) -> bool { self.flags.png_strip_16_bit_to_8_bit } /// Return whether `zune-image` should decode animated images or /// whether we should just decode the first frame only pub const fn png_decode_animated(&self) -> bool { self.flags.png_decode_animated } /// Set whether `zune-image` should decode animated images or /// whether we should just decode the first frame only pub const fn png_set_decode_animated(mut self, yes: bool) -> Self { self.flags.png_decode_animated = yes; self } } /// JPEG specific options impl DecoderOptions { /// Get maximum scans for which the jpeg decoder /// should not go above for progressive images pub const fn jpeg_get_max_scans(&self) -> usize { self.max_scans } /// Set maximum scans for which the jpeg decoder should /// not exceed when reconstructing images. pub fn jpeg_set_max_scans(mut self, max_scans: usize) -> Self { self.max_scans = max_scans; self } /// Get expected output colorspace set by the user for which the image /// is expected to be reconstructed into. /// /// This may be different from the pub const fn jpeg_get_out_colorspace(&self) -> ColorSpace { self.out_colorspace } /// Set expected colorspace for which the jpeg output is expected to be in /// /// This is mainly provided as is, we do not guarantee the decoder can convert to all colorspaces /// and the decoder can change it internally when it sees fit. #[must_use] pub fn jpeg_set_out_colorspace(mut self, colorspace: ColorSpace) -> Self { self.out_colorspace = colorspace; self } } /// Intrinsics support /// /// These routines are compiled depending /// on the platform they are used, if compiled for a platform /// it doesn't support,(e.g avx2 on Arm), it will always return `false` impl DecoderOptions { /// Use SSE 2 code paths where possible /// /// This checks for existence of SSE2 first and returns /// false if it's not present #[allow(unreachable_code)] pub fn use_sse2(&self) -> bool { let opt = self.flags.zune_use_sse2 | self.flags.zune_use_unsafe; // options says no if !opt { return false; } #[cfg(any(target_arch = "x86_64", target_arch = "x86"))] { // where we can do runtime check if feature is present #[cfg(feature = "std")] { if is_x86_feature_detected!("sse2") { return true; } } // where we can't do runtime check if feature is present // check if the compile feature had it enabled #[cfg(all(not(feature = "std"), target_feature = "sse2"))] { return true; } } // everything failed return false false } /// Use SSE 3 paths where possible /// /// /// This also checks for SSE3 support and returns false if /// it's not present #[allow(unreachable_code)] pub fn use_sse3(&self) -> bool { let opt = self.flags.zune_use_sse3 | self.flags.zune_use_unsafe; // options says no if !opt { return false; } #[cfg(any(target_arch = "x86_64", target_arch = "x86"))] { // where we can do runtime check if feature is present #[cfg(feature = "std")] { if is_x86_feature_detected!("sse3") { return true; } } // where we can't do runtime check if feature is present // check if the compile feature had it enabled #[cfg(all(not(feature = "std"), target_feature = "sse3"))] { return true; } } // everything failed return false false } /// Use SSE4 paths where possible /// /// This also checks for sse 4.1 support and returns false if it /// is not present #[allow(unreachable_code)] pub fn use_sse41(&self) -> bool { let opt = self.flags.zune_use_sse41 | self.flags.zune_use_unsafe; // options says no if !opt { return false; } #[cfg(any(target_arch = "x86_64", target_arch = "x86"))] { // where we can do runtime check if feature is present #[cfg(feature = "std")] { if is_x86_feature_detected!("sse4.1") { return true; } } // where we can't do runtime check if feature is present // check if the compile feature had it enabled #[cfg(all(not(feature = "std"), target_feature = "sse4.1"))] { return true; } } // everything failed return false false } /// Use AVX paths where possible /// /// This also checks for AVX support and returns false if it's /// not present #[allow(unreachable_code)] pub fn use_avx(&self) -> bool { let opt = self.flags.zune_use_avx | self.flags.zune_use_unsafe; // options says no if !opt { return false; } #[cfg(any(target_arch = "x86_64", target_arch = "x86"))] { // where we can do runtime check if feature is present #[cfg(feature = "std")] { if is_x86_feature_detected!("avx") { return true; } } // where we can't do runitme check if feature is present // check if the compile feature had it enabled #[cfg(all(not(feature = "std"), target_feature = "avx"))] { return true; } } // everything failed return false false } /// Use avx2 paths where possible /// /// This also checks for AVX2 support and returns false if it's not /// present #[allow(unreachable_code)] pub fn use_avx2(&self) -> bool { let opt = self.flags.zune_use_avx2 | self.flags.zune_use_unsafe; // options says no if !opt { return false; } #[cfg(any(target_arch = "x86_64", target_arch = "x86"))] { // where we can do runtime check if feature is present #[cfg(feature = "std")] { if is_x86_feature_detected!("avx2") { return true; } } // where we can't do runitme check if feature is present // check if the compile feature had it enabled #[cfg(all(not(feature = "std"), target_feature = "avx2"))] { return true; } } // everything failed return false false } #[allow(unreachable_code)] pub fn use_neon(&self) -> bool { let opt = self.flags.zune_use_neon | self.flags.zune_use_unsafe; // options says no if !opt { return false; } #[cfg(target_arch = "aarch64")] { // aarch64 implies neon on a compliant cpu // but for real prod should do something better here return true; } // everything failed return false false } } /// JPEG_XL specific options impl DecoderOptions { /// Return whether `zune-image` should decode animated images or /// whether we should just decode the first frame only pub const fn jxl_decode_animated(&self) -> bool { self.flags.jxl_decode_animated } /// Set whether `zune-image` should decode animated images or /// whether we should just decode the first frame only pub const fn jxl_set_decode_animated(mut self, yes: bool) -> Self { self.flags.jxl_decode_animated = yes; self } } impl Default for DecoderOptions { /// Create a default and sane option for decoders /// /// The following are the defaults /// /// - All decoders /// - max_width: 16536 /// - max_height: 16535 /// - use_unsafe: Use unsafe intrinsics where possible. /// /// - JPEG /// - max_scans: 100 (progressive images only, artificial cap to prevent a specific DOS) /// - error_on_non_conformance: False (slightly corrupt images will be allowed) /// - DEFLATE /// - deflate_limit: 1GB (will not continue decoding deflate archives larger than this) /// - PNG /// - endianness: Default endianess is Big Endian when decoding 16 bit images to be viewed as 8 byte images /// - confirm_crc: False (CRC will not be confirmed to be safe) /// - strip_16_bit_to_8: False, 16 bit images are handled as 16 bit images /// - add alpha: False, alpha channel is not added where it isn't present /// - decode_animated: True: All frames in an animated image are decoded /// /// - JXL /// - decode_animated: True: All frames in an animated image are decoded /// fn default() -> Self { Self { out_colorspace: ColorSpace::RGB, max_width: 1 << 14, max_height: 1 << 14, max_scans: 100, deflate_limit: 1 << 30, flags: decoder_error_tolerance_mode(), endianness: ByteEndian::BE } } } zune-core-0.5.1/src/options/encoder.rs000064400000000000000000000143041046102023000160070ustar 00000000000000/* * Copyright (c) 2023. * * This software is free software; * * You can redistribute it or modify it under terms of the MIT, Apache License or Zlib license */ use crate::bit_depth::BitDepth; use crate::colorspace::ColorSpace; /// Encoder options that are flags #[derive(Copy, Debug, Clone, Default)] struct EncoderFlags { /// Whether JPEG images should be encoded as progressive images jpeg_encode_progressive: bool, /// Whether JPEG images should use optimized huffman tables jpeg_optimize_huffman: bool, /// Whether to not preserve metadata across image transformations image_strip_metadata: bool } /// Options shared by some of the encoders in /// the `zune-` family of image crates #[derive(Debug, Copy, Clone)] pub struct EncoderOptions { width: usize, height: usize, colorspace: ColorSpace, quality: u8, depth: BitDepth, num_threads: u8, effort: u8, flags: EncoderFlags } impl Default for EncoderOptions { fn default() -> Self { Self { width: 0, height: 0, colorspace: ColorSpace::RGB, quality: 80, depth: BitDepth::Eight, num_threads: 4, effort: 4, flags: EncoderFlags::default() } } } impl EncoderOptions { /// Create new encode options /// /// # Arguments /// /// * `width`: Image width /// * `height`: Image height /// * `colorspace`: Image colorspaces /// * `depth`: Image depth /// /// returns: EncoderOptions /// pub fn new( width: usize, height: usize, colorspace: ColorSpace, depth: BitDepth ) -> EncoderOptions { EncoderOptions { width, height, colorspace, depth, ..Default::default() } } /// Get the width for which the image will be encoded in pub const fn width(&self) -> usize { self.width } /// Get height for which the image will be encoded in /// /// returns: usize /// /// # Panics /// If height is zero pub fn height(&self) -> usize { assert_ne!(self.height, 0); self.height } /// Get the depth for which the image will be encoded in pub const fn depth(&self) -> BitDepth { self.depth } /// Get the quality for which the image will be encoded with /// /// # Lossy /// - Higher quality means some images take longer to write and /// are big but they look good /// /// - Lower quality means small images and low quality. /// /// # Lossless /// - High quality indicates more time is spent in making the file /// smaller /// /// - Low quality indicates less time is spent in making the file bigger pub const fn quality(&self) -> u8 { self.quality } /// Get the colorspace for which the image will be encoded in pub const fn colorspace(&self) -> ColorSpace { self.colorspace } pub const fn effort(&self) -> u8 { self.effort } /// Set width for the image to be encoded pub fn set_width(mut self, width: usize) -> Self { self.width = width; self } /// Set height for the image to be encoded pub fn set_height(mut self, height: usize) -> Self { self.height = height; self } /// Set depth for the image to be encoded pub fn set_depth(mut self, depth: BitDepth) -> Self { self.depth = depth; self } /// Set quality of the image to be encoded /// /// Quality is clamped from 0..100 /// /// Quality means different options depending on the encoder, see /// [get_quality](Self::quality) pub fn set_quality(mut self, quality: u8) -> Self { self.quality = quality.clamp(0, 100); self } /// Set colorspace for the image to be encoded pub fn set_colorspace(mut self, colorspace: ColorSpace) -> Self { self.colorspace = colorspace; self } /// Set the number of threads allowed for multithreaded encoding /// where supported /// /// Zero means use a single thread pub fn set_num_threads(mut self, threads: u8) -> Self { self.num_threads = threads; self } pub fn set_effort(mut self, effort: u8) -> Self { self.effort = effort; self } /// Return number of threads configured for multithreading /// where possible /// /// This is used for multi-threaded encoders, /// currently only jpeg-xl pub const fn num_threads(&self) -> u8 { self.num_threads } /// Set whether the encoder should remove metadata from the image /// /// When set to `true`, supported encoders will strip away metadata /// from the resulting image. If set to false, where supported, encoders /// will not remove metadata from images pub fn set_strip_metadata(mut self, yes: bool) -> Self { self.flags.image_strip_metadata = yes; self } /// Whether or not the encoder should remove metadata from the image /// /// The default value is false, and encoders that respect this try to preserve as much /// data as possible from one image to another pub const fn strip_metadata(&self) -> bool { !self.flags.image_strip_metadata } } /// JPEG options impl EncoderOptions { /// Whether the jpeg encoder should encode the image in progressive mode /// /// Default is `false`. /// /// This may be used to create slightly smaller images at the cost of more processing /// time pub const fn jpeg_encode_progressive(&self) -> bool { self.flags.jpeg_encode_progressive } /// Whether the jpeg encoder should optimize huffman tables to create smaller files /// at the cost of processing time /// /// Default is `false`. pub const fn jpeg_optimized_huffman_tables(&self) -> bool { self.flags.jpeg_optimize_huffman } /// Set whether the jpeg encoder should encode the imagei in progressive mode /// /// Default is `false` pub fn set_jpeg_encode_progressive(mut self, yes: bool) -> Self { self.flags.jpeg_optimize_huffman = yes; self } } zune-core-0.5.1/src/options.rs000064400000000000000000000006271046102023000143730ustar 00000000000000//! Decoder and Encoder Options //! //! This module exposes a struct for which all implemented //! decoders get shared options for decoding //! //! All supported options are put into one _Options to allow for global configurations //! options e.g the same `DecoderOption` can be reused for all other decoders //! pub use decoder::DecoderOptions; pub use encoder::EncoderOptions; mod decoder; mod encoder; zune-core-0.5.1/src/result.rs000064400000000000000000000036431046102023000142170ustar 00000000000000/* * Copyright (c) 2023. * * This software is free software; * * You can redistribute it or modify it under terms of the MIT, Apache License or Zlib license */ //! Decoding results for images use alloc::vec::Vec; /// A simple enum that can hold decode /// results of most images #[non_exhaustive] pub enum DecodingResult { U8(Vec), U16(Vec), F32(Vec) } impl DecodingResult { /// Return the contents if the enum stores `Vec` or otherwise /// return `None`. /// /// Useful for de-sugaring the result of a decoding operation /// into raw bytes /// /// # Example /// ``` /// use zune_core::result::DecodingResult; /// let data = DecodingResult::U8(vec![0;100]); /// // we know this won't fail because we created it with u8 /// assert!(data.u8().is_some()); /// /// let data = DecodingResult::U16(vec![0;100]); /// // it should now return nothing since the type is u18 /// assert!(data.u8().is_none()); /// /// ``` pub fn u8(self) -> Option> { match self { DecodingResult::U8(data) => Some(data), _ => None } } /// Return the contents if the enum stores `Vec` or otherwise /// return `None`. /// /// Useful for de-sugaring the result of a decoding operation /// into raw bytes /// /// # Example /// ``` /// use zune_core::result::DecodingResult; /// let data = DecodingResult::U8(vec![0;100]); /// // we know this will fail because we created it with u16 /// assert!(data.u16().is_none()); /// /// /// let data = DecodingResult::U16(vec![0;100]); /// // it should now return something since the type is u16 /// assert!(data.u16().is_some()); /// /// ``` pub fn u16(self) -> Option> { match self { DecodingResult::U16(data) => Some(data), _ => None } } } zune-core-0.5.1/src/serde.rs000064400000000000000000000030401046102023000137720ustar 00000000000000/* * Copyright (c) 2023. * * This software is free software; * * You can redistribute it or modify it under terms of the MIT, Apache License or Zlib license */ #![cfg(feature = "serde")] //! Serde support for serializing //! crate datastructures //! //! Implements serialize for //! - ColorSpace //! - BitDepth //! - ColorCharacteristics use alloc::format; use serde::ser::*; use crate::bit_depth::BitDepth; use crate::colorspace::{ColorCharacteristics, ColorSpace, RenderingIntent}; impl Serialize for ColorSpace { #[allow(clippy::uninlined_format_args)] fn serialize(&self, serializer: S) -> Result where S: Serializer { // colorspace serialization is simply it's debug value serializer.serialize_str(&format!("{:?}", self)) } } impl Serialize for BitDepth { #[allow(clippy::uninlined_format_args)] fn serialize(&self, serializer: S) -> Result where S: Serializer { serializer.serialize_str(&format!("{:?}", self)) } } impl Serialize for ColorCharacteristics { #[allow(clippy::uninlined_format_args)] fn serialize(&self, serializer: S) -> Result where S: Serializer { serializer.serialize_str(&format!("{:?}", self)) } } impl Serialize for RenderingIntent { #[allow(clippy::uninlined_format_args)] fn serialize(&self, serializer: S) -> Result where S: Serializer { serializer.serialize_str(&format!("{:?}", self)) } }