Where communities thrive


  • Join over 1.5M+ people
  • Join over 100K+ communities
  • Free without limits
  • Create your own community
People
Repo info
Activity
  • Dec 01 18:14
    Xaeroxe opened #1830
  • Dec 01 10:01
    BearOve edited #1829
  • Dec 01 10:01
    BearOve opened #1829
  • Nov 25 19:52
    pratik-mahamuni edited #1822
  • Nov 25 19:49
    pratik-mahamuni commented #1822
  • Nov 25 19:47
    pratik-mahamuni commented #1822
  • Nov 25 15:50

    HeroicKatora on master

    Remove exception for old flate2… Merge pull request #1827 from f… (compare)

  • Nov 25 15:50
    HeroicKatora closed #1827
  • Nov 25 03:31
    fintelia opened #1827
  • Nov 21 12:10
    kangalioo commented #1825
  • Nov 21 12:07
    kangalioo commented #1825
  • Nov 21 12:00
    HeroicKatora commented #1825
  • Nov 21 10:01
    kangalioo commented #1825
  • Nov 20 13:10
    HeroicKatora commented #1825
  • Nov 20 12:55
    HeroicKatora commented #1826
  • Nov 20 12:55

    HeroicKatora on master

    Update to gif 0.12 Merge pull request #1826 from s… (compare)

  • Nov 20 12:55
    HeroicKatora closed #1826
  • Nov 20 09:52
    sdroege opened #1826
  • Nov 20 04:35
    HeroicKatora edited #1825
  • Nov 20 03:56
    lidarbtc opened #1825
Rupansh
@rupansh
Hello I am trying to implement seam resize
Deleting seam along the width seems to be working fine but I can't figure out what's wrong with it when I try to delete them along the height.
Here's the code for deleting a single seam
    fn nuke_seamline_h(&self, buf: &mut Vec<u8>, image: &mut Vec<u8>, width: usize) {
        let mut min_idx: usize = 0;
        let mut min: u8 = 255;
        let mut seam = BTreeSet::<usize>::new();
        for i in (0..(buf.len()-width+1)).step_by(width) {
            if buf[i] <= min {
                min_idx = i;
                min = buf[i];
            }
        }

        seam.insert(min_idx);
        while (min_idx + 1) % width != 0 {
            let tedge =  min_idx < width;
            let bedge = min_idx + width >= buf.len();
            let midi = min_idx+1;
            let mid = buf[midi];
            let bef = if !tedge { buf[midi-width] } else { 255 };
            let aft = if !bedge { buf[midi+width] } else { 255 };
            let mut min = bef;
            let mut t_minid = midi-width;
            if mid <= bef {
                min = mid;
                t_minid = midi;
            }
            if min <= aft {
                min_idx = t_minid;
            } else {
                min_idx = midi+width;
            }

            seam.insert(min_idx);
        }

        for (rem, i) in seam.iter().enumerate() {
            image.remove(*i - rem);
            buf.remove(*i - rem);
        }
    }
