1use 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)]
41pub struct LinearApproxScaler {
44 pub(crate) scaler: Scaler,
45 pub(crate) transfer_function: TransferFunction,
46}
47
48impl LinearApproxScaler {
49 pub fn new(filter: ResamplingFunction) -> Self {
51 LinearApproxScaler {
52 scaler: Scaler::new(filter),
53 transfer_function: TransferFunction::Srgb,
54 }
55 }
56
57 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}