I am making a program that can read parts of an image to extract the RGB values of pixels at any set of coordinates in an image. This is something I've already got set up with the Rust image library but there is an issue I've found that I hope I can fix.
The whole image is loaded in at once:
let img = image::open(dir).unwrap();
For my use case, this won't work for very large images as it exceeds any built in limits of the library. I want it to only read a small set of pixels of the image in question, irrespective of how big the whole image is.
On the image library website there is a trait called ImageDecoderRect and I think it's exactly what I'm looking for, a way to read only a small subsection of an image without the whole image being 'loaded' at once and getting the program to freak out.
I do not know if this ImageDecoderRect trait is actually what I'm looking for, and if it is what I want, I don't know the syntax to use it in my program as I can't find a single example of it being used (very new to Rust programming).
I assume you are seeking a ready-to-use decoder (instead of implement one yourself), and unless your image is in one of a few formats which has built-in impl of the trait, I guess you are out of luck. but if your picture happens to be one of the formats, e.g. bmp, you can use the following code to load portion of the image:
// import the trait into scope so you can call method defined in the trait
use image::ImageDecoderRect;
// create the decoder object:
let mut decoder = image::codecs::bmp::BmpDecoder::new(std::fs::File::open("image.bmp").unwrap()).unwrap();
// get the decoded color type, typically it's the same format of the file, as bmp is not compressed
let color_type = decoder.color_type();
// define which portion of the image you want to load
let x = 65536;
let y = 32768;
let width = 1024;
let height = 1024;
// allocate the buffer to store the decoded pixels, assuming tightly packed without paddings between scanlines
let mut buffer = vec![0; width * heigh * color_type.bytes_per_pixel() * color_type.channel_count()];
// load the pixels to the buffer
decoder.read_rect(x, y, width, height, &mut buffer).unwrap();
// you can use the pixels stored in the buffer directly, or you can wrap them into a ImageBuffer
// you need to make sure the `Pixel` type is correct
// in this example, I assume it's Rgb8
assert!(color_type == ColorType::Rgb8);
let loaded_image = image::RgbImage::from_vec(width, height, buffer);
you probably don't need to implement the ImageDecoder yourself, as there are decoders for every supported formats already in image crate which you can wrap. it's the ImageDecoderRect trait that you need to implement, because not every image format can be implemented efficiently, mainly due to compression. that's also the reason only very few selected decoders have ImageDecoderRect implemented out of the box.
to efficiently implement ImageDecoderRect, the format should be able to seek to the offset location, but most compressed image format lack the ability to seek fast. it is doable though. if you try to implement for such formats anyway, you'll probably end up decode sequentially then skip/discard the unwanted pixels. in such cases, it is purely for the purpose of saving memory footprint (as opposed to efficiency in runtime).
What exactly does this mean? I'm not too sure how this could be done. I am new to the concept of traits in Rust, so even trying to apply it in this context is even more confusing!
ImageDecoderRect has ImageDecoder as a super trait, so a type implements ImageDecoderRect must also implement ImageDecoder. what I meant was, you don't need to actually implement ImageDecoder for the specific file format you are interested, you can simply wrap an existing decoder.
for example, the format you need to decode is png, you can make your decoder type a wrapper on top of image::codecs::png::PngDecoder and delegate the ImageDecoder implementation to it. but you do have to implement the ImageDecoderRect trait yourself. the reason you need to make your own wrapper type is due to the orphan rule.
/// wrapper decoder type
struct MyPngDedecoder<R: Read>(PngDecoder<R>);
/// delegate the super trait impl to PngDecoder
impl<'a, R: Read + 'a> ImageDecoder<'a> for MyDecoder<R> {
type Reader = <PngDecoder<R> as ImageDecoder<'a>>::Reader;
fn dimensions(&self) -> (u32, u32) { self.0.dimensions() }
fn color_type(&self) -> ColorType { self.0.color_type() }
fn into_reader(self) -> ImageResult<Self::Reader> { self.0.into_reader() }
}
/// actually implement the function to decode portion of the image
impl<'a, R: Read + 'a> ImageDecoderRect<'a> for MyDecoder<R> {
fn read_rect_with_progress<F: Fn(image::Progress)>(
&mut self,
x: u32,
y: u32,
width: u32,
height: u32,
buf: &mut [u8],
progress_callback: F,
) -> ImageResult<()> {
// possible implementation: decode row by row but discard unwanted pixels
todo!()
}
}
if you are new to rust, there are many concept you need to understand first, like lifetime bounds, associated type, the seemingly magical Fn trait, etc.