Skip to content

Commit

Permalink
perf: move work on cropped image to creation
Browse files Browse the repository at this point in the history
Prior to this change, cropping an image was a very cheap operation, but accessing the contents of that cropped image was expensive. This change reverses that cost so that it is cheaper to access data than to crop an image.
  • Loading branch information
hschimke committed Mar 21, 2024
1 parent 6a48cee commit 01f2803
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 198 deletions.
129 changes: 49 additions & 80 deletions src/buffered_image_luminance_source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@
* limitations under the License.
*/

use std::rc::Rc;

use image::{DynamicImage, GenericImageView, ImageBuffer, Luma, Pixel};
use imageproc::geometric_transformations::rotate_about_center;

Expand All @@ -34,33 +32,20 @@ const MINUS_45_IN_RADIANS: f32 = std::f32::consts::FRAC_PI_4;
*/
pub struct BufferedImageLuminanceSource {
// extends LuminanceSource {
image: Rc<DynamicImage>,
image: DynamicImage,
width: usize,
height: usize,
left: u32,
top: u32,
}

impl BufferedImageLuminanceSource {
pub fn new(image: DynamicImage) -> Self {
let w = image.width();
let h = image.height();
Self::with_details(image, 0, 0, w as usize, h as usize)
}

pub fn with_details(
image: DynamicImage,
left: u32,
top: u32,
width: usize,
height: usize,
) -> Self {
let width = image.width() as usize;
let height = image.height() as usize;
// Self::with_details(image, 0, 0, w as usize, h as usize)
Self {
image: Rc::new(build_local_grey_image(image)),
image: build_local_grey_image(image),
width,
height,
left,
top,
}
}
}
Expand All @@ -77,8 +62,7 @@ impl LuminanceSource for BufferedImageLuminanceSource {
self.image
.as_luma8()?
.rows()
.nth(y + self.top as usize)?
.skip(self.left as usize)
.nth(y)?
.take(width)
.map(|&p| p.0[0])
.collect(),
Expand All @@ -91,59 +75,50 @@ impl LuminanceSource for BufferedImageLuminanceSource {

fn get_column(&self, x: usize) -> Vec<u8> {
let pixels: Vec<u8> = || -> Option<Vec<u8>> {
Some(
self.image
.as_luma8()?
.rows()
.skip(self.top as usize)
.fold(Vec::default(), |mut acc, e| {
acc.push(
e.into_iter()
.nth(self.left as usize + x)
.unwrap_or(&Luma([0_u8])),
);
acc
})
.iter()
.map(|&p| p.0[0])
.collect(),
)
Some(self.image.as_luma8()?.rows().fold(
Vec::with_capacity(self.get_height()),
|mut acc, e| {
let pix = e.into_iter().nth(x).unwrap_or(&Luma([0_u8]));
acc.push(pix.0[0]);
acc
},
))
}()
.unwrap_or_default();

pixels
}

fn get_matrix(&self) -> Vec<u8> {
if self.height == self.image.height() as usize && self.width == self.image.width() as usize
{
return self.image.as_bytes().to_vec();
}
let skip = self.top * self.image.width();
let row_skip = self.left;
let total_row_take = self.width;
let total_rows_to_take = self.image.width() * self.height as u32;

let unmanaged = self
.image
.as_bytes()
.iter()
.skip(skip as usize) // get to the row we want
.take(total_rows_to_take as usize)
.collect::<Vec<&u8>>(); // get all the rows we want to look at

let data = unmanaged
.chunks_exact(self.image.width() as usize) // Get rows
.flat_map(|f| {
f.iter()
.skip(row_skip as usize)
.take(total_row_take)
.copied()
}) // flatten this all out
.copied() // copy it over so that it's u8
.collect(); // collect into a vec

data
// if self.height == self.image.height() as usize && self.width == self.image.width() as usize
// {
self.image.as_bytes().to_vec()
// }
// let skip = self.image.width();
// let row_skip = 0;
// let total_row_take = self.width;
// let total_rows_to_take = self.image.width() * self.height as u32;

// let unmanaged = self
// .image
// .as_bytes()
// .iter()
// .skip(skip as usize) // get to the row we want
// .take(total_rows_to_take as usize)
// .collect::<Vec<&u8>>(); // get all the rows we want to look at

// let data = unmanaged
// .chunks_exact(self.image.width() as usize) // Get rows
// .flat_map(|f| {
// f.iter()
// .skip(row_skip as usize)
// .take(total_row_take)
// .copied()
// }) // flatten this all out
// .copied() // copy it over so that it's u8
// .collect(); // collect into a vec

// data
}

fn get_width(&self) -> usize {
Expand All @@ -156,28 +131,24 @@ impl LuminanceSource for BufferedImageLuminanceSource {

fn crop(&self, left: usize, top: usize, width: usize, height: usize) -> Result<Self> {
Ok(Self {
image: self.image.clone(),
image: self
.image
.crop_imm(left as u32, top as u32, width as u32, height as u32),
width,
height,
left: self.left + left as u32,
top: self.top + top as u32,
})
}

fn invert(&mut self) {
let mut img = (*self.image).clone();
img.invert();
self.image = Rc::new(img);
self.image.invert()
}

fn rotate_counter_clockwise(&self) -> Result<Self> {
let img = self.image.rotate270();
Ok(Self {
width: img.width() as usize,
height: img.height() as usize,
image: Rc::new(img),
left: 0,
top: 0,
image: img,
})
}

Expand All @@ -194,9 +165,7 @@ impl LuminanceSource for BufferedImageLuminanceSource {
Ok(Self {
width: new_img.width() as usize,
height: new_img.height() as usize,
image: Rc::new(new_img),
left: 0,
top: 0,
image: new_img,
})
}

Expand Down
75 changes: 32 additions & 43 deletions src/luma_luma_source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,19 @@ use crate::LuminanceSource;
pub struct Luma8LuminanceSource {
/// image dimension in form (x,y)
dimensions: (u32, u32),
/// image origin in the form (x,y)
origin: (u32, u32),
/// raw data for luma 8
data: Box<[u8]>,
/// flag indicating if the underlying data needs to be inverted for use
inverted: bool,
/// original dimensions of the data, used to manage crop
original_dimension: (u32, u32),
}
impl LuminanceSource for Luma8LuminanceSource {
const SUPPORTS_CROP: bool = true;
const SUPPORTS_ROTATION: bool = true;

fn get_row(&self, y: usize) -> Vec<u8> {
let chunk_size = self.original_dimension.0 as usize;
let row_skip = y + self.origin.1 as usize;
let column_skip = self.origin.0 as usize;
let chunk_size = self.dimensions.0 as usize;
let row_skip = y; //self.origin.1 as usize;
let column_skip = 0; //self.origin.0 as usize;
let column_take = self.dimensions.0 as usize;

let data_start = (chunk_size * row_skip) + column_skip;
Expand All @@ -37,33 +33,17 @@ impl LuminanceSource for Luma8LuminanceSource {

fn get_column(&self, x: usize) -> Vec<u8> {
self.data
.chunks_exact(self.original_dimension.0 as usize)
.skip(self.origin.1 as usize)
.fold(Vec::default(), |mut acc, e| {
acc.push(e[self.origin.0 as usize + x]);
.chunks_exact(self.dimensions.0 as usize)
// .skip(self.origin.1 as usize)
.fold(Vec::with_capacity(self.get_height()), |mut acc, e| {
let byte = e[x];
acc.push(Self::invert_if_should(byte, self.inverted));
acc
})
.iter()
.map(|byte| Self::invert_if_should(*byte, self.inverted))
.collect()
}

fn get_matrix(&self) -> Vec<u8> {
self.data
.iter()
.skip((self.original_dimension.0 * self.origin.1) as usize)
.take((self.dimensions.1 * self.original_dimension.0) as usize)
.collect::<Vec<&u8>>()
.chunks_exact(self.original_dimension.0 as usize)
.flat_map(|f| {
f.iter()
.skip((self.origin.0) as usize)
.take(self.get_width())
.copied()
}) // flatten this all out
.copied() // copy it over so that it's u8
.map(|byte| Self::invert_if_should(byte, self.inverted))
.collect() // collect into a vec
self.data.clone().into()
}

fn get_width(&self) -> usize {
Expand All @@ -81,20 +61,35 @@ impl LuminanceSource for Luma8LuminanceSource {
fn crop(&self, left: usize, top: usize, width: usize, height: usize) -> Result<Self> {
Ok(Self {
dimensions: (width as u32, height as u32),
origin: (self.origin.0 + left as u32, self.origin.1 + top as u32),
data: self.data.clone(),
// origin: (self.origin.0 + left as u32, self.origin.1 + top as u32),
data: self
.data
.chunks_exact(self.dimensions.0 as usize)
.skip(top)
.flat_map(|f| f.iter().skip(left).take(width))
.map(|byte| Self::invert_if_should(*byte, self.inverted))
.collect(),
// data: self
// .data
// .iter()
// .skip((self.dimensions.0 as usize * top) as usize) // Skip to the desired top
// .take((height * self.dimensions.0 as usize) as usize) // take only the height desired
// .collect::<Vec<&u8>>() // collect for chunks_exact
// .chunks_exact(self.dimensions.0 as usize) // Chunk data by rows
// .flat_map(|f| f.iter().skip((left) as usize).take(width).copied()) // flatten this all out
// .copied() // copy it over so that it's u8
// .map(|byte| Self::invert_if_should(byte, self.inverted))
// .collect(), // collect into a vec
inverted: self.inverted,
original_dimension: self.original_dimension,
// original_dimension: self.original_dimension,
})
}

fn rotate_counter_clockwise(&self) -> Result<Self> {
let mut new_matrix = Self {
dimensions: self.dimensions,
origin: self.origin,
data: self.data.clone(),
inverted: self.inverted,
original_dimension: self.original_dimension,
};
new_matrix.transpose();
new_matrix.reverseColumns();
Expand All @@ -108,9 +103,9 @@ impl LuminanceSource for Luma8LuminanceSource {
}

fn get_luma8_point(&self, column: usize, row: usize) -> u8 {
let chunk_size = self.original_dimension.0 as usize;
let row_skip = row + self.origin.1 as usize;
let column_skip = self.origin.0 as usize;
let chunk_size = self.dimensions.0 as usize;
let row_skip = row; //row + self.origin.1 as usize;
let column_skip = 0; //self.origin.0 as usize;

let data_start = (chunk_size * row_skip) + column_skip;
let data_point = data_start + column;
Expand Down Expand Up @@ -157,8 +152,6 @@ impl Luma8LuminanceSource {
}
self.data = new_data.into_boxed_slice();
self.dimensions = new_dim;
self.original_dimension = (self.original_dimension.1, self.original_dimension.0);
self.origin = (self.origin.1, self.origin.0);
}

fn transpose(&mut self) {
Expand All @@ -175,20 +168,16 @@ impl Luma8LuminanceSource {
pub fn new(source: Vec<u8>, width: u32, height: u32) -> Self {
Self {
dimensions: (width, height),
origin: (0, 0),
data: source.into_boxed_slice(),
inverted: false,
original_dimension: (width, height),
}
}

pub fn with_empty_image(width: usize, height: usize) -> Self {
Self {
dimensions: (width as u32, height as u32),
origin: (0, 0),
data: vec![0u8; width * height].into_boxed_slice(),
inverted: false,
original_dimension: (width as u32, height as u32),
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/oned/rss/expanded/decoders/ai_01_and_other_ais.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ use super::{AI01decoder, AbstractExpandedDecoder, GeneralAppIdDecoder};
* @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es)
* @author Eduardo Castillejo, University of Deusto (eduardo.castillejo@deusto.es)
*/
pub struct AI01AndOtherAIs<'a>(&'a BitArray, GeneralAppIdDecoder<'a>);
pub struct AI01AndOtherAIs<'a>((), GeneralAppIdDecoder<'a>);
impl AI01decoder for AI01AndOtherAIs<'_> {}
impl AbstractExpandedDecoder for AI01AndOtherAIs<'_> {
fn parseInformation(&mut self) -> Result<String> {
Expand All @@ -56,7 +56,7 @@ impl AbstractExpandedDecoder for AI01AndOtherAIs<'_> {
}
impl<'a> AI01AndOtherAIs<'_> {
pub fn new(information: &'a BitArray) -> AI01AndOtherAIs<'a> {
AI01AndOtherAIs(information, GeneralAppIdDecoder::new(information))
AI01AndOtherAIs((), GeneralAppIdDecoder::new(information))
}

//first bit encodes the linkage flag,
Expand Down
4 changes: 1 addition & 3 deletions src/oned/upc_ean_extension_5_support.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,7 @@ impl UPCEANExtension5Support {
if raw.chars().count() != 5 {
return None;
}
let Some(value) = Self::parseExtension5String(raw) else {
return None;
};
let value = Self::parseExtension5String(raw)?;

let mut result = HashMap::new();
result.insert(
Expand Down
Loading

0 comments on commit 01f2803

Please sign in to comment.