pic_scale/colors/
linear_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 */
29
30use crate::mixed_storage::CpuRound;
31use crate::pic_scale_error::{PicScaleError, try_vec};
32use crate::scaler::Scaling;
33use crate::support::check_image_size_overflow;
34use crate::{
35    ImageStore, ImageStoreMut, ImageStoreScaling, ResamplingFunction, Scaler, ScalingOptions,
36    ScalingU16, ThreadingPolicy,
37};
38use colorutils_rs::TransferFunction;
39
40#[derive(Debug, Copy, Clone)]
41/// Linearize image into u16, scale and then convert it back.
42/// It's much faster than scale in f32, however involves small precision loss
43pub struct LinearApproxScaler {
44    pub(crate) scaler: Scaler,
45    pub(crate) transfer_function: TransferFunction,
46}
47
48impl LinearApproxScaler {
49    /// Creates new instance with sRGB transfer function
50    pub fn new(filter: ResamplingFunction) -> Self {
51        LinearApproxScaler {
52            scaler: Scaler::new(filter),
53            transfer_function: TransferFunction::Srgb,
54        }
55    }
56
57    /// Creates new instance with provided transfer function
58    pub fn new_with_transfer(
59        filter: ResamplingFunction,
60        transfer_function: TransferFunction,
61    ) -> Self {
62        LinearApproxScaler {
63            scaler: Scaler::new(filter),
64            transfer_function,
65        }
66    }
67}
68
69struct Linearization {
70    linearization: Box<[u16; 256]>,
71    gamma: Box<[u8; 65536]>,
72}
73
74struct Linearization16 {
75    linearization: Box<[u16; 65536]>,
76    gamma: Box<[u16; 65536]>,
77}
78
79fn make_linearization(transfer_function: TransferFunction) -> Linearization {
80    let mut linearizing = Box::new([0u16; 256]);
81    let max_lin_depth = (1u32 << 12) - 1;
82    let mut gamma = Box::new([0u8; 65536]);
83
84    for (i, dst) in linearizing.iter_mut().enumerate() {
85        *dst = (transfer_function.linearize(i as f32 / 255.) * max_lin_depth as f32)
86            .cpu_round()
87            .min(max_lin_depth as f32) as u16;
88    }
89
90    let max_keep = 1u32 << 12;
91
92    for (i, dst) in gamma.iter_mut().take(max_keep as usize).enumerate() {
93        *dst = (transfer_function.gamma(i as f32 / max_lin_depth as f32) * 255.)
94            .cpu_round()
95            .min(255.) as u8;
96    }
97
98    Linearization {
99        linearization: linearizing,
100        gamma,
101    }
102}
103
104fn make_linearization16(
105    transfer_function: TransferFunction,
106    bit_depth: usize,
107) -> Result<Linearization16, PicScaleError> {
108    if bit_depth < 8 {
109        return Err(PicScaleError::UnsupportedBitDepth(bit_depth));
110    }
111    let mut linearizing = Box::new([0u16; 65536]);
112    let max_lin_depth = (1u32 << bit_depth) - 1;
113    let keep_max = 1u32 << bit_depth;
114    let mut gamma = Box::new([0u16; 65536]);
115
116    for (i, dst) in linearizing.iter_mut().take(keep_max as usize).enumerate() {
117        *dst = (transfer_function.linearize(i as f32 / max_lin_depth as f32) * 65535.)
118            .cpu_round()
119            .min(65535.) as u16;
120    }
121
122    for (i, dst) in gamma.iter_mut().enumerate() {
123        *dst = (transfer_function.gamma(i as f32 / 65535.) * max_lin_depth as f32)
124            .cpu_round()
125            .min(max_lin_depth as f32) as u16;
126    }
127
128    Ok(Linearization16 {
129        linearization: linearizing,
130        gamma,
131    })
132}
133
134fn resize_typical8<'a, const CN: usize>(
135    resampling_function: ResamplingFunction,
136    transfer_function: TransferFunction,
137    threading_policy: ThreadingPolicy,
138    store: &ImageStore<'a, u8, CN>,
139    into: &mut ImageStoreMut<'a, u8, CN>,
140) -> Result<(), PicScaleError>
141where
142    ImageStore<'a, u16, CN>: ImageStoreScaling<'a, u16, CN>,
143{
144    let new_size = into.get_size();
145    into.validate()?;
146    store.validate()?;
147    if store.width == 0 || store.height == 0 || new_size.width == 0 || new_size.height == 0 {
148        return Err(PicScaleError::ZeroImageDimensions);
149    }
150
151    if check_image_size_overflow(store.width, store.height, store.channels) {
152        return Err(PicScaleError::SourceImageIsTooLarge);
153    }
154
155    if check_image_size_overflow(new_size.width, new_size.height, store.channels) {
156        return Err(PicScaleError::DestinationImageIsTooLarge);
157    }
158
159    if store.width == new_size.width && store.height == new_size.height {
160        store.copied_to_mut(into);
161        return Ok(());
162    }
163
164    let mut target_vertical = try_vec![u16::default(); store.width * store.height * CN];
165
166    let mut linear_store =
167        ImageStoreMut::<u16, CN>::from_slice(&mut target_vertical, store.width, store.height)?;
168    linear_store.bit_depth = 12;
169
170    let linearization = make_linearization(transfer_function);
171
172    for (&src, dst) in store
173        .as_bytes()
174        .iter()
175        .zip(linear_store.buffer.borrow_mut())
176    {
177        *dst = linearization.linearization[src as usize];
178    }
179
180    let new_immutable_store = ImageStore::<u16, CN> {
181        buffer: std::borrow::Cow::Owned(target_vertical),
182        channels: CN,
183        width: store.width,
184        height: store.height,
185        stride: store.width * CN,
186        bit_depth: 12,
187    };
188
189    let mut new_store =
190        ImageStoreMut::<u16, CN>::try_alloc_with_depth(into.width, into.height, 12)?;
191
192    new_immutable_store.scale(
193        &mut new_store,
194        ScalingOptions {
195            resampling_function,
196            threading_policy,
197            ..Default::default()
198        },
199    )?;
200
201    for (&src, dst) in new_store.as_bytes().iter().zip(into.buffer.borrow_mut()) {
202        *dst = linearization.gamma[src as usize];
203    }
204
205    Ok(())
206}
207
208impl Scaling for LinearApproxScaler {
209    fn set_threading_policy(&mut self, threading_policy: ThreadingPolicy) {
210        self.scaler.threading_policy = threading_policy;
211    }
212
213    fn resize_plane<'a>(
214        &'a self,
215        store: &ImageStore<'a, u8, 1>,
216        into: &mut ImageStoreMut<'a, u8, 1>,
217    ) -> Result<(), PicScaleError> {
218        resize_typical8(
219            self.scaler.function,
220            self.transfer_function,
221            self.scaler.threading_policy,
222            store,
223            into,
224        )
225    }
226
227    fn resize_cbcr8<'a>(
228        &'a self,
229        store: &ImageStore<'a, u8, 2>,
230        into: &mut ImageStoreMut<'a, u8, 2>,
231    ) -> Result<(), PicScaleError> {
232        resize_typical8(
233            self.scaler.function,
234            self.transfer_function,
235            self.scaler.threading_policy,
236            store,
237            into,
238        )
239    }
240
241    fn resize_gray_alpha<'a>(
242        &'a self,
243        store: &ImageStore<'a, u8, 2>,
244        into: &mut ImageStoreMut<'a, u8, 2>,
245        premultiply_alpha: bool,
246    ) -> Result<(), PicScaleError> {
247        let new_size = into.get_size();
248        into.validate()?;
249        store.validate()?;
250        if store.width == 0 || store.height == 0 || new_size.width == 0 || new_size.height == 0 {
251            return Err(PicScaleError::ZeroImageDimensions);
252        }
253
254        if check_image_size_overflow(store.width, store.height, store.channels) {
255            return Err(PicScaleError::SourceImageIsTooLarge);
256        }
257
258        if check_image_size_overflow(new_size.width, new_size.height, store.channels) {
259            return Err(PicScaleError::DestinationImageIsTooLarge);
260        }
261
262        if store.width == new_size.width && store.height == new_size.height {
263            store.copied_to_mut(into);
264            return Ok(());
265        }
266
267        const CN: usize = 2;
268
269        let mut target_vertical = try_vec![u16::default(); store.width * store.height * CN];
270
271        let mut linear_store =
272            ImageStoreMut::<u16, CN>::from_slice(&mut target_vertical, store.width, store.height)?;
273        linear_store.bit_depth = 12;
274
275        let linearization = make_linearization(self.transfer_function);
276
277        for (src, dst) in store
278            .as_bytes()
279            .chunks_exact(2)
280            .zip(linear_store.buffer.borrow_mut().chunks_exact_mut(2))
281        {
282            dst[0] = linearization.linearization[src[0] as usize];
283            dst[1] = ((src[1] as u16) << 4) | ((src[1] as u16) >> 4);
284        }
285
286        let new_immutable_store = ImageStore::<u16, CN> {
287            buffer: std::borrow::Cow::Owned(target_vertical),
288            channels: CN,
289            width: store.width,
290            height: store.height,
291            stride: store.width * CN,
292            bit_depth: 12,
293        };
294
295        let mut new_store =
296            ImageStoreMut::<u16, CN>::try_alloc_with_depth(into.width, into.height, 12)?;
297
298        self.scaler
299            .resize_gray_alpha16(&new_immutable_store, &mut new_store, premultiply_alpha)?;
300
301        for (src, dst) in new_store
302            .as_bytes()
303            .chunks_exact(2)
304            .zip(into.buffer.borrow_mut().chunks_exact_mut(2))
305        {
306            dst[0] = linearization.gamma[src[0] as usize];
307            dst[1] = (src[1] >> 4).min(255) as u8;
308        }
309
310        Ok(())
311    }
312
313    fn resize_rgb<'a>(
314        &self,
315        store: &ImageStore<'a, u8, 3>,
316        into: &mut ImageStoreMut<'a, u8, 3>,
317    ) -> Result<(), PicScaleError> {
318        resize_typical8(
319            self.scaler.function,
320            self.transfer_function,
321            self.scaler.threading_policy,
322            store,
323            into,
324        )
325    }
326
327    fn resize_rgba<'a>(
328        &self,
329        store: &ImageStore<'a, u8, 4>,
330        into: &mut ImageStoreMut<'a, u8, 4>,
331        premultiply_alpha: bool,
332    ) -> Result<(), PicScaleError> {
333        let new_size = into.get_size();
334        into.validate()?;
335        store.validate()?;
336        if store.width == 0 || store.height == 0 || new_size.width == 0 || new_size.height == 0 {
337            return Err(PicScaleError::ZeroImageDimensions);
338        }
339
340        if check_image_size_overflow(store.width, store.height, store.channels) {
341            return Err(PicScaleError::SourceImageIsTooLarge);
342        }
343
344        if check_image_size_overflow(new_size.width, new_size.height, store.channels) {
345            return Err(PicScaleError::DestinationImageIsTooLarge);
346        }
347
348        if store.width == new_size.width && store.height == new_size.height {
349            store.copied_to_mut(into);
350            return Ok(());
351        }
352
353        const CN: usize = 4;
354
355        let mut target_vertical = try_vec![u16::default(); store.width * store.height * CN];
356
357        let mut linear_store =
358            ImageStoreMut::<u16, CN>::from_slice(&mut target_vertical, store.width, store.height)?;
359        linear_store.bit_depth = 12;
360
361        let linearization = make_linearization(self.transfer_function);
362
363        for (src, dst) in store
364            .as_bytes()
365            .chunks_exact(4)
366            .zip(linear_store.buffer.borrow_mut().chunks_exact_mut(4))
367        {
368            dst[0] = linearization.linearization[src[0] as usize];
369            dst[1] = linearization.linearization[src[1] as usize];
370            dst[2] = linearization.linearization[src[2] as usize];
371            dst[3] = ((src[3] as u16) << 4) | ((src[3] as u16) >> 4);
372        }
373
374        let new_immutable_store = ImageStore::<u16, CN> {
375            buffer: std::borrow::Cow::Owned(target_vertical),
376            channels: CN,
377            width: store.width,
378            height: store.height,
379            stride: store.width * CN,
380            bit_depth: 12,
381        };
382
383        let mut new_store =
384            ImageStoreMut::<u16, CN>::try_alloc_with_depth(into.width, into.height, 12)?;
385
386        self.scaler
387            .resize_rgba_u16(&new_immutable_store, &mut new_store, premultiply_alpha)?;
388
389        for (src, dst) in new_store
390            .as_bytes()
391            .chunks_exact(4)
392            .zip(into.buffer.borrow_mut().chunks_exact_mut(4))
393        {
394            dst[0] = linearization.gamma[src[0] as usize];
395            dst[1] = linearization.gamma[src[1] as usize];
396            dst[2] = linearization.gamma[src[2] as usize];
397            dst[3] = (src[3] >> 4).min(255) as u8;
398        }
399
400        Ok(())
401    }
402}
403
404fn resize_typical16<'a, const CN: usize>(
405    resampling_function: ResamplingFunction,
406    transfer_function: TransferFunction,
407    threading_policy: ThreadingPolicy,
408    store: &ImageStore<'a, u16, CN>,
409    into: &mut ImageStoreMut<'a, u16, CN>,
410) -> Result<(), PicScaleError>
411where
412    ImageStore<'a, u16, CN>: ImageStoreScaling<'a, u16, CN>,
413{
414    let new_size = into.get_size();
415    into.validate()?;
416    store.validate()?;
417    if store.width == 0 || store.height == 0 || new_size.width == 0 || new_size.height == 0 {
418        return Err(PicScaleError::ZeroImageDimensions);
419    }
420
421    if check_image_size_overflow(store.width, store.height, store.channels) {
422        return Err(PicScaleError::SourceImageIsTooLarge);
423    }
424
425    if check_image_size_overflow(new_size.width, new_size.height, store.channels) {
426        return Err(PicScaleError::DestinationImageIsTooLarge);
427    }
428
429    if store.width == new_size.width && store.height == new_size.height {
430        store.copied_to_mut(into);
431        return Ok(());
432    }
433
434    let mut target_vertical = try_vec![u16::default(); store.width * store.height * CN];
435
436    let mut linear_store =
437        ImageStoreMut::<u16, CN>::from_slice(&mut target_vertical, store.width, store.height)?;
438    linear_store.bit_depth = 16;
439
440    let linearization = make_linearization16(transfer_function, into.bit_depth)?;
441
442    for (&src, dst) in store
443        .as_bytes()
444        .iter()
445        .zip(linear_store.buffer.borrow_mut())
446    {
447        *dst = linearization.linearization[src as usize];
448    }
449
450    let new_immutable_store = ImageStore::<u16, CN> {
451        buffer: std::borrow::Cow::Owned(target_vertical),
452        channels: CN,
453        width: store.width,
454        height: store.height,
455        stride: store.width * CN,
456        bit_depth: 16,
457    };
458
459    let mut new_store =
460        ImageStoreMut::<u16, CN>::try_alloc_with_depth(into.width, into.height, 16)?;
461
462    new_immutable_store.scale(
463        &mut new_store,
464        ScalingOptions {
465            resampling_function,
466            threading_policy,
467            ..Default::default()
468        },
469    )?;
470
471    for (&src, dst) in new_store.as_bytes().iter().zip(into.buffer.borrow_mut()) {
472        *dst = linearization.gamma[src as usize];
473    }
474
475    Ok(())
476}
477
478impl ScalingU16 for LinearApproxScaler {
479    fn resize_plane_u16<'a>(
480        &'a self,
481        store: &ImageStore<'a, u16, 1>,
482        into: &mut ImageStoreMut<'a, u16, 1>,
483    ) -> Result<(), PicScaleError> {
484        resize_typical16(
485            self.scaler.function,
486            self.transfer_function,
487            self.scaler.threading_policy,
488            store,
489            into,
490        )
491    }
492
493    fn resize_cbcr_u16<'a>(
494        &'a self,
495        store: &ImageStore<'a, u16, 2>,
496        into: &mut ImageStoreMut<'a, u16, 2>,
497    ) -> Result<(), PicScaleError> {
498        resize_typical16(
499            self.scaler.function,
500            self.transfer_function,
501            self.scaler.threading_policy,
502            store,
503            into,
504        )
505    }
506
507    fn resize_gray_alpha16<'a>(
508        &'a self,
509        store: &ImageStore<'a, u16, 2>,
510        into: &mut ImageStoreMut<'a, u16, 2>,
511        premultiply_alpha: bool,
512    ) -> Result<(), PicScaleError> {
513        let new_size = into.get_size();
514        into.validate()?;
515        store.validate()?;
516        if store.width == 0 || store.height == 0 || new_size.width == 0 || new_size.height == 0 {
517            return Err(PicScaleError::ZeroImageDimensions);
518        }
519
520        if check_image_size_overflow(store.width, store.height, store.channels) {
521            return Err(PicScaleError::SourceImageIsTooLarge);
522        }
523
524        if check_image_size_overflow(new_size.width, new_size.height, store.channels) {
525            return Err(PicScaleError::DestinationImageIsTooLarge);
526        }
527
528        if store.width == new_size.width && store.height == new_size.height {
529            store.copied_to_mut(into);
530            return Ok(());
531        }
532
533        const CN: usize = 2;
534
535        let mut target_vertical = try_vec![u16::default(); store.width * store.height * CN];
536
537        let mut linear_store =
538            ImageStoreMut::<u16, CN>::from_slice(&mut target_vertical, store.width, store.height)?;
539        linear_store.bit_depth = 16;
540
541        let linearization = make_linearization16(self.transfer_function, into.bit_depth)?;
542
543        let max_bit_depth_value = ((1u32 << into.bit_depth) - 1) as f32;
544
545        let a_f_scale = 65535. / max_bit_depth_value;
546
547        for (src, dst) in store
548            .as_bytes()
549            .chunks_exact(2)
550            .zip(linear_store.buffer.borrow_mut().chunks_exact_mut(2))
551        {
552            dst[0] = linearization.linearization[src[0] as usize];
553            dst[1] = (src[1] as f32 * a_f_scale).cpu_round().min(65535.) as u16;
554        }
555
556        let new_immutable_store = ImageStore::<u16, CN> {
557            buffer: std::borrow::Cow::Owned(target_vertical),
558            channels: CN,
559            width: store.width,
560            height: store.height,
561            stride: store.width * CN,
562            bit_depth: 16,
563        };
564
565        let mut new_store =
566            ImageStoreMut::<u16, CN>::try_alloc_with_depth(into.width, into.height, 16)?;
567
568        self.scaler
569            .resize_gray_alpha16(&new_immutable_store, &mut new_store, premultiply_alpha)?;
570
571        let a_r_scale = max_bit_depth_value / 65535.;
572
573        for (src, dst) in new_store
574            .as_bytes()
575            .chunks_exact(2)
576            .zip(into.buffer.borrow_mut().chunks_exact_mut(2))
577        {
578            dst[0] = linearization.gamma[src[0] as usize];
579            dst[1] = (src[1] as f32 * a_r_scale)
580                .cpu_round()
581                .min(max_bit_depth_value) as u16;
582        }
583
584        Ok(())
585    }
586
587    fn resize_rgb_u16<'a>(
588        &'a self,
589        store: &ImageStore<'a, u16, 3>,
590        into: &mut ImageStoreMut<'a, u16, 3>,
591    ) -> Result<(), PicScaleError> {
592        resize_typical16(
593            self.scaler.function,
594            self.transfer_function,
595            self.scaler.threading_policy,
596            store,
597            into,
598        )
599    }
600
601    fn resize_rgba_u16<'a>(
602        &'a self,
603        store: &ImageStore<'a, u16, 4>,
604        into: &mut ImageStoreMut<'a, u16, 4>,
605        premultiply_alpha: bool,
606    ) -> Result<(), PicScaleError> {
607        let new_size = into.get_size();
608        into.validate()?;
609        store.validate()?;
610        if store.width == 0 || store.height == 0 || new_size.width == 0 || new_size.height == 0 {
611            return Err(PicScaleError::ZeroImageDimensions);
612        }
613
614        if check_image_size_overflow(store.width, store.height, store.channels) {
615            return Err(PicScaleError::SourceImageIsTooLarge);
616        }
617
618        if check_image_size_overflow(new_size.width, new_size.height, store.channels) {
619            return Err(PicScaleError::DestinationImageIsTooLarge);
620        }
621
622        if store.width == new_size.width && store.height == new_size.height {
623            store.copied_to_mut(into);
624            return Ok(());
625        }
626
627        const CN: usize = 4;
628
629        let mut target_vertical = try_vec![u16::default(); store.width * store.height * CN];
630
631        let mut linear_store =
632            ImageStoreMut::<u16, CN>::from_slice(&mut target_vertical, store.width, store.height)?;
633        linear_store.bit_depth = 16;
634
635        let linearization = make_linearization16(self.transfer_function, into.bit_depth)?;
636
637        let max_bit_depth_value = ((1u32 << into.bit_depth) - 1) as f32;
638
639        let a_f_scale = 65535. / max_bit_depth_value;
640
641        for (src, dst) in store
642            .as_bytes()
643            .chunks_exact(4)
644            .zip(linear_store.buffer.borrow_mut().chunks_exact_mut(4))
645        {
646            dst[0] = linearization.linearization[src[0] as usize];
647            dst[1] = linearization.linearization[src[1] as usize];
648            dst[2] = linearization.linearization[src[2] as usize];
649            dst[3] = (src[3] as f32 * a_f_scale).cpu_round().min(65535.) as u16;
650        }
651
652        let new_immutable_store = ImageStore::<u16, CN> {
653            buffer: std::borrow::Cow::Owned(target_vertical),
654            channels: CN,
655            width: store.width,
656            height: store.height,
657            stride: store.width * CN,
658            bit_depth: 16,
659        };
660
661        let mut new_store =
662            ImageStoreMut::<u16, CN>::try_alloc_with_depth(into.width, into.height, 16)?;
663
664        self.scaler
665            .resize_rgba_u16(&new_immutable_store, &mut new_store, premultiply_alpha)?;
666
667        let a_r_scale = max_bit_depth_value / 65535.;
668
669        for (src, dst) in new_store
670            .as_bytes()
671            .chunks_exact(4)
672            .zip(into.buffer.borrow_mut().chunks_exact_mut(4))
673        {
674            dst[0] = linearization.gamma[src[0] as usize];
675            dst[1] = linearization.gamma[src[1] as usize];
676            dst[2] = linearization.gamma[src[2] as usize];
677            dst[3] = (src[3] as f32 * a_r_scale)
678                .cpu_round()
679                .min(max_bit_depth_value) as u16;
680        }
681
682        Ok(())
683    }
684}