The resulting image looks like this
buf is the gradient of the image by the way
HeroicKatora
@HeroicKatora
I'm assuming you have an image buffer as in image, i.e. the buf is always stored row-major? This would significantly affect the indexing correction in the last loop.
In *i - rem it assumes that exactly rem pixels have been removed prior to the one to-be removed in any iteration.
For the aforementioned layout this holds true for row-by-row removal as each row has increasing indices and removes exactly one pixel.
But if you traverse the same layout column by column then the first column might remove a pixel at the end of the buffer so the next column now removes the wrong pixel.
I'd personally sort the indices before removing them and then remove them in strictly inreasing order.
This is an overhead of O(width ln width) (typically square root of your image) but you don't have to do any index correction during removal
Chehui Chou
@deadshot465_gitlab
Anyone knows what causes the failed to fill whole buffer error? I try to use image to decode a JPEG and read into a buffer, but it keeps showing me this error. I checked that the buffer length and total_bytes() is the same, and I have no problem getting dimension and color types from the stream, so I think there are no problems in decoding.
Still I don't know what causes the failed to fill whole buffer error.
Chehui Chou
@deadshot465_gitlab
I tried load_from_memory_with_format but to no avail
HeroicKatora
@HeroicKatora
Very likely the jpeg is actually corrupt. Do you know what produced it?
Many implementations have varying strategies for recovering but that incurs a lot of complexity.
And I'm afraid no-one had enough time to deal with that.
You can try djpeg or convert -verbose to see if any warnings or errors occur in other decoders
Chehui Chou
@deadshot465_gitlab
It's some base64-encoded texture files embedded inside glTF 3D models
Should I first try to decode it using base64 crate before constructing a JpegDecoder?
HeroicKatora
@HeroicKatora
Certainly image won't magically know that it happens to be base64 encoded.
I doubt that what you're passing to image is still base64-encoded as otherwise the decoder should complain much sooner.
Chehui Chou
@deadshot465_gitlab
Ok. I will try to decode it first and see if it fixes
Thanks
Martin Tomasi
@GyrosOfWar
hi there, i was wondering if there's an elegant way to convert a DynamicImage into an image buffer containing Rgb<f32> (instead of whatever the original representation was)? If there is one, I haven't been able to find it so far
HeroicKatora
@HeroicKatora
@GyrosOfWar I'm afraid there is no elegant way, that is a single statement or so for the conversion. In any case I would DynamicImage::into_rgb()and go from there. Then probably convert it into the vec container and map each pixel.
Martin Tomasi
@GyrosOfWar
alright, yeah, i was sort of expecting that
Raphael
@ruffson
Hi there, I do not want to pressure anyone but are there any concerns about image-rs/image-tiff#100 on the image-tiff repo implementing support for GeoTiff?
HeroicKatora
@HeroicKatora
I didn't get around to review it fully, no.
My first impression is that it's a breaking change and seems tacked on with free methods and little interaction.
Maybe just add the missing Tags first
Raphael
@ruffson
Ok. It is not a breaking change. Non-GeoTIff images can be opened fine. I'll wait for a review, no rush. Thank you!
HeroicKatora
@HeroicKatora
It's not? Hm, maybe I did misread the code on a first glance :)
Raphael
@ruffson
Haha maybe.
Raphael
@ruffson
Also I think I spotted a regression in image-tiff. Until a few weeks ago, I was able to load normal sized TIFF images with over 300MiB after I increased the buffer limits. Now, I get a InconsistentSizesEncountered error in expand_strip() because for whatever reason at some point a strip that was read is bigger than all the others. I don't really have time to do much debugging, I just observed that the new of LZWDecoder reads more bytes than a strip contains at some point, first few runs are fine. Has anyone any idea what could've changed that caused this?
HeroicKatora
@HeroicKatora
The lzw decoder changed, and the integration with it.
Assuming you're using 0.6 now
Raphael
@ruffson
I should be on master (using my PR'ed branch which I rebased when I created the PR)
Jorge Carrasco
@carrascomj_gitlab

Hello! I'm trying to use rayon with Pixels, but, although I see it implements ParallelBridge, I am unable to do it. Here's the sample code

use image::{GenericImage, Pixel};
#[cfg(feature = "rayon")]
use rayon::prelude::*;

pub type Image<P> = ImageBuffer<P, Vec<<P as Pixel>::Subpixel>>;

pub fn map_colors<I, P, Q, F>(image: &I, f: F) -> Image<Q>
where
    I: GenericImage<Pixel = P>,
    P: Pixel,
    Q: Pixel + 'static,
    F: Fn(P) -> Q,
{
    let (width, height) = image.dimensions();
    let mut out: ImageBuffer<Q, Vec<Q::Subpixel>> = ImageBuffer::new(width, height);

    #[cfg(feature = "rayon")]
    let iter = image.pixels().par_bridge();
    #[cfg(not(feature = "rayon"))]
    let iter = image.pixels();

    // UNSAFE JUSTIFICATION:
    // - no need to check bounds
    // - `out` is created from the dimensions of `image`
    iter.for_each(|(x, y, pixel)| unsafe { out.unsafe_put_pixel(x, y, f(pixel)) });

    out
}

And the error is

error[E0599]: no method named `par_bridge` found for struct `image::Pixels<'_, I>` in the current scope
   --> src/map.rs:103:43
    |
103 |     let iter = image.pixels().into_iter().par_bridge();
    |                                           ^^^^^^^^^^ method not found in `image::Pixels<'_, I>`
    |
   ::: /home/georg/.local/share/cargo/registry/src/github.com-1ecc6299db9ec823/image-0.23.11/./src/image.rs:558:1
    |
558 | pub struct Pixels<'a, I: ?Sized + 'a> {
    | -------------------------------------
    | |
    | doesn't satisfy `image::Pixels<'_, I>: rayon::iter::ParallelBridge`
    | doesn't satisfy `image::Pixels<'_, I>: std::marker::Send`
    |
    = note: the method `par_bridge` exists but the following trait bounds were not satisfied:
            `image::Pixels<'_, I>: std::marker::Send`
            which is required by `image::Pixels<'_, I>: rayon::iter::ParallelBridge`
            `(u32, u32, P): std::marker::Send`
            which is required by `image::Pixels<'_, I>: rayon::iter::ParallelBridge`
            `&image::Pixels<'_, I>: std::marker::Send`
            which is required by `&image::Pixels<'_, I>: rayon::iter::ParallelBridge`
            `&image::Pixels<'_, I>: std::iter::Iterator`
            which is required by `&image::Pixels<'_, I>: rayon::iter::ParallelBridge`
            `&mut image::Pixels<'_, I>: std::marker::Send`
            which is required by `&mut image::Pixels<'_, I>: rayon::iter::ParallelBridge`
            `(u32, u32, P): std::marker::Send`
            which is required by `&mut image::Pixels<'_, I>: rayon::iter::ParallelBridge`

Does anyone know if it's possible to get it to work?

Jorge Carrasco
@carrascomj_gitlab
Solved it by putting Send + Sync on everything (including <Q as Pixel>::Subpixel) and gathering it into an intermediate vector. The vector is then flushed into the ImageBuffer (single threaded). I am unsure if this is the best alternative, could the ImageBuffer be separated into chunks and then merged? Would that be better?
Jorge Carrasco
@carrascomj_gitlab
All this babbling is related to image-rs/imageproc#446, if someone is interested
HeroicKatora
@HeroicKatora
Sounds like you did everything right. Maybe some of the Sync is execessive but Send should be required on most structures.
As far as separating the buffer goes, it depends. You need not use Pixels if you iterate the container by hand.
Jorge Carrasco
@carrascomj_gitlab
Right, my first implementation relied on a custom struct that implements iterator "by hand" (using unsafes and such). I opted for Pixels in the end because it ended up being more readable and the par_bridge was already there, so I didn't have to reinvent the wheel. However, if the goal is ultimate performance, I would need to go that route I think
Jorge Carrasco
@carrascomj_gitlab
Just in case, I have this "by hand" implementation working here https://github.com/carrascomj/imageproc/blob/pixeliter/src/map.rs#L71 but I'm still unsure if it's worth it
arthmis
@arthmis
Hello I want to be able to reinterpret an image. I'm essentially doing some processing, transposing the image, do some more processing, and transposing the image back to its original dimensions. Would flat samples allow me to reinterpret the dimensions of the image?
HeroicKatora
@HeroicKatora
@arthmis That depends on the needs for reinterpretation.
With flat samples you get a standard slices of samples so you can do anything that's sound on such a slice.
You might want to use a dedicated library such as bytemuck for the actual reinterpretation but it should work in principle.
If it's only the actual slice you're interested in, you can use ImageBuffer as ops::Deref though, which will also get you the raw slice of samples.
arthmis
@arthmis
pub fn box_filter_mut(filter: MeanKernel, image: &mut RgbaImage) {
    let (width, height) = image.dimensions();

    // want the truncated value of this division, hence not using float
    let radius: i32 = filter.size() as i32 / 2;

    let mut new_image: RgbaImage =
        ImageBuffer::from_pixel(width, height, Rgba::from_channels(255, 255, 255, 255));

    // at this point both images have the same dimensions and I can do a 
    // horizontal pass over image and write the output onto new_image
    // blur pixels row wise
    horizontal_blur(radius, &image, &mut new_image);

    // Now I don't want to allocate a new buffer to transpose new_image 
    // so I want to reuse image's buffer to reinterpret as (height, width)
    // instead of (width, height)
    let mut image: RgbaImage = ImageBuffer::from_raw(height, width, *image.as_raw()).unwrap();
    transpose_rgba(&new_image, &mut image);
}
I'm actually having an issue where the borrow checker doesn't allow me to use image's buffer because it's behind a mutable reference. So at this point, if you have advice on how I can do this when taking image as a mutable reference. I worked around this issue already by taking ownership of image. I just wanted to know if I could provide two interfaces like, box_filter_mut and box_filter.
arthmis
@arthmis
Would it be possible to directly set the width and height of imagebuffer? Similar in vein to the unsafe function for Vec set_len
HeroicKatora
@HeroicKatora
You can replace the image with a zero-sized allocation temporarily which allows you to take the buffer and reuse it directly.
let temp = core::mem::replace(image, ImageBuffer::new(0, 0));
let temp: RgbaImage = ImageBuffer::from_raw(height, width, tmp.into_raw()).unwrap();
*image = temp;
This should have practically very little overhead (a few instructions at most).
The problem with resizing is of course the unnecessary unsafety and the unclear semantics. Should it move pixels? Obv. you don't want that for your case but that means that the pixel at (1, 1) doesn't stay there which would be arguably unexpected for changing dimensions. So I'd rather add it only with stronger motivation.
arthmis
@arthmis
Yeah I don't even want to have to add that the image crate. I was actually hoping I could implement a trait that does what I want, but it doesn't allow me access to the private fields of ImageBuffer. Oh well, I'll have to live with only box_filter and return the filtered image
HeroicKatora
@HeroicKatora
How do you need to access the private fields here?
Adding a resizing method and the solution I proposed are orthogonal concerns
arthmis
@arthmis
I'm not necessarily resizing, so to speak. I want to have the image Buffer to swap the width and height values. That's the simplest explanation I can give. Because when I transpose an image into another buffer, the other buffer's height would be the old image's width, and the other buffer's width would be the old image's height. I essentially want a quick and dirty way to tell an image that it's height is now its width and its width is now its height. Nothing changes in regards to the buffer. I was hoping to write a trait that simply swaps the height and width of the ImageBuffer.
arthmis
@arthmis
    /// swaps width and height for when transposing image
    pub fn swap_dimensions(&mut self) {
        let (width, height) = self.dimensions();
        self.width = height;
        self.height = width;
    }

I implement this function for ImageBuffer, in the buffer file, which will swap the dimensions, then in my function:

pub fn box_filter_mut(filter: MeanKernel, mut image: &mut RgbaImage) {
    use crate::matrix_ops::transpose_rgba;
    use image::Pixel;

    let (width, height) = image.dimensions();

    // want the truncated value of this division, hence not using float
    let radius: i32 = filter.size() as i32 / 2;

    let mut new_image: RgbaImage =
        ImageBuffer::from_pixel(width, height, Rgba::from_channels(255, 255, 255, 255));

    // blur pixels row wise
    horizontal_blur(radius, &image, &mut new_image);
    // swap image dimensions to allow transposing new_image into image
    image.swap_dimensions();
    transpose_rgba(&new_image, &mut image);

    // swap new_image dimensions to allow blurring image and writing
    // to new_image
    new_image.swap_dimensions();
    // blur pixels column wise
    horizontal_blur(radius, &image, &mut new_image);

    // swap dimensions again to transpose new image into image for final
    // output
    image.swap_dimensions();
    transpose_rgba(&new_image, &mut image);
}

I'm not necessarily asking for this to be implemented, but I want to clarify what I'm trying to do. The reason why i'm trying to reuse buffers is because I'm compiling this to Webassembly and I'm trying to cut down on allocations. I'm using WeeAlloc as the allocator.

arthmis
@arthmis
    /// sets dimensions of image
    pub fn set_dimensions(&mut self, width: u32, height: u32) -> Result<(), String> {
        if (width * height * <P as Pixel>::CHANNEL_COUNT as u32) as usize > self.data.len() {
            return Err("width and height too large for container".to_string());
        }
        self.width = width;
        self.height = height;
        Ok(())
    }
something like this would be nice too. I don't know how useful this would be to the overall project though.
arthmis
@arthmis
image-rs/image#1273
I feel like the ConvertInplace interface proposed in this issue could even be used to take an image and convert into a transposed image, if that makes sense.
HeroicKatora
@HeroicKatora

It seems like an separate isse. ConvertInplace was, to my understanding of the draft, meant to preserve pixel relation and also work pixel-by-pixel. This enables some parallelism where a true transpose of the image matrix requires a different kind of parallelization strategy. Your implemtation of swap_dimensions of course only perserves semantics together with transpose_rgba. I would recommend going to flat::Flat for this purpose, it gives you full control over all aspects.

The reason why i'm trying to reuse buffers is because I'm compiling this to Webassembly and I'm trying to cut down on allocations. I'm using WeeAlloc as the allocator.

Please look closely at the code here, and you will find that it does not allocate any memory. Yet, it gives you an intermediate owned Vec of the pixel data. You seem to be under the impression that &mut RgbaImage makes this impossible. That is not the case. The Rust type system only requires you to give a temporary replacement, which can be constructed without an allocation by substituting a zero-sized image.

With here I mean this piece from earlier:
let temp = core::mem::replace(image, ImageBuffer::new(0, 0));
let temp: RgbaImage = ImageBuffer::from_raw(height, width, tmp.into_raw()).unwrap();
*image = temp;