Search code examples
linuxperlimagemagickperl-module

How do I increase the RGB value of a specific pixel with Image::Magic in Perl?


I want get 1 pixel (x=3, y=3) and change its RGB values (R from 100 to 101, G from 99 to 100, B from 193 to 194).

enter image description here

use strict;
use Image::Magick;
my $p = new Image::Magick;
   $p->Read( 'myfile.jpg' );

   my $pix = $p->GetPixel(
            width     => 1,
            height    => 1,
            x         => 3,
            y         => 3,
            map       => 'RGB',
            normalize => 0
        );

   # in $pix RGB value now? 

How do I add 1 for all RGB components?

Can I split decimal RGB to 3 values (r,g,b) and increment separately, and then merge three R,G,B values to one RGB? :) How do I do that?

   $pix = .... something code here...

   # make changes
   $p->SetPixel(
            x       => 3,
            y       => 3,
            channel => 'RGB',
            color   => [ $pix ]
        );
   $p->Write ('my_new_file.jpg');

Solution

  • This was a bit tricky to figure out, but here we go. I'll show you what I did to get to the result, not just how it works.

    I'm using a small image that has your starting color (100, 99, 193).

    starting image and color

    At the top of my program I will always have this code.

    use strict;
    use warnings;
    use Data::Printer;
    use Image::Magick;
    my $p = new Image::Magick;
    $p->Read('33141038.jpg');
    
    my @pixel = $p->GetPixel(
        x         => 1,
        y         => 1,
        map       => 'RGB',
        normalize => 1,
    );
    

    I checked the documentation at imagemagick.org.. It is linked in Image::Magick on CPAN. There I searched GetPixel. That yields two helpful things. One is the explanation, the other one an example that shows that an array @pixel is returned, and not a scalar like you tried.

    Here we reduce the intensity of the red component at (1,1) by half:

    @pixels = $image->GetPixel(x=>1,y=>1);
    

    Ok. Let's use that. I've already got the @pixel in my code above. Note that I also turned on the normalize option. You can leave that out as it's on by default.

    p @pixel;
    
    # [
    #     [0] 0.392156862745098,
    #     [1] 0.388235294117647,
    #     [2] 0.756862745098039
    # ]
    

    So those are floats. After some googling I found this answer, which deals with something similar. It looks like a fraction of 255. Let's multiply. We can modify things in @pixel by assigning to $_ in the postfix foreach. That's neat.

    $_ *= 255 foreach @pixel;
    p @pixel;
    
    # [
    #     [0] 100,
    #     [1] 99,
    #     [2] 193
    # ]
    

    That's what we wanted. Easy enough. Let's add one each.

    $_ = ( $_ * 255 ) + 1 foreach @pixel;
    p @pixel;
    
    # [
    #     [0] 101,
    #     [1] 100,
    #     [2] 194
    # ]
    

    Still good. But how do we get that back in? The docs have something to say about SetPixel in the Manipulate section.

    color=>array of float values
    [...]
    set a single pixel. By default normalized pixel values are expected.

    So apparently we need to go back to the float. No problem.

    $_ = ( ( $_ * 255 ) + 1 ) / 255 foreach  @pixel;
    p @pixel;
    
    # [
    #     [0] 0.396078431372549,
    #     [1] 0.392156862745098,
    #     [2] 0.76078431372549
    # ]
    

    Nice. We can of course also make the math a bit shorter. The result is the same.

    $_ = $_ + 1 / 255 foreach @pixel;
    

    Now let's write it back to the image.

    $p->SetPixel(
        x => 1,
        y => 1,
        color => \@pixel, # need the array ref here
    );
    
    $p->Write('my_new_file.jpg');
    

    In the screenshot, I changed it to add 20 instead of 1 so it's more visible.

    New image with +20 including freehand circles

    After cleaning up the code looks like this.

    use strict;
    use warnings;
    use Data::Printer;
    use Image::Magick;
    
    my $p = new Image::Magick;
    $p->Read('33141038.jpg');
    
    my @pixel = $p->GetPixel(
        x => 1,
        y => 1,
    );
    
    # increase RGB by 1 each
    $_ = $_ + 1 / 255 foerach @pixel;
    
    $p->SetPixel(
        x     => 1,
        y     => 1,
        color => \@pixel,
    );
    
    $p->Write('my_new_file.jpg');
    

    I've removed the map and channel arguments from GetPixel and SetPixel as RGB is the default. Same for the normalize.