Search code examples
rubyimagemagickrmagick

Port ImageMagick commands to RMagick


I'm doing image processing with ImageMagick commands and I would like to port them to RMagick. The goal of this task is to take a picture and to pixelate given areas (one or more) for privacy purpose.

Here is my bash script (script.sh), which works very well using the convert command:

convert invoice.png -scale 10% -scale 1000% pixelated.png
convert invoice.png -gamma 0 -fill white -draw "rectangle 35, 110, 215, 250" mask.png
convert invoice.png pixelated.png mask.png -composite result.png

Now I want to create the Ruby version of this script using ImageMagick. Here is what I have now:

require 'rmagick'

# pixelate_areas('invoice.png', [ [ x1, y1, width, height ] ])
def pixelate_areas(image_path, areas)
  image     = Magick::Image::read(image_path).first
  pixelated = image.scale(0.1).scale(10)
  mask      = Magick::Image.new(image.columns, image.rows) { self.background_color = '#000' }

  areas.each do |coordinates|
    area = Magick::Image.new(coordinates[2], coordinates[3]) { self.background_color = '#fff' }
    mask.composite!(area, coordinates[0], coordinates[1], Magick::OverCompositeOp)
  end

  # Now, how can I merge my 3 images?
  # I need to extract the part of pixelated that overlap with the white part of the mask (everything else must be transparent).
  # Then I have to superpose the resulting image to the original (it's the easy part).
end

As you can see, I'm stuck at the last step. What operation do I need to do with my original picture, my pixelated picture and my mask in order to have this result?

How can I build an image with just the overlapping of the white part of the mask and the pixelated picture. Just like this one but with transparency instead of black?


Solution

  • First, why are you porting commands to RMagick when you've got something that already works? The bash version is short and comprehensible. If this is just part of a larger script that you're porting, don't be afraid of system().

    That said, here's a different tack that I believe accomplishes what you're trying to do in a more straightforward manner.

    require 'RMagick'
    
    def pixelate_area(image_path, x1, y1, x2, y2)
      image          = Magick::Image::read(image_path).first
      sensitive_area = image.crop(x1, y1, x2 - x1, y2 - y1).scale(0.1).scale(10)
    
      image.composite!(sensitive_area, x1, y1, Magick::AtopCompositeOp)
      image.write('result.png') { self.depth = image.depth }
    end
    

    This then seems to do the same as your original bash commands:

    pixelate_area('invoice.png', 35, 110, 215, 250)
    

    Since it looks like you want to handle multiple areas to blur out, here's a version that takes an array of areas (each as [x1, y1, x2, y2]):

    def pixelate_areas(image_path, areas)
      image = Magick::Image::read(image_path).first
    
      areas.each do |area|
        x1, y1, x2, y2 = area
    
        sensitive_area = image.crop(x1, y1, x2 - x1, y2 - y1).scale(0.1).scale(10)
        image.composite!(sensitive_area, x1, y1, Magick::AtopCompositeOp)
      end
    
      image.write('result.png') { self.depth = image.depth }
    end