rxing/
buffered_image_luminance_source.rs

1/*
2 * Copyright 2009 ZXing authors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17use std::borrow::Cow;
18
19use image::{DynamicImage, GenericImageView, ImageBuffer, Luma, Pixel};
20use imageproc::geometric_transformations::rotate_about_center;
21
22use crate::common::Result;
23use crate::LuminanceSource;
24
25// const MINUS_45_IN_RADIANS: f32 = -0.7853981633974483; // Math.toRadians(-45.0)
26const MINUS_45_IN_RADIANS: f32 = std::f32::consts::FRAC_PI_4;
27
28/**
29 * This LuminanceSource implementation is meant for J2SE clients and our blackbox unit tests.
30 *
31 * @author dswitkin@google.com (Daniel Switkin)
32 * @author Sean Owen
33 * @author code@elektrowolle.de (Wolfgang Jung)
34 */
35pub struct BufferedImageLuminanceSource {
36    // extends LuminanceSource {
37    image: DynamicImage,
38    width: usize,
39    height: usize,
40}
41
42impl BufferedImageLuminanceSource {
43    pub fn new(image: DynamicImage) -> Self {
44        let width = image.width() as usize;
45        let height = image.height() as usize;
46        // Self::with_details(image, 0, 0, w as usize, h as usize)
47        Self {
48            image: build_local_grey_image(image),
49            width,
50            height,
51        }
52    }
53}
54
55impl LuminanceSource for BufferedImageLuminanceSource {
56    const SUPPORTS_CROP: bool = true;
57    const SUPPORTS_ROTATION: bool = true;
58
59    fn get_row(&'_ self, y: usize) -> Option<Cow<'_, [u8]>> {
60        let buf = self.image.as_luma8()?;
61
62        let width = self.get_width();
63        let stride = buf.width() as usize; // full row length in pixels
64        let start = y
65            .checked_mul(stride) // guard against overflow
66            .and_then(|off| off.checked_add(0))
67            .unwrap_or(0);
68
69        // Make sure we don’t go past the end
70        if start + width > buf.as_raw().len() {
71            return None;
72        }
73
74        // Copy the exact sub-slice in one memcpy
75        Some(Cow::Borrowed(&buf.as_raw()[start..start + width]))
76    }
77
78    fn get_column(&self, x: usize) -> Vec<u8> {
79        let pixels: Vec<u8> = || -> Option<Vec<u8>> {
80            Some(self.image.as_luma8()?.rows().fold(
81                Vec::with_capacity(self.get_height()),
82                |mut acc, e| {
83                    let pix = e.into_iter().nth(x).unwrap_or(&Luma([0_u8]));
84                    acc.push(pix.0[0]);
85                    acc
86                },
87            ))
88        }()
89        .unwrap_or_default();
90
91        pixels
92    }
93
94    fn get_matrix(&self) -> Vec<u8> {
95        // if self.height == self.image.height() as usize && self.width == self.image.width() as usize
96        // {
97        self.image.as_bytes().to_vec()
98        // }
99        // let skip = self.image.width();
100        // let row_skip = 0;
101        // let total_row_take = self.width;
102        // let total_rows_to_take = self.image.width() * self.height as u32;
103
104        // let unmanaged = self
105        //     .image
106        //     .as_bytes()
107        //     .iter()
108        //     .skip(skip as usize) // get to the row we want
109        //     .take(total_rows_to_take as usize)
110        //     .collect::<Vec<&u8>>(); // get all the rows we want to look at
111
112        // let data = unmanaged
113        //     .chunks_exact(self.image.width() as usize) // Get rows
114        //     .flat_map(|f| {
115        //         f.iter()
116        //             .skip(row_skip as usize)
117        //             .take(total_row_take)
118        //             .copied()
119        //     }) // flatten this all out
120        //     .copied() // copy it over so that it's u8
121        //     .collect(); // collect into a vec
122
123        // data
124    }
125
126    fn get_width(&self) -> usize {
127        self.width
128    }
129
130    fn get_height(&self) -> usize {
131        self.height
132    }
133
134    fn crop(&self, left: usize, top: usize, width: usize, height: usize) -> Result<Self> {
135        Ok(Self {
136            image: self
137                .image
138                .crop_imm(left as u32, top as u32, width as u32, height as u32),
139            width,
140            height,
141        })
142    }
143
144    fn invert(&mut self) {
145        self.image.invert()
146    }
147
148    fn rotate_counter_clockwise(&self) -> Result<Self> {
149        let img = self.image.rotate270();
150        Ok(Self {
151            width: img.width() as usize,
152            height: img.height() as usize,
153            image: img,
154        })
155    }
156
157    fn rotate_counter_clockwise_45(&self) -> Result<Self> {
158        let img = rotate_about_center(
159            &self.image.to_luma8(),
160            MINUS_45_IN_RADIANS,
161            imageproc::geometric_transformations::Interpolation::Nearest,
162            Luma([u8::MAX / 2; 1]),
163        );
164
165        let new_img = DynamicImage::from(img);
166
167        Ok(Self {
168            width: new_img.width() as usize,
169            height: new_img.height() as usize,
170            image: new_img,
171        })
172    }
173
174    fn get_luma8_point(&self, x: usize, y: usize) -> u8 {
175        self.image.get_pixel(x as u32, y as u32).to_luma().0[0]
176    }
177}
178
179fn build_local_grey_image(source: DynamicImage) -> DynamicImage {
180    let raster = match source {
181        DynamicImage::ImageLuma8(img) => img,
182        DynamicImage::ImageLumaA8(img) => {
183            let mut raster: ImageBuffer<_, Vec<_>> = ImageBuffer::new(img.width(), img.height());
184
185            for (x, y, new_pixel) in raster.enumerate_pixels_mut() {
186                let pixel = img.get_pixel(x, y);
187                let [luma, alpha] = pixel.0;
188                if alpha == 0 {
189                    // white, so we know its luminance is 255
190                    *new_pixel = Luma([0xFF])
191                } else {
192                    *new_pixel = Luma([luma.saturating_mul(alpha)])
193                }
194            }
195
196            raster
197        }
198        // DynamicImage::ImageRgb8(_) => todo!(),
199        // DynamicImage::ImageRgba8(_) => todo!(),
200        DynamicImage::ImageLuma16(img) => {
201            let mut raster: ImageBuffer<_, Vec<_>> = ImageBuffer::new(img.width(), img.height());
202
203            for (x, y, new_pixel) in raster.enumerate_pixels_mut() {
204                let pixel = img.get_pixel(x, y);
205                let [luma] = pixel.0;
206
207                *new_pixel = Luma([(luma / u8::MAX as u16) as u8])
208            }
209
210            raster
211        }
212        DynamicImage::ImageLumaA16(img) => {
213            let mut raster: ImageBuffer<_, Vec<_>> = ImageBuffer::new(img.width(), img.height());
214
215            for (x, y, new_pixel) in raster.enumerate_pixels_mut() {
216                let pixel = img.get_pixel(x, y);
217                let [luma, alpha] = pixel.0;
218                if alpha == 0 {
219                    // white, so we know its luminance is 255
220                    *new_pixel = Luma([0xFF])
221                } else {
222                    *new_pixel = Luma([((luma.saturating_mul(alpha)) / u8::MAX as u16) as u8])
223                }
224            }
225
226            raster
227        }
228        // DynamicImage::ImageRgb16(_) => todo!(),
229        // DynamicImage::ImageRgba16(_) => todo!(),
230        // DynamicImage::ImageRgb32F(_) => todo!(),
231        // DynamicImage::ImageRgba32F(_) => todo!(),
232        _ => {
233            let img = source.to_rgba8();
234
235            let mut raster: ImageBuffer<_, Vec<_>> =
236                ImageBuffer::new(source.width(), source.height());
237
238            for (x, y, new_pixel) in raster.enumerate_pixels_mut() {
239                let pixel = img.get_pixel(x, y);
240                let [red, green, blue, alpha] = pixel.0;
241                if alpha == 0 {
242                    // white, so we know its luminance is 255
243                    *new_pixel = Luma([0xFF])
244                } else {
245                    // .299R + 0.587G + 0.114B (YUV/YIQ for PAL and NTSC),
246                    // (306*R) >> 10 is approximately equal to R*0.299, and so on.
247                    // 0x200 >> 10 is 0.5, it implements rounding.
248                    *new_pixel = Luma([((306 * (red as u64)
249                        + 601 * (green as u64)
250                        + 117 * (blue as u64)
251                        + 0x200)
252                        >> 10) as u8])
253                }
254            }
255            raster
256        }
257    };
258
259    DynamicImage::from(raster)
260}