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