| 1 |
|
| 2 | //! Data structures that represent a complete exr image.
|
| 3 | //! Contains generic structs that must be nested to obtain a complete image type.
|
| 4 | //!
|
| 5 | //!
|
| 6 | //! For example, an rgba image containing multiple layers
|
| 7 | //! can be represented using `Image<Layers<SpecificChannels<MyPixelStorage>>>`.
|
| 8 | //! An image containing a single layer with arbitrary channels and no deep data
|
| 9 | //! can be represented using `Image<Layer<AnyChannels<FlatSamples>>>`.
|
| 10 | //!
|
| 11 | //!
|
| 12 | //! These and other predefined types are included in this module as
|
| 13 | //! 1. `PixelImage`: A single layer, fixed set of arbitrary channels.
|
| 14 | //! 1. `PixelLayersImage`: Multiple layers, fixed set of arbitrary channels.
|
| 15 | //! 1. `RgbaImage`: A single layer, fixed set of channels: rgb, optional a.
|
| 16 | //! 1. `RgbaLayersImage`: Multiple layers, fixed set of channels: rgb, optional a.
|
| 17 | //! 1. `FlatImage`: Multiple layers, any channels, no deep data.
|
| 18 | //! 1. `AnyImage`: All supported data (multiple layers, arbitrary channels, no deep data yet)
|
| 19 | //!
|
| 20 | //! You can also use your own types inside an image,
|
| 21 | //! for example if you want to use a custom sample storage.
|
| 22 | //!
|
| 23 | //! This is the high-level interface for the pixels of an image.
|
| 24 | //! See `exr::blocks` module for a low-level interface.
|
| 25 |
|
| 26 | pub mod read;
|
| 27 | pub mod write;
|
| 28 | pub mod crop;
|
| 29 | pub mod pixel_vec;
|
| 30 | pub mod recursive;
|
| 31 | // pub mod channel_groups;
|
| 32 |
|
| 33 |
|
| 34 | use crate::meta::header::{ImageAttributes, LayerAttributes};
|
| 35 | use crate::meta::attribute::{Text, LineOrder};
|
| 36 | use half::f16;
|
| 37 | use crate::math::{Vec2, RoundingMode};
|
| 38 | use crate::compression::Compression;
|
| 39 | use smallvec::{SmallVec};
|
| 40 | use crate::error::Error;
|
| 41 |
|
| 42 | /// Don't do anything
|
| 43 | pub(crate) fn ignore_progress(_progress: f64){}
|
| 44 |
|
| 45 | /// This image type contains all supported exr features and can represent almost any image.
|
| 46 | /// It currently does not support deep data yet.
|
| 47 | pub type AnyImage = Image<Layers<AnyChannels<Levels<FlatSamples>>>>;
|
| 48 |
|
| 49 | /// This image type contains the most common exr features and can represent almost any plain image.
|
| 50 | /// Does not contain resolution levels. Does not support deep data.
|
| 51 | pub type FlatImage = Image<Layers<AnyChannels<FlatSamples>>>;
|
| 52 |
|
| 53 | /// This image type contains multiple layers, with each layer containing a user-defined type of pixels.
|
| 54 | pub type PixelLayersImage<Storage, Channels> = Image<Layers<SpecificChannels<Storage, Channels>>>;
|
| 55 |
|
| 56 | /// This image type contains a single layer containing a user-defined type of pixels.
|
| 57 | pub type PixelImage<Storage, Channels> = Image<Layer<SpecificChannels<Storage, Channels>>>;
|
| 58 |
|
| 59 | /// This image type contains multiple layers, with each layer containing a user-defined type of rgba pixels.
|
| 60 | pub type RgbaLayersImage<Storage> = PixelLayersImage<Storage, RgbaChannels>;
|
| 61 |
|
| 62 | /// This image type contains a single layer containing a user-defined type of rgba pixels.
|
| 63 | pub type RgbaImage<Storage> = PixelImage<Storage, RgbaChannels>;
|
| 64 |
|
| 65 | /// Contains information about the channels in an rgba image, in the order `(red, green, blue, alpha)`.
|
| 66 | /// The alpha channel is not required. May be `None` if the image did not contain an alpha channel.
|
| 67 | pub type RgbaChannels = (ChannelDescription, ChannelDescription, ChannelDescription, Option<ChannelDescription>);
|
| 68 |
|
| 69 | /// Contains information about the channels in an rgb image, in the order `(red, green, blue)`.
|
| 70 | pub type RgbChannels = (ChannelDescription, ChannelDescription, ChannelDescription);
|
| 71 |
|
| 72 | /// The complete exr image.
|
| 73 | /// `Layers` can be either a single `Layer` or `Layers`.
|
| 74 | #[derive (Debug, Clone, PartialEq)]
|
| 75 | pub struct Image<Layers> {
|
| 76 |
|
| 77 | /// Attributes that apply to the whole image file.
|
| 78 | /// These attributes appear in each layer of the file.
|
| 79 | /// Excludes technical meta data.
|
| 80 | /// Each layer in this image also has its own attributes.
|
| 81 | pub attributes: ImageAttributes,
|
| 82 |
|
| 83 | /// The layers contained in the image file.
|
| 84 | /// Can be either a single `Layer` or a list of layers.
|
| 85 | pub layer_data: Layers,
|
| 86 | }
|
| 87 |
|
| 88 | /// A list of layers. `Channels` can be `SpecificChannels` or `AnyChannels`.
|
| 89 | pub type Layers<Channels> = SmallVec<[Layer<Channels>; 2]>;
|
| 90 |
|
| 91 | /// A single Layer, including fancy attributes and compression settings.
|
| 92 | /// `Channels` can be either `SpecificChannels` or `AnyChannels`
|
| 93 | #[derive (Debug, Clone, PartialEq)]
|
| 94 | pub struct Layer<Channels> {
|
| 95 |
|
| 96 | /// The actual pixel data. Either `SpecificChannels` or `AnyChannels`
|
| 97 | pub channel_data: Channels,
|
| 98 |
|
| 99 | /// Attributes that apply to this layer.
|
| 100 | /// May still contain attributes that should be considered global for an image file.
|
| 101 | /// Excludes technical meta data: Does not contain data window size, line order, tiling, or compression attributes.
|
| 102 | /// The image also has attributes, which do not differ per layer.
|
| 103 | pub attributes: LayerAttributes,
|
| 104 |
|
| 105 | /// The pixel resolution of this layer.
|
| 106 | /// See `layer.attributes` for more attributes, like for example layer position.
|
| 107 | pub size: Vec2<usize>,
|
| 108 |
|
| 109 | /// How the pixels are split up and compressed.
|
| 110 | pub encoding: Encoding
|
| 111 | }
|
| 112 |
|
| 113 | /// How the pixels are split up and compressed.
|
| 114 | #[derive (Copy, Clone, Debug, PartialEq)]
|
| 115 | pub struct Encoding {
|
| 116 |
|
| 117 | /// How the pixel data of all channels in this layer is compressed. May be `Compression::Uncompressed`.
|
| 118 | /// See `layer.attributes` for more attributes.
|
| 119 | pub compression: Compression,
|
| 120 |
|
| 121 | /// Describes how the pixels of this layer are divided into smaller blocks.
|
| 122 | /// Either splits the image into its scan lines or splits the image into tiles of the specified size.
|
| 123 | /// A single block can be loaded without processing all bytes of a file.
|
| 124 | pub blocks: Blocks,
|
| 125 |
|
| 126 | /// In what order the tiles of this header occur in the file.
|
| 127 | /// Does not change any actual image orientation.
|
| 128 | /// See `layer.attributes` for more attributes.
|
| 129 | pub line_order: LineOrder,
|
| 130 | }
|
| 131 |
|
| 132 | /// How the image pixels are split up into separate blocks.
|
| 133 | #[derive (Copy, Clone, Debug, PartialEq, Eq)]
|
| 134 | pub enum Blocks {
|
| 135 |
|
| 136 | /// The image is divided into scan line blocks.
|
| 137 | /// The number of scan lines in a block depends on the compression method.
|
| 138 | ScanLines,
|
| 139 |
|
| 140 | /// The image is divided into tile blocks.
|
| 141 | /// Also specifies the size of each tile in the image
|
| 142 | /// and whether this image contains multiple resolution levels.
|
| 143 | ///
|
| 144 | /// The inner `Vec2` describes the size of each tile.
|
| 145 | /// Stays the same number of pixels across all levels.
|
| 146 | Tiles (Vec2<usize>)
|
| 147 | }
|
| 148 |
|
| 149 |
|
| 150 | /// A grid of pixels. The pixels are written to your custom pixel storage.
|
| 151 | /// `PixelStorage` can be anything, from a flat `Vec<f16>` to `Vec<Vec<AnySample>>`, as desired.
|
| 152 | /// In order to write this image to a file, your `PixelStorage` must implement [`GetPixel`].
|
| 153 | #[derive (Debug, Clone, PartialEq, Eq)]
|
| 154 | pub struct SpecificChannels<Pixels, ChannelsDescription> {
|
| 155 |
|
| 156 | /// A description of the channels in the file, as opposed to the channels in memory.
|
| 157 | /// Should always be a tuple containing `ChannelDescription`s, one description for each channel.
|
| 158 | pub channels: ChannelsDescription, // TODO this is awkward. can this be not a type parameter please? maybe vec<option<chan_info>> ??
|
| 159 |
|
| 160 | /// Your custom pixel storage
|
| 161 | // TODO should also support `Levels<YourStorage>`, where levels are desired!
|
| 162 | pub pixels: Pixels, // TODO rename to "pixels"?
|
| 163 | }
|
| 164 |
|
| 165 |
|
| 166 | /// A dynamic list of arbitrary channels.
|
| 167 | /// `Samples` can currently only be `FlatSamples` or `Levels<FlatSamples>`.
|
| 168 | #[derive (Debug, Clone, PartialEq)]
|
| 169 | pub struct AnyChannels<Samples> {
|
| 170 |
|
| 171 | /// This list must be sorted alphabetically, by channel name.
|
| 172 | /// Use `AnyChannels::sorted` for automatic sorting.
|
| 173 | pub list: SmallVec<[AnyChannel<Samples>; 4]>
|
| 174 | }
|
| 175 |
|
| 176 | /// A single arbitrary channel.
|
| 177 | /// `Samples` can currently only be `FlatSamples` or `Levels<FlatSamples>`
|
| 178 | #[derive (Debug, Clone, PartialEq)]
|
| 179 | pub struct AnyChannel<Samples> {
|
| 180 |
|
| 181 | /// One of "R", "G", or "B" most of the time.
|
| 182 | pub name: Text,
|
| 183 |
|
| 184 | /// The actual pixel data.
|
| 185 | /// Can be `FlatSamples` or `Levels<FlatSamples>`.
|
| 186 | pub sample_data: Samples,
|
| 187 |
|
| 188 | /// This attribute only tells lossy compression methods
|
| 189 | /// whether this value should be quantized exponentially or linearly.
|
| 190 | ///
|
| 191 | /// Should be `false` for red, green, blue and luma channels, as they are not perceived linearly.
|
| 192 | /// Should be `true` for hue, chroma, saturation, and alpha channels.
|
| 193 | pub quantize_linearly: bool,
|
| 194 |
|
| 195 | /// How many of the samples are skipped compared to the other channels in this layer.
|
| 196 | ///
|
| 197 | /// Can be used for chroma subsampling for manual lossy data compression.
|
| 198 | /// Values other than 1 are allowed only in flat, scan-line based images.
|
| 199 | /// If an image is deep or tiled, the sampling rates for all of its channels must be 1.
|
| 200 | pub sampling: Vec2<usize>,
|
| 201 | }
|
| 202 |
|
| 203 | /// One or multiple resolution levels of the same image.
|
| 204 | /// `Samples` can be `FlatSamples`.
|
| 205 | #[derive (Debug, Clone, PartialEq, Eq)]
|
| 206 | pub enum Levels<Samples> {
|
| 207 |
|
| 208 | /// A single image without smaller versions of itself.
|
| 209 | /// If you only want to handle exclusively this case, use `Samples` directly, and not `Levels<Samples>`.
|
| 210 | Singular(Samples),
|
| 211 |
|
| 212 | /// Contains uniformly scaled smaller versions of the original.
|
| 213 | Mip
|
| 214 | {
|
| 215 | /// Whether to round up or down when calculating Mip/Rip levels.
|
| 216 | rounding_mode: RoundingMode,
|
| 217 |
|
| 218 | /// The smaller versions of the original.
|
| 219 | level_data: LevelMaps<Samples>
|
| 220 | },
|
| 221 |
|
| 222 | /// Contains any possible combination of smaller versions of the original.
|
| 223 | Rip
|
| 224 | {
|
| 225 | /// Whether to round up or down when calculating Mip/Rip levels.
|
| 226 | rounding_mode: RoundingMode,
|
| 227 |
|
| 228 | /// The smaller versions of the original.
|
| 229 | level_data: RipMaps<Samples>
|
| 230 | },
|
| 231 | }
|
| 232 |
|
| 233 | /// A list of resolution levels. `Samples` can currently only be `FlatSamples`.
|
| 234 | // or `DeepAndFlatSamples` (not yet implemented).
|
| 235 | pub type LevelMaps<Samples> = Vec<Samples>;
|
| 236 |
|
| 237 | /// In addition to the full resolution image,
|
| 238 | /// this layer also contains smaller versions,
|
| 239 | /// and each smaller version has further versions with varying aspect ratios.
|
| 240 | /// `Samples` can currently only be `FlatSamples`.
|
| 241 | #[derive (Debug, Clone, PartialEq, Eq)]
|
| 242 | pub struct RipMaps<Samples> {
|
| 243 |
|
| 244 | /// A flattened list containing the individual levels
|
| 245 | pub map_data: LevelMaps<Samples>,
|
| 246 |
|
| 247 | /// The number of levels that were generated along the x-axis and y-axis.
|
| 248 | pub level_count: Vec2<usize>,
|
| 249 | }
|
| 250 |
|
| 251 |
|
| 252 | // TODO deep data
|
| 253 | /*#[derive(Clone, PartialEq)]
|
| 254 | pub enum DeepAndFlatSamples {
|
| 255 | Deep(DeepSamples),
|
| 256 | Flat(FlatSamples)
|
| 257 | }*/
|
| 258 |
|
| 259 | /// A vector of non-deep values (one value per pixel per channel).
|
| 260 | /// Stores row after row in a single vector.
|
| 261 | /// The precision of all values is either `f16`, `f32` or `u32`.
|
| 262 | ///
|
| 263 | /// Since this is close to the pixel layout in the byte file,
|
| 264 | /// this will most likely be the fastest storage.
|
| 265 | /// Using a different storage, for example `SpecificChannels`,
|
| 266 | /// will probably be slower.
|
| 267 | #[derive (Clone, PartialEq)] // debug is implemented manually
|
| 268 | pub enum FlatSamples {
|
| 269 |
|
| 270 | /// A vector of non-deep `f16` values.
|
| 271 | F16(Vec<f16>),
|
| 272 |
|
| 273 | /// A vector of non-deep `f32` values.
|
| 274 | F32(Vec<f32>),
|
| 275 |
|
| 276 | /// A vector of non-deep `u32` values.
|
| 277 | U32(Vec<u32>),
|
| 278 | }
|
| 279 |
|
| 280 |
|
| 281 | /*#[derive(Clone, PartialEq)]
|
| 282 | pub enum DeepSamples {
|
| 283 | F16(Vec<Vec<f16>>),
|
| 284 | F32(Vec<Vec<f32>>),
|
| 285 | U32(Vec<Vec<u32>>),
|
| 286 | }*/
|
| 287 |
|
| 288 | use crate::block::samples::*;
|
| 289 | use crate::meta::attribute::*;
|
| 290 | use crate::error::Result;
|
| 291 | use crate::block::samples::Sample;
|
| 292 | use crate::image::write::channels::*;
|
| 293 | use crate::image::write::layers::WritableLayers;
|
| 294 | use crate::image::write::samples::{WritableSamples};
|
| 295 | use crate::meta::{mip_map_levels, rip_map_levels};
|
| 296 | use crate::io::Data;
|
| 297 | use crate::image::recursive::{NoneMore, Recursive, IntoRecursive};
|
| 298 | use std::marker::PhantomData;
|
| 299 | use std::ops::Not;
|
| 300 | use crate::image::validate_results::{ValidationOptions};
|
| 301 |
|
| 302 |
|
| 303 | impl<Channels> Layer<Channels> {
|
| 304 | /// Sometimes called "data window"
|
| 305 | pub fn absolute_bounds(&self) -> IntegerBounds {
|
| 306 | IntegerBounds::new(self.attributes.layer_position, self.size)
|
| 307 | }
|
| 308 | }
|
| 309 |
|
| 310 |
|
| 311 | impl<SampleStorage, Channels> SpecificChannels<SampleStorage, Channels> {
|
| 312 | /// Create some pixels with channel information.
|
| 313 | /// The `Channels` must be a tuple containing either `ChannelDescription` or `Option<ChannelDescription>`.
|
| 314 | /// The length of the tuple dictates the number of channels in the sample storage.
|
| 315 | pub fn new(channels: Channels, source_samples: SampleStorage) -> Self
|
| 316 | where
|
| 317 | SampleStorage: GetPixel,
|
| 318 | SampleStorage::Pixel: IntoRecursive,
|
| 319 | Channels: Sync + Clone + IntoRecursive,
|
| 320 | <Channels as IntoRecursive>::Recursive: WritableChannelsDescription<<SampleStorage::Pixel as IntoRecursive>::Recursive>,
|
| 321 | {
|
| 322 | SpecificChannels { channels, pixels: source_samples }
|
| 323 | }
|
| 324 | }
|
| 325 |
|
| 326 | /// Convert this type into one of the known sample types.
|
| 327 | /// Also specify the preferred native type, which dictates the default sample type in the image.
|
| 328 | pub trait IntoSample: IntoNativeSample {
|
| 329 |
|
| 330 | /// The native sample types that this type should be converted to.
|
| 331 | const PREFERRED_SAMPLE_TYPE: SampleType;
|
| 332 | }
|
| 333 |
|
| 334 | impl IntoSample for f16 { const PREFERRED_SAMPLE_TYPE: SampleType = SampleType::F16; }
|
| 335 | impl IntoSample for f32 { const PREFERRED_SAMPLE_TYPE: SampleType = SampleType::F32; }
|
| 336 | impl IntoSample for u32 { const PREFERRED_SAMPLE_TYPE: SampleType = SampleType::U32; }
|
| 337 |
|
| 338 | /// Used to construct a `SpecificChannels`.
|
| 339 | /// Call `with_named_channel` as many times as desired,
|
| 340 | /// and then call `with_pixels` to define the colors.
|
| 341 | #[derive (Debug)]
|
| 342 | pub struct SpecificChannelsBuilder<RecursiveChannels, RecursivePixel> {
|
| 343 | channels: RecursiveChannels,
|
| 344 | px: PhantomData<RecursivePixel>
|
| 345 | }
|
| 346 |
|
| 347 | /// This check can be executed at compile time
|
| 348 | /// if the channel names are `&'static str` and the compiler is smart enough.
|
| 349 | pub trait CheckDuplicates {
|
| 350 |
|
| 351 | /// Check for duplicate channel names.
|
| 352 | fn already_contains(&self, name: &Text) -> bool;
|
| 353 | }
|
| 354 |
|
| 355 | impl CheckDuplicates for NoneMore {
|
| 356 | fn already_contains(&self, _: &Text) -> bool { false }
|
| 357 | }
|
| 358 |
|
| 359 | impl<Inner: CheckDuplicates> CheckDuplicates for Recursive<Inner, ChannelDescription> {
|
| 360 | fn already_contains(&self, name: &Text) -> bool {
|
| 361 | &self.value.name == name || self.inner.already_contains(name)
|
| 362 | }
|
| 363 | }
|
| 364 |
|
| 365 | impl SpecificChannels<(),()>
|
| 366 | {
|
| 367 | /// Start building some specific channels. On the result of this function,
|
| 368 | /// call `with_named_channel` as many times as desired,
|
| 369 | /// and then call `with_pixels` to define the colors.
|
| 370 | pub fn build() -> SpecificChannelsBuilder<NoneMore, NoneMore> {
|
| 371 | SpecificChannelsBuilder { channels: NoneMore, px: Default::default() }
|
| 372 | }
|
| 373 | }
|
| 374 |
|
| 375 | impl<RecursiveChannels: CheckDuplicates, RecursivePixel> SpecificChannelsBuilder<RecursiveChannels, RecursivePixel>
|
| 376 | {
|
| 377 | /// Add another channel to this image. Does not add the actual pixels,
|
| 378 | /// but instead only declares the presence of the channel.
|
| 379 | /// Panics if the name contains unsupported characters.
|
| 380 | /// Panics if a channel with the same name already exists.
|
| 381 | /// Use `Text::new_or_none()` to manually handle these cases.
|
| 382 | /// Use `with_channel_details` instead if you want to specify more options than just the name of the channel.
|
| 383 | /// The generic parameter can usually be inferred from the closure in `with_pixels`.
|
| 384 | pub fn with_channel<Sample: IntoSample>(self, name: impl Into<Text>)
|
| 385 | -> SpecificChannelsBuilder<Recursive<RecursiveChannels, ChannelDescription>, Recursive<RecursivePixel, Sample>>
|
| 386 | {
|
| 387 | self.with_channel_details::<Sample>(ChannelDescription::named(name, Sample::PREFERRED_SAMPLE_TYPE))
|
| 388 | }
|
| 389 |
|
| 390 | /// Add another channel to this image. Does not add the actual pixels,
|
| 391 | /// but instead only declares the presence of the channel.
|
| 392 | /// Use `with_channel` instead if you only want to specify the name of the channel.
|
| 393 | /// Panics if a channel with the same name already exists.
|
| 394 | /// The generic parameter can usually be inferred from the closure in `with_pixels`.
|
| 395 | pub fn with_channel_details<Sample: Into<Sample>>(self, channel: ChannelDescription)
|
| 396 | -> SpecificChannelsBuilder<Recursive<RecursiveChannels, ChannelDescription>, Recursive<RecursivePixel, Sample>>
|
| 397 | {
|
| 398 | // duplicate channel names are checked later, but also check now to make sure there are no problems with the `SpecificChannelsWriter`
|
| 399 | assert!(self.channels.already_contains(&channel.name).not(), "channel name ` {}` is duplicate" , channel.name);
|
| 400 |
|
| 401 | SpecificChannelsBuilder {
|
| 402 | channels: Recursive::new(self.channels, channel),
|
| 403 | px: PhantomData::default()
|
| 404 | }
|
| 405 | }
|
| 406 |
|
| 407 | /// Specify the actual pixel contents of the image.
|
| 408 | /// You can pass a closure that returns a color for each pixel (`Fn(Vec2<usize>) -> Pixel`),
|
| 409 | /// or you can pass your own image if it implements `GetPixel`.
|
| 410 | /// The pixel type must be a tuple with the correct number of entries, depending on the number of channels.
|
| 411 | /// The tuple entries can be either `f16`, `f32`, `u32` or `Sample`.
|
| 412 | /// Use `with_pixel_fn` instead of this function, to get extra type safety for your pixel closure.
|
| 413 | pub fn with_pixels<Pixels>(self, get_pixel: Pixels) -> SpecificChannels<Pixels, RecursiveChannels>
|
| 414 | where Pixels: GetPixel, <Pixels as GetPixel>::Pixel: IntoRecursive<Recursive=RecursivePixel>,
|
| 415 | {
|
| 416 | SpecificChannels {
|
| 417 | channels: self.channels,
|
| 418 | pixels: get_pixel
|
| 419 | }
|
| 420 | }
|
| 421 |
|
| 422 | /// Specify the contents of the image.
|
| 423 | /// The pixel type must be a tuple with the correct number of entries, depending on the number of channels.
|
| 424 | /// The tuple entries can be either `f16`, `f32`, `u32` or `Sample`.
|
| 425 | /// Use `with_pixels` instead of this function, if you want to pass an object that is not a closure.
|
| 426 | ///
|
| 427 | /// Usually, the compiler can infer the type of the pixel (for example, `f16,f32,f32`) from the closure.
|
| 428 | /// If that's not possible, you can specify the type of the channels
|
| 429 | /// when declaring the channel (for example, `with_named_channel::<f32>("R")`).
|
| 430 | pub fn with_pixel_fn<Pixel, Pixels>(self, get_pixel: Pixels) -> SpecificChannels<Pixels, RecursiveChannels>
|
| 431 | where Pixels: Sync + Fn(Vec2<usize>) -> Pixel, Pixel: IntoRecursive<Recursive=RecursivePixel>,
|
| 432 | {
|
| 433 | SpecificChannels {
|
| 434 | channels: self.channels,
|
| 435 | pixels: get_pixel
|
| 436 | }
|
| 437 | }
|
| 438 | }
|
| 439 |
|
| 440 | impl<SampleStorage> SpecificChannels<
|
| 441 | SampleStorage, (ChannelDescription, ChannelDescription, ChannelDescription, ChannelDescription)
|
| 442 | >
|
| 443 | {
|
| 444 |
|
| 445 | /// Create an image with red, green, blue, and alpha channels.
|
| 446 | /// You can pass a closure that returns a color for each pixel (`Fn(Vec2<usize>) -> (R,G,B,A)`),
|
| 447 | /// or you can pass your own image if it implements `GetPixel<Pixel=(R,G,B,A)>`.
|
| 448 | /// Each of `R`, `G`, `B` and `A` can be either `f16`, `f32`, `u32`, or `Sample`.
|
| 449 | pub fn rgba<R, G, B, A>(source_samples: SampleStorage) -> Self
|
| 450 | where R: IntoSample, G: IntoSample,
|
| 451 | B: IntoSample, A: IntoSample,
|
| 452 | SampleStorage: GetPixel<Pixel=(R, G, B, A)>
|
| 453 | {
|
| 454 | SpecificChannels {
|
| 455 | channels: (
|
| 456 | ChannelDescription::named(name:"R" , R::PREFERRED_SAMPLE_TYPE),
|
| 457 | ChannelDescription::named(name:"G" , G::PREFERRED_SAMPLE_TYPE),
|
| 458 | ChannelDescription::named(name:"B" , B::PREFERRED_SAMPLE_TYPE),
|
| 459 | ChannelDescription::named(name:"A" , A::PREFERRED_SAMPLE_TYPE),
|
| 460 | ),
|
| 461 | pixels: source_samples
|
| 462 | }
|
| 463 | }
|
| 464 | }
|
| 465 |
|
| 466 | impl<SampleStorage> SpecificChannels<
|
| 467 | SampleStorage, (ChannelDescription, ChannelDescription, ChannelDescription)
|
| 468 | >
|
| 469 | {
|
| 470 |
|
| 471 | /// Create an image with red, green, and blue channels.
|
| 472 | /// You can pass a closure that returns a color for each pixel (`Fn(Vec2<usize>) -> (R,G,B)`),
|
| 473 | /// or you can pass your own image if it implements `GetPixel<Pixel=(R,G,B)>`.
|
| 474 | /// Each of `R`, `G` and `B` can be either `f16`, `f32`, `u32`, or `Sample`.
|
| 475 | pub fn rgb<R, G, B>(source_samples: SampleStorage) -> Self
|
| 476 | where R: IntoSample, G: IntoSample, B: IntoSample,
|
| 477 | SampleStorage: GetPixel<Pixel=(R, G, B)>
|
| 478 | {
|
| 479 | SpecificChannels {
|
| 480 | channels: (
|
| 481 | ChannelDescription::named(name:"R" , R::PREFERRED_SAMPLE_TYPE),
|
| 482 | ChannelDescription::named(name:"G" , G::PREFERRED_SAMPLE_TYPE),
|
| 483 | ChannelDescription::named(name:"B" , B::PREFERRED_SAMPLE_TYPE),
|
| 484 | ),
|
| 485 | pixels: source_samples
|
| 486 | }
|
| 487 | }
|
| 488 | }
|
| 489 |
|
| 490 |
|
| 491 | /// A list of samples representing a single pixel.
|
| 492 | /// Does not heap allocate for images with 8 or fewer channels.
|
| 493 | pub type FlatSamplesPixel = SmallVec<[Sample; 8]>;
|
| 494 |
|
| 495 | // TODO also deep samples?
|
| 496 | impl Layer<AnyChannels<FlatSamples>> {
|
| 497 |
|
| 498 | /// Use `samples_at` if you can borrow from this layer
|
| 499 | pub fn sample_vec_at(&self, position: Vec2<usize>) -> FlatSamplesPixel {
|
| 500 | self.samples_at(position).collect()
|
| 501 | }
|
| 502 |
|
| 503 | /// Lookup all channels of a single pixel in the image
|
| 504 | pub fn samples_at(&self, position: Vec2<usize>) -> FlatSampleIterator<'_> {
|
| 505 | FlatSampleIterator {
|
| 506 | layer: self,
|
| 507 | channel_index: 0,
|
| 508 | position
|
| 509 | }
|
| 510 | }
|
| 511 | }
|
| 512 |
|
| 513 | /// Iterate over all channels of a single pixel in the image
|
| 514 | #[derive (Debug, Copy, Clone, PartialEq)]
|
| 515 | pub struct FlatSampleIterator<'s> {
|
| 516 | layer: &'s Layer<AnyChannels<FlatSamples>>,
|
| 517 | channel_index: usize,
|
| 518 | position: Vec2<usize>,
|
| 519 | }
|
| 520 |
|
| 521 | impl Iterator for FlatSampleIterator<'_> {
|
| 522 | type Item = Sample;
|
| 523 |
|
| 524 | fn next(&mut self) -> Option<Self::Item> {
|
| 525 | if self.channel_index < self.layer.channel_data.list.len() {
|
| 526 | let channel: &AnyChannel = &self.layer.channel_data.list[self.channel_index];
|
| 527 | let sample: Sample = channel.sample_data.value_by_flat_index(self.position.flat_index_for_size(self.layer.size));
|
| 528 | self.channel_index += 1;
|
| 529 | Some(sample)
|
| 530 | }
|
| 531 | else { None }
|
| 532 | }
|
| 533 |
|
| 534 | fn nth(&mut self, pos: usize) -> Option<Self::Item> {
|
| 535 | self.channel_index += pos;
|
| 536 | self.next()
|
| 537 | }
|
| 538 |
|
| 539 | fn size_hint(&self) -> (usize, Option<usize>) {
|
| 540 | let remaining: usize = self.layer.channel_data.list.len().saturating_sub(self.channel_index);
|
| 541 | (remaining, Some(remaining))
|
| 542 | }
|
| 543 | }
|
| 544 |
|
| 545 | impl ExactSizeIterator for FlatSampleIterator<'_> {}
|
| 546 |
|
| 547 | impl<SampleData> AnyChannels<SampleData>{
|
| 548 |
|
| 549 | /// A new list of arbitrary channels. Sorts the list to make it alphabetically stable.
|
| 550 | pub fn sort(mut list: SmallVec<[AnyChannel<SampleData>; 4]>) -> Self {
|
| 551 | list.sort_unstable_by_key(|channel: &AnyChannel| channel.name.clone()); // TODO no clone?
|
| 552 | Self { list }
|
| 553 | }
|
| 554 | }
|
| 555 |
|
| 556 | // FIXME check content size of layer somewhere??? before writing?
|
| 557 | impl<LevelSamples> Levels<LevelSamples> {
|
| 558 |
|
| 559 | /// Get a resolution level by index, sorted by size, decreasing.
|
| 560 | pub fn get_level(&self, level: Vec2<usize>) -> Result<&LevelSamples> {
|
| 561 | match self {
|
| 562 | Levels::Singular(block) => {
|
| 563 | debug_assert_eq!(level, Vec2(0,0), "singular image cannot write leveled blocks bug" );
|
| 564 | Ok(block)
|
| 565 | },
|
| 566 |
|
| 567 | Levels::Mip { level_data, .. } => {
|
| 568 | debug_assert_eq!(level.x(), level.y(), "mip map levels must be equal on x and y bug" );
|
| 569 | level_data.get(level.x()).ok_or(Error::invalid("block mip level index" ))
|
| 570 | },
|
| 571 |
|
| 572 | Levels::Rip { level_data, .. } => {
|
| 573 | level_data.get_by_level(level).ok_or(Error::invalid("block rip level index" ))
|
| 574 | }
|
| 575 | }
|
| 576 | }
|
| 577 |
|
| 578 | /// Get a resolution level by index, sorted by size, decreasing.
|
| 579 | // TODO storage order for RIP maps?
|
| 580 | pub fn get_level_mut(&mut self, level: Vec2<usize>) -> Result<&mut LevelSamples> {
|
| 581 | match self {
|
| 582 | Levels::Singular(ref mut block) => {
|
| 583 | debug_assert_eq!(level, Vec2(0,0), "singular image cannot write leveled blocks bug" );
|
| 584 | Ok(block)
|
| 585 | },
|
| 586 |
|
| 587 | Levels::Mip { level_data, .. } => {
|
| 588 | debug_assert_eq!(level.x(), level.y(), "mip map levels must be equal on x and y bug" );
|
| 589 | level_data.get_mut(level.x()).ok_or(Error::invalid("block mip level index" ))
|
| 590 | },
|
| 591 |
|
| 592 | Levels::Rip { level_data, .. } => {
|
| 593 | level_data.get_by_level_mut(level).ok_or(Error::invalid("block rip level index" ))
|
| 594 | }
|
| 595 | }
|
| 596 | }
|
| 597 |
|
| 598 | /// Get a slice of all resolution levels, sorted by size, decreasing.
|
| 599 | pub fn levels_as_slice(&self) -> &[LevelSamples] {
|
| 600 | match self {
|
| 601 | Levels::Singular(data) => std::slice::from_ref(data),
|
| 602 | Levels::Mip { level_data, .. } => level_data,
|
| 603 | Levels::Rip { level_data, .. } => &level_data.map_data,
|
| 604 | }
|
| 605 | }
|
| 606 |
|
| 607 | /// Get a mutable slice of all resolution levels, sorted by size, decreasing.
|
| 608 | pub fn levels_as_slice_mut(&mut self) -> &mut [LevelSamples] {
|
| 609 | match self {
|
| 610 | Levels::Singular(data) => std::slice::from_mut(data),
|
| 611 | Levels::Mip { level_data, .. } => level_data,
|
| 612 | Levels::Rip { level_data, .. } => &mut level_data.map_data,
|
| 613 | }
|
| 614 | }
|
| 615 |
|
| 616 | // TODO simplify working with levels in general! like level_size_by_index and such
|
| 617 |
|
| 618 | /*pub fn levels_with_size(&self, rounding: RoundingMode, max_resolution: Vec2<usize>) -> Vec<(Vec2<usize>, &S)> {
|
| 619 | match self {
|
| 620 | Levels::Singular(ref data) => vec![ (max_resolution, data) ],
|
| 621 | Levels::Mip(ref maps) => mip_map_levels(rounding, max_resolution).map(|(_index, size)| size).zip(maps).collect(),
|
| 622 | Levels::Rip(ref rip_maps) => rip_map_levels(rounding, max_resolution).map(|(_index, size)| size).zip(&rip_maps.map_data).collect(),
|
| 623 | }
|
| 624 | }*/
|
| 625 |
|
| 626 | /// Whether this stores multiple resolution levels.
|
| 627 | pub fn level_mode(&self) -> LevelMode {
|
| 628 | match self {
|
| 629 | Levels::Singular(_) => LevelMode::Singular,
|
| 630 | Levels::Mip { .. } => LevelMode::MipMap,
|
| 631 | Levels::Rip { .. } => LevelMode::RipMap,
|
| 632 | }
|
| 633 | }
|
| 634 | }
|
| 635 |
|
| 636 | impl<Samples> RipMaps<Samples> {
|
| 637 |
|
| 638 | /// Flatten the 2D level index to a one dimensional index.
|
| 639 | pub fn get_level_index(&self, level: Vec2<usize>) -> usize {
|
| 640 | level.flat_index_for_size(self.level_count)
|
| 641 | }
|
| 642 |
|
| 643 | /// Return a level by level index. Level `0` has the largest resolution.
|
| 644 | pub fn get_by_level(&self, level: Vec2<usize>) -> Option<&Samples> {
|
| 645 | self.map_data.get(self.get_level_index(level))
|
| 646 | }
|
| 647 |
|
| 648 | /// Return a mutable level reference by level index. Level `0` has the largest resolution.
|
| 649 | pub fn get_by_level_mut(&mut self, level: Vec2<usize>) -> Option<&mut Samples> {
|
| 650 | let index: usize = self.get_level_index(level);
|
| 651 | self.map_data.get_mut(index)
|
| 652 | }
|
| 653 | }
|
| 654 |
|
| 655 | impl FlatSamples {
|
| 656 |
|
| 657 | /// The number of samples in the image. Should be the width times the height.
|
| 658 | /// Might vary when subsampling is used.
|
| 659 | pub fn len(&self) -> usize {
|
| 660 | match self {
|
| 661 | FlatSamples::F16(vec) => vec.len(),
|
| 662 | FlatSamples::F32(vec) => vec.len(),
|
| 663 | FlatSamples::U32(vec) => vec.len(),
|
| 664 | }
|
| 665 | }
|
| 666 |
|
| 667 | /// Views all samples in this storage as f32.
|
| 668 | /// Matches the underlying sample type again for every sample,
|
| 669 | /// match yourself if performance is critical! Does not allocate.
|
| 670 | pub fn values_as_f32<'s>(&'s self) -> impl 's + Iterator<Item = f32> {
|
| 671 | self.values().map(|sample| sample.to_f32())
|
| 672 | }
|
| 673 |
|
| 674 | /// All samples in this storage as iterator.
|
| 675 | /// Matches the underlying sample type again for every sample,
|
| 676 | /// match yourself if performance is critical! Does not allocate.
|
| 677 | pub fn values<'s>(&'s self) -> impl 's + Iterator<Item = Sample> {
|
| 678 | (0..self.len()).map(move |index| self.value_by_flat_index(index))
|
| 679 | }
|
| 680 |
|
| 681 | /// Lookup a single value, by flat index.
|
| 682 | /// The flat index can be obtained using `Vec2::flatten_for_width`
|
| 683 | /// which computes the index in a flattened array of pixel rows.
|
| 684 | pub fn value_by_flat_index(&self, index: usize) -> Sample {
|
| 685 | match self {
|
| 686 | FlatSamples::F16(vec) => Sample::F16(vec[index]),
|
| 687 | FlatSamples::F32(vec) => Sample::F32(vec[index]),
|
| 688 | FlatSamples::U32(vec) => Sample::U32(vec[index]),
|
| 689 | }
|
| 690 | }
|
| 691 | }
|
| 692 |
|
| 693 |
|
| 694 | impl<'s, ChannelData:'s> Layer<ChannelData> {
|
| 695 |
|
| 696 | /// Create a layer with the specified size, attributes, encoding and channels.
|
| 697 | /// The channels can be either `SpecificChannels` or `AnyChannels`.
|
| 698 | pub fn new(
|
| 699 | dimensions: impl Into<Vec2<usize>>,
|
| 700 | attributes: LayerAttributes,
|
| 701 | encoding: Encoding,
|
| 702 | channels: ChannelData
|
| 703 | ) -> Self
|
| 704 | where ChannelData: WritableChannels<'s>
|
| 705 | {
|
| 706 | Layer { channel_data: channels, attributes, size: dimensions.into(), encoding }
|
| 707 | }
|
| 708 |
|
| 709 | // TODO test pls wtf
|
| 710 | /// Panics for images with Scanline encoding.
|
| 711 | pub fn levels_with_resolution<'l, L>(&self, levels: &'l Levels<L>) -> Box<dyn 'l + Iterator<Item=(&'l L, Vec2<usize>)>> {
|
| 712 | match levels {
|
| 713 | Levels::Singular(level) => Box::new(std::iter::once((level, self.size))),
|
| 714 |
|
| 715 | Levels::Mip { rounding_mode, level_data } => Box::new(level_data.iter().zip(
|
| 716 | mip_map_levels(*rounding_mode, self.size)
|
| 717 | .map(|(_index, size)| size)
|
| 718 | )),
|
| 719 |
|
| 720 | Levels::Rip { rounding_mode, level_data } => Box::new(level_data.map_data.iter().zip(
|
| 721 | rip_map_levels(*rounding_mode, self.size)
|
| 722 | .map(|(_index, size)| size)
|
| 723 | )),
|
| 724 | }
|
| 725 | }
|
| 726 | }
|
| 727 |
|
| 728 | impl Encoding {
|
| 729 |
|
| 730 | /// No compression. Massive space requirements.
|
| 731 | /// Fast, because it minimizes data shuffling and reallocation.
|
| 732 | pub const UNCOMPRESSED: Encoding = Encoding {
|
| 733 | compression: Compression::Uncompressed,
|
| 734 | blocks: Blocks::ScanLines, // longest lines, faster memcpy
|
| 735 | line_order: LineOrder::Increasing // presumably fastest?
|
| 736 | };
|
| 737 |
|
| 738 | /// Run-length encoding with tiles of 64x64 pixels. This is the recommended default encoding.
|
| 739 | /// Almost as fast as uncompressed data, but optimizes single-colored areas such as mattes and masks.
|
| 740 | pub const FAST_LOSSLESS: Encoding = Encoding {
|
| 741 | compression: Compression::RLE,
|
| 742 | blocks: Blocks::Tiles(Vec2(64, 64)), // optimize for RLE compression
|
| 743 | line_order: LineOrder::Unspecified
|
| 744 | };
|
| 745 |
|
| 746 | /// ZIP compression with blocks of 16 lines. Slow, but produces small files without visible artefacts.
|
| 747 | pub const SMALL_LOSSLESS: Encoding = Encoding {
|
| 748 | compression: Compression::ZIP16,
|
| 749 | blocks: Blocks::ScanLines, // largest possible, but also with high probability of parallel workers
|
| 750 | line_order: LineOrder::Increasing
|
| 751 | };
|
| 752 |
|
| 753 | /// PIZ compression with tiles of 256x256 pixels. Small images, not too slow.
|
| 754 | pub const SMALL_FAST_LOSSLESS: Encoding = Encoding {
|
| 755 | compression: Compression::PIZ,
|
| 756 | blocks: Blocks::Tiles(Vec2(256, 256)),
|
| 757 | line_order: LineOrder::Unspecified
|
| 758 | };
|
| 759 | }
|
| 760 |
|
| 761 | impl Default for Encoding {
|
| 762 | fn default() -> Self { Encoding::FAST_LOSSLESS }
|
| 763 | }
|
| 764 |
|
| 765 | impl<'s, LayerData: 's> Image<LayerData> where LayerData: WritableLayers<'s> {
|
| 766 | /// Create an image with one or multiple layers. The layer can be a `Layer`, or `Layers` small vector, or `Vec<Layer>` or `&[Layer]`.
|
| 767 | pub fn new(image_attributes: ImageAttributes, layer_data: LayerData) -> Self {
|
| 768 | Image { attributes: image_attributes, layer_data }
|
| 769 | }
|
| 770 | }
|
| 771 |
|
| 772 | // explorable constructor alias
|
| 773 | impl<'s, Channels: 's> Image<Layers<Channels>> where Channels: WritableChannels<'s> {
|
| 774 | /// Create an image with multiple layers. The layer can be a `Vec<Layer>` or `Layers` (a small vector).
|
| 775 | pub fn from_layers(image_attributes: ImageAttributes, layer_data: impl Into<Layers<Channels>>) -> Self {
|
| 776 | Self::new(image_attributes, layer_data.into())
|
| 777 | }
|
| 778 | }
|
| 779 |
|
| 780 |
|
| 781 | impl<'s, ChannelData:'s> Image<Layer<ChannelData>> where ChannelData: WritableChannels<'s> {
|
| 782 |
|
| 783 | /// Uses the display position and size to the channel position and size of the layer.
|
| 784 | pub fn from_layer(layer: Layer<ChannelData>) -> Self {
|
| 785 | let bounds: IntegerBounds = IntegerBounds::new(start:layer.attributes.layer_position, layer.size);
|
| 786 | Self::new(ImageAttributes::new(display_window:bounds), layer)
|
| 787 | }
|
| 788 |
|
| 789 | /// Uses empty attributes.
|
| 790 | pub fn from_encoded_channels(size: impl Into<Vec2<usize>>, encoding: Encoding, channels: ChannelData) -> Self {
|
| 791 | // layer name is not required for single-layer images
|
| 792 | Self::from_layer(Layer::new(dimensions:size, attributes:LayerAttributes::default(), encoding, channels))
|
| 793 | }
|
| 794 |
|
| 795 | /// Uses empty attributes and fast compression.
|
| 796 | pub fn from_channels(size: impl Into<Vec2<usize>>, channels: ChannelData) -> Self {
|
| 797 | Self::from_encoded_channels(size, Encoding::default(), channels)
|
| 798 | }
|
| 799 | }
|
| 800 |
|
| 801 |
|
| 802 | impl Image<NoneMore> {
|
| 803 |
|
| 804 | /// Create an empty image, to be filled with layers later on. Add at least one layer to obtain a valid image.
|
| 805 | /// Call `with_layer(another_layer)` for each layer you want to add to this image.
|
| 806 | pub fn empty(attributes: ImageAttributes) -> Self { Self { attributes, layer_data: NoneMore } }
|
| 807 | }
|
| 808 |
|
| 809 | impl<'s, InnerLayers: 's> Image<InnerLayers> where
|
| 810 | InnerLayers: WritableLayers<'s>,
|
| 811 | {
|
| 812 | /// Add another layer to this image. The layer type does
|
| 813 | /// not have to equal the existing layers in this image.
|
| 814 | pub fn with_layer<NewChannels>(self, layer: Layer<NewChannels>)
|
| 815 | -> Image<Recursive<InnerLayers, Layer<NewChannels>>>
|
| 816 | where NewChannels: 's + WritableChannels<'s>
|
| 817 | {
|
| 818 | Image {
|
| 819 | attributes: self.attributes,
|
| 820 | layer_data: Recursive::new(self.layer_data, value:layer)
|
| 821 | }
|
| 822 | }
|
| 823 | }
|
| 824 |
|
| 825 |
|
| 826 | impl<'s, SampleData: 's> AnyChannel<SampleData> {
|
| 827 |
|
| 828 | /// Create a new channel without subsampling.
|
| 829 | ///
|
| 830 | /// Automatically flags this channel for specialized compression
|
| 831 | /// if the name is "R", "G", "B", "Y", or "L",
|
| 832 | /// as they typically encode values that are perceived non-linearly.
|
| 833 | /// Construct the value yourself using `AnyChannel { .. }`, if you want to control this flag.
|
| 834 | pub fn new(name: impl Into<Text>, sample_data: SampleData) -> Self where SampleData: WritableSamples<'s> {
|
| 835 | let name: Text = name.into();
|
| 836 |
|
| 837 | AnyChannel {
|
| 838 | quantize_linearly: ChannelDescription::guess_quantization_linearity(&name),
|
| 839 | name, sample_data,
|
| 840 | sampling: Vec2(1, 1),
|
| 841 | }
|
| 842 | }
|
| 843 |
|
| 844 | /*/// This is the same as `AnyChannel::new()`, but additionally ensures that the closure type is correct.
|
| 845 | pub fn from_closure<V>(name: Text, sample_data: S) -> Self
|
| 846 | where S: Sync + Fn(Vec2<usize>) -> V, V: InferSampleType + Data
|
| 847 | {
|
| 848 | Self::new(name, sample_data)
|
| 849 | }*/
|
| 850 | }
|
| 851 |
|
| 852 | impl std::fmt::Debug for FlatSamples {
|
| 853 | fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
| 854 | if self.len() <= 6 {
|
| 855 | match self {
|
| 856 | FlatSamples::F16(vec: &Vec) => vec.fmt(formatter),
|
| 857 | FlatSamples::F32(vec: &Vec) => vec.fmt(formatter),
|
| 858 | FlatSamples::U32(vec: &Vec) => vec.fmt(formatter),
|
| 859 | }
|
| 860 | }
|
| 861 | else {
|
| 862 | match self {
|
| 863 | FlatSamples::F16(vec: &Vec) => write!(formatter, "[f16; {}]" , vec.len()),
|
| 864 | FlatSamples::F32(vec: &Vec) => write!(formatter, "[f32; {}]" , vec.len()),
|
| 865 | FlatSamples::U32(vec: &Vec) => write!(formatter, "[u32; {}]" , vec.len()),
|
| 866 | }
|
| 867 | }
|
| 868 | }
|
| 869 | }
|
| 870 |
|
| 871 |
|
| 872 |
|
| 873 | /// Compare the result of a round trip test with the original method.
|
| 874 | /// Supports lossy compression methods.
|
| 875 | // #[cfg(test)] TODO do not ship this code
|
| 876 | pub mod validate_results {
|
| 877 | use crate::prelude::*;
|
| 878 | use smallvec::Array;
|
| 879 | use crate::prelude::recursive::*;
|
| 880 | use crate::image::write::samples::WritableSamples;
|
| 881 | use std::ops::Not;
|
| 882 | use crate::block::samples::IntoNativeSample;
|
| 883 |
|
| 884 |
|
| 885 | /// Compare two objects, but with a few special quirks.
|
| 886 | /// Intended mainly for unit testing.
|
| 887 | pub trait ValidateResult {
|
| 888 |
|
| 889 | /// Compare self with the other. Panics if not equal.
|
| 890 | ///
|
| 891 | /// Exceptional behaviour:
|
| 892 | /// This does not work the other way around! This method is not symmetrical!
|
| 893 | /// Returns whether the result is correct for this image.
|
| 894 | /// For lossy compression methods, uses approximate equality.
|
| 895 | /// Intended for unit testing.
|
| 896 | ///
|
| 897 | /// Warning: If you use `SpecificChannels`, the comparison might be inaccurate
|
| 898 | /// for images with mixed compression methods. This is to be used with `AnyChannels` mainly.
|
| 899 | fn assert_equals_result(&self, result: &Self) {
|
| 900 | self.validate_result(result, ValidationOptions::default(), || String::new()).unwrap();
|
| 901 | }
|
| 902 |
|
| 903 | /// Compare self with the other.
|
| 904 | /// Exceptional behaviour:
|
| 905 | /// - Any two NaN values are considered equal, regardless of bit representation.
|
| 906 | /// - If a `lossy` is specified, any two values that differ only by a small amount will be considered equal.
|
| 907 | /// - If `nan_to_zero` is true, and __self is NaN/Infinite and the other value is zero, they are considered equal__
|
| 908 | /// (because some compression methods replace nan with zero)
|
| 909 | ///
|
| 910 | /// This does not work the other way around! This method is not symmetrical!
|
| 911 | fn validate_result(
|
| 912 | &self, lossy_result: &Self,
|
| 913 | options: ValidationOptions,
|
| 914 | // this is a lazy string, because constructing a string is only necessary in the case of an error,
|
| 915 | // but eats up memory and allocation time every time. this was measured.
|
| 916 | context: impl Fn() -> String
|
| 917 | ) -> ValidationResult;
|
| 918 | }
|
| 919 |
|
| 920 | /// Whether to do accurate or approximate comparison.
|
| 921 | #[derive (Default, Debug, Eq, PartialEq, Hash, Copy, Clone)]
|
| 922 | pub struct ValidationOptions {
|
| 923 | allow_lossy: bool,
|
| 924 | nan_converted_to_zero: bool,
|
| 925 | }
|
| 926 |
|
| 927 | /// If invalid, contains the error message.
|
| 928 | pub type ValidationResult = std::result::Result<(), String>;
|
| 929 |
|
| 930 |
|
| 931 | impl<C> ValidateResult for Image<C> where C: ValidateResult {
|
| 932 | fn validate_result(&self, other: &Self, options: ValidationOptions, location: impl Fn()->String) -> ValidationResult {
|
| 933 | if self.attributes != other.attributes { Err(location() + "| image > attributes" ) }
|
| 934 | else { self.layer_data.validate_result(&other.layer_data, options, || location() + "| image > layer data" ) }
|
| 935 | }
|
| 936 | }
|
| 937 |
|
| 938 | impl<S> ValidateResult for Layer<AnyChannels<S>>
|
| 939 | where AnyChannel<S>: ValidateResult, S: for<'a> WritableSamples<'a>
|
| 940 | {
|
| 941 | fn validate_result(&self, other: &Self, _overridden: ValidationOptions, location: impl Fn()->String) -> ValidationResult {
|
| 942 | let location = || format!(" {} (layer ` {:?}`)" , location(), self.attributes.layer_name);
|
| 943 | if self.attributes != other.attributes { Err(location() + " > attributes" ) }
|
| 944 | else if self.encoding != other.encoding { Err(location() + " > encoding" ) }
|
| 945 | else if self.size != other.size { Err(location() + " > size" ) }
|
| 946 | else if self.channel_data.list.len() != other.channel_data.list.len() { Err(location() + " > channel count" ) }
|
| 947 | else {
|
| 948 | for (own_chan, other_chan) in self.channel_data.list.iter().zip(other.channel_data.list.iter()) {
|
| 949 | own_chan.validate_result(
|
| 950 | other_chan,
|
| 951 |
|
| 952 | ValidationOptions {
|
| 953 | // no tolerance for lossless channels
|
| 954 | allow_lossy: other.encoding.compression
|
| 955 | .is_lossless_for(other_chan.sample_data.sample_type()).not(),
|
| 956 |
|
| 957 | // consider nan and zero equal if the compression method does not support nan
|
| 958 | nan_converted_to_zero: other.encoding.compression.supports_nan().not()
|
| 959 | },
|
| 960 |
|
| 961 | || format!(" {} > channel ` {}`" , location(), own_chan.name)
|
| 962 | )?;
|
| 963 | }
|
| 964 | Ok(())
|
| 965 | }
|
| 966 | }
|
| 967 | }
|
| 968 |
|
| 969 | impl<Px, Desc> ValidateResult for Layer<SpecificChannels<Px, Desc>>
|
| 970 | where SpecificChannels<Px, Desc>: ValidateResult
|
| 971 | {
|
| 972 | /// This does an approximate comparison for all channels,
|
| 973 | /// even if some channels can be compressed without loss.
|
| 974 | fn validate_result(&self, other: &Self, _overridden: ValidationOptions, location: impl Fn()->String) -> ValidationResult {
|
| 975 | let location = || format!(" {} (layer ` {:?}`)" , location(), self.attributes.layer_name);
|
| 976 |
|
| 977 | // TODO dedup with above
|
| 978 | if self.attributes != other.attributes { Err(location() + " > attributes" ) }
|
| 979 | else if self.encoding != other.encoding { Err(location() + " > encoding" ) }
|
| 980 | else if self.size != other.size { Err(location() + " > size" ) }
|
| 981 | else {
|
| 982 | let options = ValidationOptions {
|
| 983 | // no tolerance for lossless channels
|
| 984 | // pxr only looses data for f32 values, B44 only for f16, not other any other types
|
| 985 | allow_lossy: other.encoding.compression.may_loose_data(),// TODO check specific channels sample types
|
| 986 |
|
| 987 | // consider nan and zero equal if the compression method does not support nan
|
| 988 | nan_converted_to_zero: other.encoding.compression.supports_nan().not()
|
| 989 | };
|
| 990 |
|
| 991 | self.channel_data.validate_result(&other.channel_data, options, || location() + " > channel_data" )?;
|
| 992 | Ok(())
|
| 993 | }
|
| 994 | }
|
| 995 | }
|
| 996 |
|
| 997 | impl<S> ValidateResult for AnyChannels<S> where S: ValidateResult {
|
| 998 | fn validate_result(&self, other: &Self, options: ValidationOptions, location: impl Fn()->String) -> ValidationResult {
|
| 999 | self.list.validate_result(&other.list, options, location)
|
| 1000 | }
|
| 1001 | }
|
| 1002 |
|
| 1003 | impl<S> ValidateResult for AnyChannel<S> where S: ValidateResult {
|
| 1004 | fn validate_result(&self, other: &Self, options: ValidationOptions, location: impl Fn()->String) -> ValidationResult {
|
| 1005 | if self.name != other.name { Err(location() + " > name" ) }
|
| 1006 | else if self.quantize_linearly != other.quantize_linearly { Err(location() + " > quantize_linearly" ) }
|
| 1007 | else if self.sampling != other.sampling { Err(location() + " > sampling" ) }
|
| 1008 | else {
|
| 1009 | self.sample_data.validate_result(&other.sample_data, options, || location() + " > sample_data" )
|
| 1010 | }
|
| 1011 | }
|
| 1012 | }
|
| 1013 |
|
| 1014 | impl<Pxs, Chans> ValidateResult for SpecificChannels<Pxs, Chans> where Pxs: ValidateResult, Chans: Eq {
|
| 1015 | fn validate_result(&self, other: &Self, options: ValidationOptions, location: impl Fn()->String) -> ValidationResult {
|
| 1016 | if self.channels != other.channels { Err(location() + " > specific channels" ) }
|
| 1017 | else { self.pixels.validate_result(&other.pixels, options, || location() + " > specific pixels" ) }
|
| 1018 | }
|
| 1019 | }
|
| 1020 |
|
| 1021 | impl<S> ValidateResult for Levels<S> where S: ValidateResult {
|
| 1022 | fn validate_result(&self, other: &Self, options: ValidationOptions, location: impl Fn()->String) -> ValidationResult {
|
| 1023 | self.levels_as_slice().validate_result(&other.levels_as_slice(), options, || location() + " > levels" )
|
| 1024 | }
|
| 1025 | }
|
| 1026 |
|
| 1027 | impl ValidateResult for FlatSamples {
|
| 1028 | fn validate_result(&self, other: &Self, options: ValidationOptions, location: impl Fn()->String) -> ValidationResult {
|
| 1029 | use FlatSamples::*;
|
| 1030 | match (self, other) {
|
| 1031 | (F16(values), F16(other_values)) => values.as_slice().validate_result(&other_values.as_slice(), options, ||location() + " > f16 samples" ),
|
| 1032 | (F32(values), F32(other_values)) => values.as_slice().validate_result(&other_values.as_slice(), options, ||location() + " > f32 samples" ),
|
| 1033 | (U32(values), U32(other_values)) => values.as_slice().validate_result(&other_values.as_slice(), options, ||location() + " > u32 samples" ),
|
| 1034 | (own, other) => Err(format!(" {}: samples type mismatch. expected {:?}, found {:?}" , location(), own.sample_type(), other.sample_type()))
|
| 1035 | }
|
| 1036 | }
|
| 1037 | }
|
| 1038 |
|
| 1039 | impl<T> ValidateResult for &[T] where T: ValidateResult {
|
| 1040 | fn validate_result(&self, other: &Self, options: ValidationOptions, location: impl Fn()->String) -> ValidationResult {
|
| 1041 | if self.len() != other.len() { Err(location() + " count" ) }
|
| 1042 | else {
|
| 1043 | for (index, (slf, other)) in self.iter().zip(other.iter()).enumerate() {
|
| 1044 | slf.validate_result(other, options, ||format!(" {} element [ {}] of {}" , location(), index, self.len()))?;
|
| 1045 | }
|
| 1046 | Ok(())
|
| 1047 | }
|
| 1048 | }
|
| 1049 | }
|
| 1050 |
|
| 1051 | impl<A: Array> ValidateResult for SmallVec<A> where A::Item: ValidateResult {
|
| 1052 | fn validate_result(&self, other: &Self, options: ValidationOptions, location: impl Fn()->String) -> ValidationResult {
|
| 1053 | self.as_slice().validate_result(&other.as_slice(), options, location)
|
| 1054 | }
|
| 1055 | }
|
| 1056 |
|
| 1057 | impl<A> ValidateResult for Vec<A> where A: ValidateResult {
|
| 1058 | fn validate_result(&self, other: &Self, options: ValidationOptions, location: impl Fn()->String) -> ValidationResult {
|
| 1059 | self.as_slice().validate_result(&other.as_slice(), options, location)
|
| 1060 | }
|
| 1061 | }
|
| 1062 |
|
| 1063 | impl<A,B,C,D> ValidateResult for (A, B, C, D) where A: Clone+ ValidateResult, B: Clone+ ValidateResult, C: Clone+ ValidateResult, D: Clone+ ValidateResult {
|
| 1064 | fn validate_result(&self, other: &Self, options: ValidationOptions, location: impl Fn()->String) -> ValidationResult {
|
| 1065 | self.clone().into_recursive().validate_result(&other.clone().into_recursive(), options, location)
|
| 1066 | }
|
| 1067 | }
|
| 1068 |
|
| 1069 | impl<A,B,C> ValidateResult for (A, B, C) where A: Clone+ ValidateResult, B: Clone+ ValidateResult, C: Clone+ ValidateResult {
|
| 1070 | fn validate_result(&self, other: &Self, options: ValidationOptions, location: impl Fn()->String) -> ValidationResult {
|
| 1071 | self.clone().into_recursive().validate_result(&other.clone().into_recursive(), options, location)
|
| 1072 | }
|
| 1073 | }
|
| 1074 |
|
| 1075 | // // (low priority because it is only used in the tests)
|
| 1076 | /*TODO
|
| 1077 | impl<Tuple> SimilarToLossy for Tuple where
|
| 1078 | Tuple: Clone + IntoRecursive,
|
| 1079 | <Tuple as IntoRecursive>::Recursive: SimilarToLossy,
|
| 1080 | {
|
| 1081 | fn similar_to_lossy(&self, other: &Self, max_difference: f32) -> bool {
|
| 1082 | self.clone().into_recursive().similar_to_lossy(&other.clone().into_recursive(), max_difference)
|
| 1083 | } // TODO no clone?
|
| 1084 | }*/
|
| 1085 |
|
| 1086 |
|
| 1087 | // implement for recursive types
|
| 1088 | impl ValidateResult for NoneMore {
|
| 1089 | fn validate_result(&self, _: &Self, _: ValidationOptions, _: impl Fn()->String) -> ValidationResult { Ok(()) }
|
| 1090 | }
|
| 1091 |
|
| 1092 | impl<Inner, T> ValidateResult for Recursive<Inner, T> where Inner: ValidateResult, T: ValidateResult {
|
| 1093 | fn validate_result(&self, other: &Self, options: ValidationOptions, location: impl Fn()->String) -> ValidationResult {
|
| 1094 | self.value.validate_result(&other.value, options, &location).and_then(|()|
|
| 1095 | self.inner.validate_result(&other.inner, options, &location)
|
| 1096 | )
|
| 1097 | }
|
| 1098 | }
|
| 1099 |
|
| 1100 | impl<S> ValidateResult for Option<S> where S: ValidateResult {
|
| 1101 | fn validate_result(&self, other: &Self, options: ValidationOptions, location: impl Fn()->String) -> ValidationResult {
|
| 1102 | match (self, other) {
|
| 1103 | (None, None) => Ok(()),
|
| 1104 | (Some(value), Some(other)) => value.validate_result(other, options, location),
|
| 1105 | _ => Err(location() + ": option mismatch" )
|
| 1106 | }
|
| 1107 | }
|
| 1108 | }
|
| 1109 |
|
| 1110 | impl ValidateResult for f32 {
|
| 1111 | fn validate_result(&self, other: &Self, options: ValidationOptions, location: impl Fn()->String) -> ValidationResult {
|
| 1112 | if self == other || (self.is_nan() && other.is_nan()) || (options.nan_converted_to_zero && !self.is_normal() && *other == 0.0) {
|
| 1113 | return Ok(());
|
| 1114 | }
|
| 1115 |
|
| 1116 | if options.allow_lossy {
|
| 1117 | let epsilon = 0.06;
|
| 1118 | let max_difference = 0.1;
|
| 1119 |
|
| 1120 | let adaptive_threshold = epsilon * (self.abs() + other.abs());
|
| 1121 | let tolerance = adaptive_threshold.max(max_difference);
|
| 1122 | let difference = (self - other).abs();
|
| 1123 |
|
| 1124 | return if difference <= tolerance { Ok(()) }
|
| 1125 | else { Err(format!(" {}: expected ~ {}, found {} (adaptive tolerance {})" , location(), self, other, tolerance)) };
|
| 1126 | }
|
| 1127 |
|
| 1128 | Err(format!(" {}: expected exactly {}, found {}" , location(), self, other))
|
| 1129 | }
|
| 1130 | }
|
| 1131 |
|
| 1132 | impl ValidateResult for f16 {
|
| 1133 | fn validate_result(&self, other: &Self, options: ValidationOptions, location: impl Fn()->String) -> ValidationResult {
|
| 1134 | if self.to_bits() == other.to_bits() { Ok(()) } else {
|
| 1135 | self.to_f32().validate_result(&other.to_f32(), options, location)
|
| 1136 | }
|
| 1137 | }
|
| 1138 | }
|
| 1139 |
|
| 1140 | impl ValidateResult for u32 {
|
| 1141 | fn validate_result(&self, other: &Self, options: ValidationOptions, location: impl Fn()->String) -> ValidationResult {
|
| 1142 | if self == other { Ok(()) } else { // todo to float conversion resulting in nan/infinity?
|
| 1143 | self.to_f32().validate_result(&other.to_f32(), options, location)
|
| 1144 | }
|
| 1145 | }
|
| 1146 | }
|
| 1147 |
|
| 1148 | impl ValidateResult for Sample {
|
| 1149 | fn validate_result(&self, other: &Self, options: ValidationOptions, location: impl Fn()->String) -> ValidationResult {
|
| 1150 | use Sample::*;
|
| 1151 | match (self, other) {
|
| 1152 | (F16(a), F16(b)) => a.validate_result(b, options, ||location() + " (f16)" ),
|
| 1153 | (F32(a), F32(b)) => a.validate_result(b, options, ||location() + " (f32)" ),
|
| 1154 | (U32(a), U32(b)) => a.validate_result(b, options, ||location() + " (u32)" ),
|
| 1155 | (_,_) => Err(location() + ": sample type mismatch" )
|
| 1156 | }
|
| 1157 | }
|
| 1158 | }
|
| 1159 |
|
| 1160 |
|
| 1161 | #[cfg (test)]
|
| 1162 | mod test_value_result {
|
| 1163 | use std::f32::consts::*;
|
| 1164 | use std::io::Cursor;
|
| 1165 | use crate::image::pixel_vec::PixelVec;
|
| 1166 | use crate::image::validate_results::{ValidateResult, ValidationOptions};
|
| 1167 | use crate::meta::attribute::LineOrder::Increasing;
|
| 1168 | use crate::image::{FlatSamples};
|
| 1169 |
|
| 1170 | fn expect_valid<T>(original: &T, result: &T, allow_lossy: bool, nan_converted_to_zero: bool) where T: ValidateResult {
|
| 1171 | original.validate_result(
|
| 1172 | result,
|
| 1173 | ValidationOptions { allow_lossy, nan_converted_to_zero },
|
| 1174 | || String::new()
|
| 1175 | ).unwrap();
|
| 1176 | }
|
| 1177 |
|
| 1178 | fn expect_invalid<T>(original: &T, result: &T, allow_lossy: bool, nan_converted_to_zero: bool) where T: ValidateResult {
|
| 1179 | assert!(original.validate_result(
|
| 1180 | result,
|
| 1181 | ValidationOptions { allow_lossy, nan_converted_to_zero },
|
| 1182 | || String::new()
|
| 1183 | ).is_err());
|
| 1184 | }
|
| 1185 |
|
| 1186 | #[test ]
|
| 1187 | fn test_f32(){
|
| 1188 | let original:&[f32] = &[0.0, 0.1, 0.2, 0.3, 0.4, 0.5, -20.4, f32::NAN];
|
| 1189 | let lossy:&[f32] = &[0.0, 0.2, 0.2, 0.3, 0.4, 0.5, -20.5, f32::NAN];
|
| 1190 |
|
| 1191 | expect_valid(&original, &original, true, true);
|
| 1192 | expect_valid(&original, &original, true, false);
|
| 1193 | expect_valid(&original, &original, false, true);
|
| 1194 | expect_valid(&original, &original, false, false);
|
| 1195 |
|
| 1196 | expect_invalid(&original, &lossy, false, false);
|
| 1197 | expect_valid(&original, &lossy, true, false);
|
| 1198 |
|
| 1199 | expect_invalid(&original, &&original[..original.len()-2], true, true);
|
| 1200 |
|
| 1201 | // test relative comparison with some large values
|
| 1202 | expect_valid(&1_000_f32, &1_001_f32, true, false);
|
| 1203 | expect_invalid(&1_000_f32, &1_200_f32, true, false);
|
| 1204 |
|
| 1205 | expect_valid(&10_000_f32, &10_100_f32, true, false);
|
| 1206 | expect_invalid(&10_000_f32, &12_000_f32, true, false);
|
| 1207 |
|
| 1208 | expect_valid(&33_120_f32, &30_120_f32, true, false);
|
| 1209 | expect_invalid(&33_120_f32, &20_120_f32, true, false);
|
| 1210 | }
|
| 1211 |
|
| 1212 | #[test ]
|
| 1213 | fn test_nan(){
|
| 1214 | let original:&[f32] = &[ 0.0, f32::NAN, f32::NAN ];
|
| 1215 | let lossy:&[f32] = &[ 0.0, f32::NAN, 0.0 ];
|
| 1216 |
|
| 1217 | expect_valid(&original, &lossy, true, true);
|
| 1218 | expect_invalid(&lossy, &original, true, true);
|
| 1219 |
|
| 1220 | expect_valid(&lossy, &lossy, true, true);
|
| 1221 | expect_valid(&lossy, &lossy, false, true);
|
| 1222 | }
|
| 1223 |
|
| 1224 | #[test ]
|
| 1225 | fn test_error(){
|
| 1226 |
|
| 1227 | fn print_error<T: ValidateResult>(original: &T, lossy: &T, allow_lossy: bool){
|
| 1228 | let message = original
|
| 1229 | .validate_result(
|
| 1230 | &lossy,
|
| 1231 | ValidationOptions { allow_lossy, .. Default::default() },
|
| 1232 | || String::new() // type_name::<T>().to_string()
|
| 1233 | )
|
| 1234 | .unwrap_err();
|
| 1235 |
|
| 1236 | println!("message: {}" , message);
|
| 1237 | }
|
| 1238 |
|
| 1239 | let original:&[f32] = &[ 0.0, f32::NAN, f32::NAN ];
|
| 1240 | let lossy:&[f32] = &[ 0.0, f32::NAN, 0.0 ];
|
| 1241 | print_error(&original, &lossy, false);
|
| 1242 |
|
| 1243 | print_error(&2.0, &1.0, true);
|
| 1244 | print_error(&2.0, &1.0, false);
|
| 1245 |
|
| 1246 | print_error(&FlatSamples::F32(vec![0.1,0.1]), &FlatSamples::F32(vec![0.1,0.2]), false);
|
| 1247 | print_error(&FlatSamples::U32(vec![0,0]), &FlatSamples::F32(vec![0.1,0.2]), false);
|
| 1248 |
|
| 1249 | {
|
| 1250 | let image = crate::prelude::read_all_data_from_file("tests/images/valid/openexr/MultiResolution/Kapaa.exr" ).unwrap();
|
| 1251 |
|
| 1252 | let mut mutated = image.clone();
|
| 1253 | let samples = mutated.layer_data.first_mut().unwrap()
|
| 1254 | .channel_data.list.first_mut().unwrap().sample_data.levels_as_slice_mut().first_mut().unwrap();
|
| 1255 |
|
| 1256 | match samples {
|
| 1257 | FlatSamples::F16(vals) => vals[100] = vals[1],
|
| 1258 | FlatSamples::F32(vals) => vals[100] = vals[1],
|
| 1259 | FlatSamples::U32(vals) => vals[100] = vals[1],
|
| 1260 | }
|
| 1261 |
|
| 1262 | print_error(&image, &mutated, false);
|
| 1263 | }
|
| 1264 |
|
| 1265 | // TODO check out more nested behaviour!
|
| 1266 | }
|
| 1267 |
|
| 1268 | #[test ]
|
| 1269 | fn test_uncompressed(){
|
| 1270 | use crate::prelude::*;
|
| 1271 |
|
| 1272 | let original_pixels: [(f32,f32,f32); 4] = [
|
| 1273 | (0.0, -1.1, PI),
|
| 1274 | (0.0, -1.1, TAU),
|
| 1275 | (0.0, -1.1, f32::EPSILON),
|
| 1276 | (f32::NAN, 10000.1, -1024.009),
|
| 1277 | ];
|
| 1278 |
|
| 1279 | let mut file_bytes = Vec::new();
|
| 1280 | let original_image = Image::from_encoded_channels(
|
| 1281 | (2,2),
|
| 1282 | Encoding {
|
| 1283 | compression: Compression::Uncompressed,
|
| 1284 | line_order: Increasing, // FIXME unspecified may be optimized to increasing, which destroys test eq
|
| 1285 | .. Encoding::default()
|
| 1286 | },
|
| 1287 | SpecificChannels::rgb(PixelVec::new(Vec2(2,2), original_pixels.to_vec()))
|
| 1288 | );
|
| 1289 |
|
| 1290 | original_image.write().to_buffered(Cursor::new(&mut file_bytes)).unwrap();
|
| 1291 |
|
| 1292 | let lossy_image = read().no_deep_data().largest_resolution_level()
|
| 1293 | .rgb_channels(PixelVec::<(f32,f32,f32)>::constructor, PixelVec::set_pixel)
|
| 1294 | .first_valid_layer().all_attributes().from_buffered(Cursor::new(&file_bytes)).unwrap();
|
| 1295 |
|
| 1296 | original_image.assert_equals_result(&original_image);
|
| 1297 | lossy_image.assert_equals_result(&lossy_image);
|
| 1298 | original_image.assert_equals_result(&lossy_image);
|
| 1299 | lossy_image.assert_equals_result(&original_image);
|
| 1300 | }
|
| 1301 |
|
| 1302 | #[test ]
|
| 1303 | fn test_compiles(){
|
| 1304 | use crate::prelude::*;
|
| 1305 |
|
| 1306 | fn accepts_validatable_value(_: &impl ValidateResult){}
|
| 1307 |
|
| 1308 | let object: Levels<FlatSamples> = Levels::Singular(FlatSamples::F32(Vec::default()));
|
| 1309 | accepts_validatable_value(&object);
|
| 1310 |
|
| 1311 | let object: AnyChannels<Levels<FlatSamples>> = AnyChannels::sort(SmallVec::default());
|
| 1312 | accepts_validatable_value(&object);
|
| 1313 |
|
| 1314 | let layer: Layer<AnyChannels<Levels<FlatSamples>>> = Layer::new((0,0), Default::default(), Default::default(), object);
|
| 1315 | accepts_validatable_value(&layer);
|
| 1316 |
|
| 1317 | let layers: Layers<AnyChannels<Levels<FlatSamples>>> = Default::default();
|
| 1318 | accepts_validatable_value(&layers);
|
| 1319 |
|
| 1320 | let object: Image<Layer<AnyChannels<Levels<FlatSamples>>>> = Image::from_layer(layer);
|
| 1321 | object.assert_equals_result(&object);
|
| 1322 | }
|
| 1323 | }
|
| 1324 | }
|
| 1325 |
|
| 1326 |
|
| 1327 | |