1use colorutils_rs::{
31 SRGB_TO_XYZ_D65, TransferFunction, XYZ_TO_SRGB_D65, lch_to_rgb, lch_with_alpha_to_rgba,
32 rgb_to_lch, rgba_to_lch_with_alpha,
33};
34
35use crate::pic_scale_error::{PicScaleError, try_vec};
36use crate::scaler::ScalingF32;
37use crate::support::check_image_size_overflow;
38use crate::{ImageStore, ImageStoreMut, ResamplingFunction, Scaler, Scaling, ThreadingPolicy};
39
40#[derive(Debug, Copy, Clone)]
41pub struct LChScaler {
43 pub(crate) scaler: Scaler,
44}
45
46impl LChScaler {
47 pub fn new(filter: ResamplingFunction) -> Self {
48 LChScaler {
49 scaler: Scaler::new(filter),
50 }
51 }
52
53 fn rgba_to_lcha<'a>(store: &ImageStore<'a, u8, 4>) -> ImageStore<'a, f32, 4> {
54 let mut source_slice = vec![f32::default(); 4 * store.width * store.height];
55 let lab_stride = store.width as u32 * 4u32 * size_of::<f32>() as u32;
56 rgba_to_lch_with_alpha(
57 store.buffer.as_ref(),
58 store.width as u32 * 4u32,
59 &mut source_slice,
60 lab_stride,
61 store.width as u32,
62 store.height as u32,
63 &SRGB_TO_XYZ_D65,
64 TransferFunction::Srgb,
65 );
66
67 ImageStore::<f32, 4> {
68 buffer: std::borrow::Cow::Owned(source_slice),
69 channels: 4,
70 width: store.width,
71 height: store.height,
72 stride: store.width * 4,
73 bit_depth: store.bit_depth,
74 }
75 }
76
77 fn lcha_to_srgba<'a>(store: &ImageStoreMut<'a, f32, 4>, into: &mut ImageStoreMut<'a, u8, 4>) {
78 lch_with_alpha_to_rgba(
79 store.buffer.borrow(),
80 store.width as u32 * 4u32 * size_of::<f32>() as u32,
81 into.buffer.borrow_mut(),
82 store.width as u32 * 4u32,
83 store.width as u32,
84 store.height as u32,
85 &XYZ_TO_SRGB_D65,
86 TransferFunction::Srgb,
87 );
88 }
89}
90
91impl Scaling for LChScaler {
92 fn set_threading_policy(&mut self, threading_policy: ThreadingPolicy) {
93 self.scaler.set_threading_policy(threading_policy)
94 }
95
96 fn resize_plane<'a>(
97 &'a self,
98 _: &ImageStore<'a, u8, 1>,
99 _: &mut ImageStoreMut<'a, u8, 1>,
100 ) -> Result<(), PicScaleError> {
101 unimplemented!()
102 }
103
104 fn resize_cbcr8<'a>(
105 &'a self,
106 _: &ImageStore<'a, u8, 2>,
107 _: &mut ImageStoreMut<'a, u8, 2>,
108 ) -> Result<(), PicScaleError> {
109 unimplemented!()
110 }
111
112 fn resize_gray_alpha<'a>(
113 &'a self,
114 _: &ImageStore<'a, u8, 2>,
115 _: &mut ImageStoreMut<'a, u8, 2>,
116 _: bool,
117 ) -> Result<(), PicScaleError> {
118 unimplemented!()
119 }
120
121 fn resize_rgb<'a>(
122 &'a self,
123 store: &ImageStore<'a, u8, 3>,
124 into: &mut ImageStoreMut<'a, u8, 3>,
125 ) -> Result<(), PicScaleError> {
126 let new_size = into.get_size();
127 into.validate()?;
128 store.validate()?;
129 if store.width == 0 || store.height == 0 || new_size.width == 0 || new_size.height == 0 {
130 return Err(PicScaleError::ZeroImageDimensions);
131 }
132
133 if check_image_size_overflow(store.width, store.height, store.channels) {
134 return Err(PicScaleError::SourceImageIsTooLarge);
135 }
136
137 if check_image_size_overflow(new_size.width, new_size.height, store.channels) {
138 return Err(PicScaleError::DestinationImageIsTooLarge);
139 }
140
141 if store.width == new_size.width && store.height == new_size.height {
142 store.copied_to_mut(into);
143 return Ok(());
144 }
145
146 const CN: usize = 3;
147
148 let mut target_vertical = try_vec![f32::default(); store.width * store.height * CN];
149
150 let mut lab_store =
151 ImageStoreMut::<f32, CN>::from_slice(&mut target_vertical, store.width, store.height)?;
152 lab_store.bit_depth = into.bit_depth;
153
154 let lab_stride = lab_store.width as u32 * CN as u32 * std::mem::size_of::<f32>() as u32;
155 rgb_to_lch(
156 store.buffer.as_ref(),
157 store.width as u32 * CN as u32,
158 lab_store.buffer.borrow_mut(),
159 lab_stride,
160 lab_store.width as u32,
161 lab_store.height as u32,
162 &SRGB_TO_XYZ_D65,
163 TransferFunction::Srgb,
164 );
165
166 let new_immutable_store = ImageStore::<f32, CN> {
167 buffer: std::borrow::Cow::Owned(target_vertical),
168 channels: CN,
169 width: store.width,
170 height: store.height,
171 stride: store.width * CN,
172 bit_depth: into.bit_depth,
173 };
174
175 let mut new_store = ImageStoreMut::<f32, CN>::try_alloc(into.width, into.height)?;
176 self.scaler
177 .resize_rgb_f32(&new_immutable_store, &mut new_store)?;
178
179 let new_lab_stride = new_store.width as u32 * CN as u32 * std::mem::size_of::<f32>() as u32;
180
181 lch_to_rgb(
182 new_store.buffer.borrow(),
183 new_lab_stride,
184 into.buffer.borrow_mut(),
185 into.width as u32 * CN as u32,
186 new_store.width as u32,
187 new_store.height as u32,
188 &XYZ_TO_SRGB_D65,
189 TransferFunction::Srgb,
190 );
191 Ok(())
192 }
193
194 fn resize_rgba<'a>(
195 &'a self,
196 store: &ImageStore<'a, u8, 4>,
197 into: &mut ImageStoreMut<'a, u8, 4>,
198 premultiply_alpha: bool,
199 ) -> Result<(), PicScaleError> {
200 let new_size = into.get_size();
201 into.validate()?;
202 store.validate()?;
203 if store.width == 0 || store.height == 0 || new_size.width == 0 || new_size.height == 0 {
204 return Err(PicScaleError::ZeroImageDimensions);
205 }
206
207 if check_image_size_overflow(store.width, store.height, store.channels) {
208 return Err(PicScaleError::SourceImageIsTooLarge);
209 }
210
211 if check_image_size_overflow(new_size.width, new_size.height, store.channels) {
212 return Err(PicScaleError::DestinationImageIsTooLarge);
213 }
214
215 if store.width == new_size.width && store.height == new_size.height {
216 store.copied_to_mut(into);
217 return Ok(());
218 }
219
220 let lab_store = Self::rgba_to_lcha(store);
221 let mut new_target_store = ImageStoreMut::try_alloc(new_size.width, new_size.height)?;
222
223 self.scaler
224 .resize_rgba_f32(&lab_store, &mut new_target_store, premultiply_alpha)?;
225 Self::lcha_to_srgba(&new_target_store, into);
226 Ok(())
227 }
228}