Skip to main content

pic_scale/colors/
linear_precise_scaler.rs

1/*
2 * Copyright (c) Radzivon Bartoshyk. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without modification,
5 * are permitted provided that the following conditions are met:
6 *
7 * 1.  Redistributions of source code must retain the above copyright notice, this
8 * list of conditions and the following disclaimer.
9 *
10 * 2.  Redistributions in binary form must reproduce the above copyright notice,
11 * this list of conditions and the following disclaimer in the documentation
12 * and/or other materials provided with the distribution.
13 *
14 * 3.  Neither the name of the copyright holder nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29use crate::colors::common_splitter::{SplitPlanInterceptor, Splitter};
30use crate::mixed_storage::CpuRound;
31use crate::plan::Resampling;
32use crate::validation::PicScaleError;
33use crate::{ImageSize, ImageStore, ImageStoreMut, ResamplingFunction, Scaler, ThreadingPolicy};
34use colorutils_rs::TransferFunction;
35use std::sync::Arc;
36
37#[derive(Debug, Copy, Clone)]
38/// Converts image to linear f32 components scales it and convert back.
39///
40/// This is more precise and slower than scaling in linear fixed point.
41///
42/// This is an expensive method, and its precision might not be required,
43/// consider to use [LinearApproxScaler] instead
44pub struct LinearScaler {
45    pub(crate) scaler: Scaler,
46    pub(crate) transfer_function: TransferFunction,
47}
48
49impl LinearScaler {
50    /// Creates new instance with sRGB transfer function
51    pub fn new(filter: ResamplingFunction) -> Self {
52        LinearScaler {
53            scaler: Scaler::new(filter),
54            transfer_function: TransferFunction::Srgb,
55        }
56    }
57
58    /// Creates new instance with requested transfer function
59    pub fn new_with_transfer(
60        filter: ResamplingFunction,
61        transfer_function: TransferFunction,
62    ) -> Self {
63        LinearScaler {
64            scaler: Scaler::new(filter),
65            transfer_function,
66        }
67    }
68}
69
70struct Common8BitSplitter<const N: usize> {
71    linearization: Box<[f32; 256]>,
72    gamma: Box<[u8; 65536]>,
73    has_alpha: bool,
74}
75
76impl<const N: usize> Splitter<u8, f32, N> for Common8BitSplitter<N> {
77    fn split(
78        &self,
79        from: &ImageStore<'_, u8, N>,
80        into: &mut ImageStoreMut<'_, f32, N>,
81    ) -> Result<(), PicScaleError> {
82        const S: f32 = 1. / 255.;
83        if N == 4 {
84            for (src, dst) in from
85                .as_bytes()
86                .chunks_exact(4)
87                .zip(into.buffer.borrow_mut().chunks_exact_mut(4))
88            {
89                dst[0] = self.linearization[src[0] as usize];
90                dst[1] = self.linearization[src[1] as usize];
91                dst[2] = self.linearization[src[2] as usize];
92                dst[3] = src[3] as f32 * S;
93            }
94        } else if N == 2 && self.has_alpha {
95            for (src, dst) in from
96                .as_bytes()
97                .chunks_exact(2)
98                .zip(into.buffer.borrow_mut().chunks_exact_mut(2))
99            {
100                dst[0] = self.linearization[src[0] as usize];
101                dst[1] = src[1] as f32 * S;
102            }
103        } else {
104            for (&src, dst) in from.as_bytes().iter().zip(into.buffer.borrow_mut()) {
105                *dst = self.linearization[src as usize];
106            }
107        }
108        Ok(())
109    }
110
111    fn merge(
112        &self,
113        from: &ImageStore<'_, f32, N>,
114        into: &mut ImageStoreMut<'_, u8, N>,
115    ) -> Result<(), PicScaleError> {
116        if N == 4 {
117            for (src, dst) in from
118                .as_bytes()
119                .chunks_exact(4)
120                .zip(into.buffer.borrow_mut().chunks_exact_mut(4))
121            {
122                let v0 = (src[0] * 65535.).cpu_round() as u16;
123                let v1 = (src[1] * 65535.).cpu_round() as u16;
124                let v2 = (src[2] * 65535.).cpu_round() as u16;
125
126                dst[0] = self.gamma[v0 as usize];
127                dst[1] = self.gamma[v1 as usize];
128                dst[2] = self.gamma[v2 as usize];
129                dst[3] = (src[3] * 255.).cpu_round() as u8;
130            }
131        } else if N == 2 && self.has_alpha {
132            for (src, dst) in from
133                .as_bytes()
134                .chunks_exact(2)
135                .zip(into.buffer.borrow_mut().chunks_exact_mut(2))
136            {
137                let v0 = (src[0] * 65535.).cpu_round() as u16;
138
139                dst[0] = self.gamma[v0 as usize];
140                dst[1] = (src[1] * 255.).cpu_round() as u8;
141            }
142        } else {
143            for (&src, dst) in from.as_bytes().iter().zip(into.buffer.borrow_mut()) {
144                let v = (src * 65535.).cpu_round() as u16;
145                *dst = self.gamma[v as usize];
146            }
147        }
148        Ok(())
149    }
150
151    fn bit_depth(&self) -> usize {
152        8
153    }
154}
155
156struct Common16BitSplitter<const N: usize> {
157    linearization: Box<[f32; 65536]>,
158    gamma: Box<[u16; 262144]>,
159    has_alpha: bool,
160    bit_depth: usize,
161}
162
163impl<const N: usize> Splitter<u16, f32, N> for Common16BitSplitter<N> {
164    fn split(
165        &self,
166        from: &ImageStore<'_, u16, N>,
167        into: &mut ImageStoreMut<'_, f32, N>,
168    ) -> Result<(), PicScaleError> {
169        if N == 4 {
170            let max_bit_depth_value = ((1u32 << self.bit_depth) - 1) as f32;
171
172            let v_recip = 1. / max_bit_depth_value;
173
174            for (src, dst) in from
175                .as_bytes()
176                .chunks_exact(4)
177                .zip(into.buffer.borrow_mut().chunks_exact_mut(4))
178            {
179                dst[0] = self.linearization[src[0] as usize];
180                dst[1] = self.linearization[src[1] as usize];
181                dst[2] = self.linearization[src[2] as usize];
182                dst[3] = src[3] as f32 * v_recip;
183            }
184        } else if N == 2 && self.has_alpha {
185            let max_bit_depth_value = ((1u32 << self.bit_depth) - 1) as f32;
186            let v_recip = 1. / max_bit_depth_value;
187
188            for (src, dst) in from
189                .as_bytes()
190                .chunks_exact(2)
191                .zip(into.buffer.borrow_mut().chunks_exact_mut(2))
192            {
193                dst[0] = self.linearization[src[0] as usize];
194                dst[1] = src[1] as f32 * v_recip;
195            }
196        } else {
197            for (&src, dst) in from.as_bytes().iter().zip(into.buffer.borrow_mut()) {
198                *dst = self.linearization[src as usize];
199            }
200        }
201        Ok(())
202    }
203
204    fn merge(
205        &self,
206        from: &ImageStore<'_, f32, N>,
207        into: &mut ImageStoreMut<'_, u16, N>,
208    ) -> Result<(), PicScaleError> {
209        if N == 4 {
210            let max_bit_depth_value = ((1u32 << self.bit_depth) - 1) as f32;
211            for (src, dst) in from
212                .as_bytes()
213                .chunks_exact(4)
214                .zip(into.buffer.borrow_mut().chunks_exact_mut(4))
215            {
216                let v0 = ((src[0] * 262143.).cpu_round().max(0.) as u32).min(262143);
217                let v1 = ((src[1] * 262143.).cpu_round().max(0.) as u32).min(262143);
218                let v2 = ((src[2] * 262143.).cpu_round().max(0.) as u32).min(262143);
219
220                dst[0] = self.gamma[v0 as usize];
221                dst[1] = self.gamma[v1 as usize];
222                dst[2] = self.gamma[v2 as usize];
223                dst[3] = (src[3] * max_bit_depth_value)
224                    .cpu_round()
225                    .min(max_bit_depth_value)
226                    .max(0.) as u16;
227            }
228        } else if N == 2 && self.has_alpha {
229            let max_bit_depth_value = ((1u32 << self.bit_depth) - 1) as f32;
230            for (src, dst) in from
231                .as_bytes()
232                .chunks_exact(2)
233                .zip(into.buffer.borrow_mut().chunks_exact_mut(2))
234            {
235                let v0 = ((src[0] * 262143.).cpu_round().max(0.) as u32).min(262143);
236
237                dst[0] = self.gamma[v0 as usize];
238                dst[1] = (src[1] * max_bit_depth_value)
239                    .cpu_round()
240                    .min(max_bit_depth_value)
241                    .max(0.) as u16;
242            }
243        } else {
244            for (&src, dst) in from.as_bytes().iter().zip(into.buffer.borrow_mut()) {
245                let v = ((src * 262143.).cpu_round().max(0.) as u32).min(262143);
246                *dst = self.gamma[v as usize];
247            }
248        }
249        Ok(())
250    }
251
252    fn bit_depth(&self) -> usize {
253        8
254    }
255}
256
257struct Linearization {
258    linearization: Box<[f32; 256]>,
259    gamma: Box<[u8; 65536]>,
260}
261
262struct Linearization16 {
263    linearization: Box<[f32; 65536]>,
264    gamma: Box<[u16; 262144]>,
265}
266
267fn make_linearization16(
268    transfer_function: TransferFunction,
269    bit_depth: usize,
270) -> Result<Linearization16, PicScaleError> {
271    if bit_depth < 8 {
272        return Err(PicScaleError::UnsupportedBitDepth(bit_depth));
273    }
274    let mut linearizing = Box::new([0f32; 65536]);
275    let max_lin_depth = (1u32 << bit_depth) - 1;
276    let keep_max = 1u32 << bit_depth;
277    let mut gamma = Box::new([0u16; 262144]);
278
279    let rcp_bit_depth = 1. / max_lin_depth as f32;
280
281    for (i, dst) in linearizing.iter_mut().take(keep_max as usize).enumerate() {
282        *dst = transfer_function.linearize(i as f32 * rcp_bit_depth);
283    }
284
285    const RCP_LUT_CAP: f32 = 1. / 262143.;
286
287    for (i, dst) in gamma.iter_mut().enumerate() {
288        *dst = (transfer_function.gamma(i as f32 * RCP_LUT_CAP) * max_lin_depth as f32)
289            .cpu_round()
290            .min(max_lin_depth as f32) as u16;
291    }
292
293    Ok(Linearization16 {
294        linearization: linearizing,
295        gamma,
296    })
297}
298
299fn make_linearization(transfer_function: TransferFunction) -> Linearization {
300    let mut linearizing = Box::new([0f32; 256]);
301    let mut gamma = Box::new([0u8; 65536]);
302
303    const S: f32 = 1. / 255.;
304
305    for (i, dst) in linearizing.iter_mut().enumerate() {
306        *dst = transfer_function.linearize(i as f32 * S);
307    }
308
309    const RCP_LUT_CAP: f32 = 1. / 65535.;
310
311    for (i, dst) in gamma.iter_mut().enumerate() {
312        *dst = (transfer_function.gamma(i as f32 * RCP_LUT_CAP) * 255.)
313            .cpu_round()
314            .min(255.) as u8;
315    }
316
317    Linearization {
318        linearization: linearizing,
319        gamma,
320    }
321}
322
323impl LinearScaler {
324    pub fn set_threading_policy(&mut self, threading_policy: ThreadingPolicy) -> Self {
325        self.scaler.threading_policy = threading_policy;
326        *self
327    }
328
329    pub fn plan_planar_resampling(
330        &self,
331        source_size: ImageSize,
332        target_size: ImageSize,
333    ) -> Result<Arc<Resampling<u8, 1>>, PicScaleError> {
334        let intercept = self
335            .scaler
336            .plan_planar_resampling_f32(source_size, target_size)?;
337        let scratch_size = intercept.scratch_size();
338        let lin = make_linearization(self.transfer_function);
339        Ok(Arc::new(SplitPlanInterceptor {
340            intercept,
341            splitter: Arc::new(Common8BitSplitter {
342                linearization: lin.linearization,
343                gamma: lin.gamma,
344                has_alpha: false,
345            }),
346            inner_scratch: scratch_size,
347        }))
348    }
349
350    pub fn plan_cbcr_resampling(
351        &self,
352        source_size: ImageSize,
353        target_size: ImageSize,
354    ) -> Result<Arc<Resampling<u8, 2>>, PicScaleError> {
355        let intercept = self
356            .scaler
357            .plan_cbcr_resampling_f32(source_size, target_size)?;
358        let scratch_size = intercept.scratch_size();
359        let lin = make_linearization(self.transfer_function);
360        Ok(Arc::new(SplitPlanInterceptor {
361            intercept,
362            splitter: Arc::new(Common8BitSplitter {
363                linearization: lin.linearization,
364                gamma: lin.gamma,
365                has_alpha: false,
366            }),
367            inner_scratch: scratch_size,
368        }))
369    }
370
371    pub fn plan_gray_alpha_resampling(
372        &self,
373        source_size: ImageSize,
374        target_size: ImageSize,
375        premultiply_alpha: bool,
376    ) -> Result<Arc<Resampling<u8, 2>>, PicScaleError> {
377        let intercept = self.scaler.plan_gray_alpha_resampling_f32(
378            source_size,
379            target_size,
380            premultiply_alpha,
381        )?;
382        let scratch_size = intercept.scratch_size();
383        let lin = make_linearization(self.transfer_function);
384        Ok(Arc::new(SplitPlanInterceptor {
385            intercept,
386            splitter: Arc::new(Common8BitSplitter {
387                linearization: lin.linearization,
388                gamma: lin.gamma,
389                has_alpha: true,
390            }),
391            inner_scratch: scratch_size,
392        }))
393    }
394
395    pub fn plan_rgb_resampling(
396        &self,
397        source_size: ImageSize,
398        target_size: ImageSize,
399    ) -> Result<Arc<Resampling<u8, 3>>, PicScaleError> {
400        let intercept = self
401            .scaler
402            .plan_rgb_resampling_f32(source_size, target_size)?;
403        let scratch_size = intercept.scratch_size();
404        let lin = make_linearization(self.transfer_function);
405        Ok(Arc::new(SplitPlanInterceptor {
406            intercept,
407            splitter: Arc::new(Common8BitSplitter {
408                linearization: lin.linearization,
409                gamma: lin.gamma,
410                has_alpha: false,
411            }),
412            inner_scratch: scratch_size,
413        }))
414    }
415
416    pub fn plan_rgba_resampling(
417        &self,
418        source_size: ImageSize,
419        target_size: ImageSize,
420        premultiply_alpha: bool,
421    ) -> Result<Arc<Resampling<u8, 4>>, PicScaleError> {
422        let intercept =
423            self.scaler
424                .plan_rgba_resampling_f32(source_size, target_size, premultiply_alpha)?;
425        let scratch_size = intercept.scratch_size();
426        let lin = make_linearization(self.transfer_function);
427        Ok(Arc::new(SplitPlanInterceptor {
428            intercept,
429            splitter: Arc::new(Common8BitSplitter {
430                linearization: lin.linearization,
431                gamma: lin.gamma,
432                has_alpha: true,
433            }),
434            inner_scratch: scratch_size,
435        }))
436    }
437
438    pub fn plan_planar_resampling16(
439        &self,
440        source_size: ImageSize,
441        target_size: ImageSize,
442        bit_depth: usize,
443    ) -> Result<Arc<Resampling<u16, 1>>, PicScaleError> {
444        let intercept = self
445            .scaler
446            .plan_planar_resampling_f32(source_size, target_size)?;
447        let scratch_size = intercept.scratch_size();
448        let lin = make_linearization16(self.transfer_function, bit_depth)?;
449        Ok(Arc::new(SplitPlanInterceptor {
450            intercept,
451            splitter: Arc::new(Common16BitSplitter {
452                linearization: lin.linearization,
453                gamma: lin.gamma,
454                has_alpha: false,
455                bit_depth,
456            }),
457            inner_scratch: scratch_size,
458        }))
459    }
460
461    pub fn plan_gray_alpha_resampling16(
462        &self,
463        source_size: ImageSize,
464        target_size: ImageSize,
465        premultiply_alpha: bool,
466        bit_depth: usize,
467    ) -> Result<Arc<Resampling<u16, 2>>, PicScaleError> {
468        let intercept = self.scaler.plan_gray_alpha_resampling_f32(
469            source_size,
470            target_size,
471            premultiply_alpha,
472        )?;
473        let scratch_size = intercept.scratch_size();
474        let lin = make_linearization16(self.transfer_function, bit_depth)?;
475        Ok(Arc::new(SplitPlanInterceptor {
476            intercept,
477            splitter: Arc::new(Common16BitSplitter {
478                linearization: lin.linearization,
479                gamma: lin.gamma,
480                has_alpha: true,
481                bit_depth,
482            }),
483            inner_scratch: scratch_size,
484        }))
485    }
486
487    pub fn plan_cbcr_resampling16(
488        &self,
489        source_size: ImageSize,
490        target_size: ImageSize,
491        bit_depth: usize,
492    ) -> Result<Arc<Resampling<u16, 2>>, PicScaleError> {
493        let intercept = self
494            .scaler
495            .plan_cbcr_resampling_f32(source_size, target_size)?;
496        let scratch_size = intercept.scratch_size();
497        let lin = make_linearization16(self.transfer_function, bit_depth)?;
498        Ok(Arc::new(SplitPlanInterceptor {
499            intercept,
500            splitter: Arc::new(Common16BitSplitter {
501                linearization: lin.linearization,
502                gamma: lin.gamma,
503                has_alpha: false,
504                bit_depth,
505            }),
506            inner_scratch: scratch_size,
507        }))
508    }
509
510    pub fn plan_rgb_resampling16(
511        &self,
512        source_size: ImageSize,
513        target_size: ImageSize,
514        bit_depth: usize,
515    ) -> Result<Arc<Resampling<u16, 3>>, PicScaleError> {
516        let intercept = self
517            .scaler
518            .plan_rgb_resampling_f32(source_size, target_size)?;
519        let scratch_size = intercept.scratch_size();
520        let lin = make_linearization16(self.transfer_function, bit_depth)?;
521        Ok(Arc::new(SplitPlanInterceptor {
522            intercept,
523            splitter: Arc::new(Common16BitSplitter {
524                linearization: lin.linearization,
525                gamma: lin.gamma,
526                has_alpha: false,
527                bit_depth,
528            }),
529            inner_scratch: scratch_size,
530        }))
531    }
532
533    pub fn plan_rgba_resampling16(
534        &self,
535        source_size: ImageSize,
536        target_size: ImageSize,
537        premultiply_alpha: bool,
538        bit_depth: usize,
539    ) -> Result<Arc<Resampling<u16, 4>>, PicScaleError> {
540        let intercept =
541            self.scaler
542                .plan_rgba_resampling_f32(source_size, target_size, premultiply_alpha)?;
543        let scratch_size = intercept.scratch_size();
544        let lin = make_linearization16(self.transfer_function, bit_depth)?;
545        Ok(Arc::new(SplitPlanInterceptor {
546            intercept,
547            splitter: Arc::new(Common16BitSplitter {
548                linearization: lin.linearization,
549                gamma: lin.gamma,
550                has_alpha: true,
551                bit_depth,
552            }),
553            inner_scratch: scratch_size,
554        }))
555    }
556}