dmathieu - Create your own carrierwave processors

Create your own carrierwave processors

Tuesday, 24 August 2010 in Development Articles by Damien Mathieu Creative Commons License

If you need to manage images in a rails application, I can only recommand CarrierWave.

That library will allow you to easily manage files upload. But also post processing on images, such as changing their sizes.

carrierwave I use CarrierWave on this blog even though you don’t see it yet. Every category and every article has an image. That image has several different versions which have different sizes.

For one of those versions, I which to have rounded corners. You can see the result in the image at the left.

Let’s see in details how I got that.

 

## Post processing CarrierWave

Let’s imagine the following uploader :

1
2
3
4
5
6
class CategoryUploader < CarrierWave::Uploader::Base
    include CarrierWave::RMagick
    version :thumb do
        process :resize_to_fill => [248, 163]
    end
end

We have a category uploader which will create a “thumb” version of the image. That image will have a size of 248 * 163 pixels.

You can find the processor “resize_to_fill” which will change the image’s size at lib/carrierwave/processing/rmagick.rb.

In order to create our rounded corners, we’ll create our own processor. I’ve placed it at lib/dmathieu/carrier_wave/round.rb.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
unless defined? Magick
    begin
        require 'rmagick'
    rescue LoadError
        require 'RMagick'
    rescue LoadError
        puts "WARNING: Failed to require rmagick, image processing may fail!"
    end
end

module Dmathieu
    module CarrierWave
        module Round

            module ClassMethods
                def rounded_corner(radius = 10)
                    process :rounded_corner => [radius]
                end
            end

            ##
            # Makes the image's corners round
            #
            #
            # === Parameters
            #
            # [radius (#to_s)] the corner radius
            #
            # === Yields
            #
            # [Magick::Image] additional manipulations to perform
            #
            def rounded_corner(radius = 10)
                #
                # See above for this method's code
                #
            end
        end
    end

Then, in your uploader, you can include that newly created processor.

1
2
3
4
5
6
7
8
9
class CategoryUploader < CarrierWave::Uploader::Base
    include CarrierWave::RMagick
    include Dmathieu::CarrierWave::Round

    version :thumb do
        process :resize_to_fill => [248, 163]
        process :rounded_corner
    end
end

You can see that, in our module, we create a method rounded_corner which will do the job of adding the rounded corners in our image. That action will be automatically called by CarrierWave when generating that version of the image.

Rounding up the corners

In order to manipulate our image, we use rmagick.

We’re gonna do the following :

  • Create a new image of the same size than the previous which will contain a rectangle with rounded corners.
  • Put that image above the previous one.

Quite easy no ? ;-)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def rounded_corner(radius = 10)
    manipulate! do |img|
        masq = ::Magick::Image.new(img.columns, img.rows).matte_floodfill(1, 1)
        ::Magick::Draw.new.
            fill('transparent').
            stroke('black').
            stroke_width(1).
            roundrectangle(0, 0, img.columns - 1, img.rows - 1, radius, radius).
            draw(masq)

        img.composite!(masq, 0, 0, Magick::LightenCompositeOp)
        img = yield(img) if block_given?
        img
    end
end

We’ve just defined the rounded_corner method, which is located in the module provided earlier.

The manipulate! method is located in the rmagick.rb processor and gives us the image, allowing us to update it and to transmit it back to the next processor.

1
masq = ::Magick::Image.new(img.columns, img.rows).matte_floodfill(1, 1)

We create here a new image of the same size than the previous. But which contains nothing.

1
2
3
4
5
6
::Magick::Draw.new.
        fill('transparent').
        stroke('black').
        stroke_width(1).
        roundrectangle(0, 0, img.columns - 1, img.rows - 1, radius, radius).
        draw(masq)

We draw in the image we’ve just created.

1
fill('transparent').

The rectangle we’ll create will have a transparent background.

1
2
stroke('black').
stroke_width(1).

That rectangle will have a black border of 1 pixel.

1
2
roundrectangle(0, 0, img.columns - 1, img.rows - 1, radius, radius).
draw(masq)

We create here the rectangle and add it to the previously create image.

1
img.composite!(masq, 0, 0, Magick::LightenCompositeOp)

Finally we add that rectangle image on top of the one which already exists, giving the effet we want.

1
2
img = yield(img) if block_given?
img

CarrierWave’s processors are nested. Each one of them is executed one after the other. With yield, we give the image to the next processor. It’ll then just have to execute it’s own modifications.

Finally we return the image which will be saved.

Conclusion

As you can see, CarrierWave allows us to create generic processors so you can get your images exactly as you want them.

I’m actually quite surprise to see there’s not a lot of open source processors running wild.