Cropping Images using DHTML (Prototype) and symfony

by Dave Dash 16Sep06

Note: Like many of my tutorials, you don't need [symfony], just [PHP]. However, I develop in [symfony] and take advantage of the MVC-support that it offers.

Years ago when I was working on a photo gallery for davedash.com I got the art of making tumbnails down fairly well. It was automated and didn't allow for specifying how the thumbnail should be made. With dozens of photos (which was a lot back then), when would I find that kind of time.

Flashback to today, for my company... we want users with avatars... but nothing too large. Maybe a nice 80x80 picture. Well the coolest UI I've seen was Apple's Address Book which let you use this slider mechanism to crop a fixed sized image from a larger image.

Here's a demo.

Overview

The front-end GUI is based on code from [digg] which is based on the look and feel (as near as I can tell) from Apple.

The GUI provides a clever visual way of telling the server how to chop the image. The gist is this, sliding the image around and zooming in and out change a few form values that get passed to another script which uses this data to produce the image.

Frontend: What would you like to crop?

In this tutorial, we're going to be cropping an 80x80 avatar from an uploaded image. The front-end requires the correct mix of Javascript, CSS, HTML and images. The Javascript sets up the initial placements of the image and the controls. The CSS presents some necessary styling. The images makeup some of the controls. The HTML glues everything together.

HTML

Let's work on our HTML first. Since I used [symfony], I created a crop action for a userpics module. So in our cropSuccess.php template:

<div id="ava">
    <?php echo form_tag("userpics/crop") ?>
        <div id="ava_img">
            <div id="ava_overlay"></div>
            <div id="ava_drager"></div>
            <img src="<?php echo $image ?>" id="avatar" />
        </div>
        <div id="ava_slider"><div id="ava_handle"></div></div>
        <input type="hidden" id="ava_width" name="width" value="80" />
        <input type="hidden" id="ava_x" name="x" value="100" />
        <input type="hidden" id="ava_y" name="y" value="100" />
        <input type="hidden" id="ava_image" name="file" value="<?php echo $image ?>" />
        </div>
        <input type="submit" name="submit" id="ava_submit" value="Crop" style="width: auto; font-size: 105%; font-weight: bold; margin: 1em 0;" />
    </form>
</div>

Right now a lot of this doesn't quite make sense. If you attempt to render it, you will just see only the image. As we add the corresponding CSS and images it will make some more sense.

CSS and corresponding images

We'll go through each style individually and explain what purpose it serves in terms of the GUI.

#ava is our container.

#ava {
    border: 1px solid gray;
     width: 200px;
}

#ava_img is the area that contains our image. Our window for editing this image 200x200 pixels. If we drag out image out of bounds we just want the overflowing image to be clipped. We also want our position to be relative so any child elements can be positioned absolutely with respect to #ava_img.

#ava_img {
    width: 200px;
    height: 200px;
    overflow: hidden;
    position: relative;
}
overlay

#ava_overlay is a window we use to see what exactly will be our avatar. If it's in the small 80x80 window in the center of the image, then it's part of the avatar. If it's in the fuzzy region, then it's getting cropped out. This overlay of course needs to be positioned absolutely.

#ava_overlay {
    width: 200px;
    height: 200px;
    position: absolute;
    top: 0px;
    left: 0px;
    background: url('/images/overlay.png');
    z-index: 50;
}

#ava_drager is probably the least intuitive element (Heck, I'm not even sure if I've even got it right). In our demo you're not actually dragging the image, because you can drag anywhere within the #ava_img container and move the image around. You're using dragging an invisible handle. It's a 400x400 pixel square that can be dragged all over the container and thusly move the image as needed.

#ava_drager {
    width: 400px;
    height: 400px;
    position: absolute;
    z-index: 100;
    color: #fff;
    cursor: move;
}

#avatar is our image, and since it will be moving all around the window, it requires absolute positioning.

#avatar {
    position: absolute;
}
overlay

overlay

#ava_slider and #ava_handle are our slider components. They should be self-explanatory.

#ava_slider {
    width: 200px;
    height: 27px;
    background: #eee;
    position: relative;
    border-top: 1px solid gray; 
    background: url('/images/slider_back.png');
}
#ava_handle {
    width: 19px;
    height: 20px;
    background: blue;
    position: absolute;
    background: url('/images/handle.png');
}
Internet Explorer

PNG do not work so well in Internet Explorer, but there is a small trick, adding these components into a style sheet that only IE can read will make things work:

#ava_overlay {
  background: none;
  filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='/images/ui/cropper/overlay.png', sizingMethod='crop');
}

#ava_handle {
  background: none;
  filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='/images/ui/cropper/handle.png', sizingMethod='crop');
}

The Javascript

The Javascript is actually not as complicated as you'd expect thanks to the wonder of [prototype]. This framework provides so much so easily. You'll need to include prototype.js and dom-drag.js.

So let's take a look.

<script type="text/javascript" language="javascript" charset="utf-8">
// <![CDATA[
function setupAva() {
    if ($("avatar")) {
        var handle = $("ava_handle");
        var avatar = $("avatar");
        var drager = $("ava_drager");
        var slider = $("ava_slider");
        var ava_width = $("ava_width");
        var ava_x = $("ava_x");
        var ava_y = $("ava_y");
        // four numbers are minx, maxx, miny, maxy
        Drag.init(handle, null, 0, 134, 0, 0);
        Drag.init(drager, avatar, -100, 350, -100, 350);
        var start_w = avatar.width;
        var start_h = avatar.height;
        var ratio = (start_h / start_w);
        var new_h;
        var new_w;
        if (ratio > 1) {
            new_w = 80;
            new_h = (80*start_h)/start_w;
        } else {
            new_h = 80;
            new_w = (80*start_w)/start_h;
        }
        // these need to be set after we init
        avatar.style.top = '100px';
        avatar.style.left = '100px';
        avatar.style.width = new_w + 'px';
        avatar.style.height = new_h + 'px';
        avatar.style.margin = '-' + (new_h / 2) + 'px 0 0 -' + (new_w / 2) + 'px';
        handle.style.margin = '3px 0 0 20px';
        avatar.onDrag = function(x, y) {
            ava_x.value = x;
            ava_y.value = y;
        }
        handle.onDrag = function(x, y) {
            var n_width = (new_w + (x * 2));
            var n_height = (new_h + ((x * 2) * ratio));         
            avatar.style.width = n_width + 'px';
            avatar.style.height = n_height+ 'px';
            ava_width.value = n_width;  
            avatar.style.margin = '-' + (n_height / 2) + 'px 0 0 -' + (n_width / 2) + 'px';
        }
    }
}
Event.observe(window,'load',setupAva, false);
// ]]>
</script>

If this isn't exactly crystal clear, I can explain. If you're new to [prototype], $() is the same as doucment.getElementByID() (at least for our purposes).

We need to initialize two draggable elements, one is our slider for zooming and the other is our avatar itself. We initialize the draggers using Drag.init(). We specify what to drag, if another element should be used as a handle and then the range of motion in xy coordinates. In the second call we use that #dragger to move around the image in this manner.

Drag.init(handle, null, 0, 134, 0, 0);
Drag.init(drager, avatar, -100, 350, -100, 350);

We want to initialize the the size and placement of the avatar. We do that using maths. First we want it in our 80x80 pixel box. So it should be roughly 80x80. I've set the math up so that the smallest side is 80 pixels (there's reasons for doing this the other way around).

    if (ratio > 1) {
        new_w = 80;
        new_h = (80*start_h)/start_w;
    } else {
        new_h = 80;
        new_w = (80*start_w)/start_h;
    }

We then place the avatar element. We initialize it to be in the center of the screen (top: 100px;left:100px) and then nudge the image using margins.

    avatar.style.top = '100px';
    avatar.style.left = '100px';
    avatar.style.width = new_w + 'px';
    avatar.style.height = new_h + 'px';
    avatar.style.margin = '-' + (new_h / 2) + 'px 0 0 -' + (new_w / 2) + 'px';

We also use margins to place the handle.

    handle.style.margin = '3px 0 0 20px';

#ava_x and #ava_y tell us where the center of the avatar is. So when the avatar is moved we need to set these again:

    avatar.onDrag = function(x, y) {
        ava_x.value = x;
        ava_y.value = y;
    }

That was easy. Slighly more complicated is the zoomer function. We are basically adjusting the width and the height proportionately based on roughly where the slider is. Note that we're still using that ratio variable that we calculated earlier. We basically take the new x-coordinate of the handle and allow our image to get just slightly larger than the #ava_image container.

    handle.onDrag = function(x, y) {
        var n_width = (new_w + (x * 2));
        var n_height = (new_h + ((x * 2) * ratio));         
        avatar.style.width = n_width + 'px';
        avatar.style.height = n_height+ 'px';
        ava_width.value = n_width;  
        avatar.style.margin = '-' + (n_height / 2) + 'px 0 0 -' + (n_width / 2) + 'px';
    }

We want to load initialize the slider right away when the page loads: Event.observe(window,'load',setupAva, false);

Not terribly hard or complicated. Once these elements are all in place you have a working functioning slider. It returns the x and y coordinates of the center of the image with respect to our 200x200 pixel #ava_image. It also tells us the new width of our image. We feed this information into a new script and out should pop a new image which matches exactly what we see in our GUI.

Pages: 1 2


Where am I?

This is a single entry in the weblog.

"Cropping Images using DHTML (Prototype) and symfony" is filed under css, programming, reviewsby.us, spindrop, symfony and usability. It was published in September 2006.

September 2006
M T W T F S S
« Aug   Oct »
 123
45678910
11121314151617
18192021222324
252627282930  

Tags

need more help

If you found our tutorials and articles to be useful, but are still looking for more hands on help, consider hiring us. Find out more about how Spindrop can help you.

 

26 Responses to “Cropping Images using DHTML (Prototype) and symfony”


  1. 1 kevin Posted November 10th, 2006 - 6:57 pm

    People who dont have exif turned on in PHP can use this:

                $o_imagetype = getImageSize($o_filename); // is this gif/jpeg/png
    
            // appropriately create the GD image
            switch ($o_imagetype['mime']) {
                case "image/gif": // gif
                    $o_im = imagecreatefromgif($o_filename);
                    break;
                case 'image/jpeg': // jpeg
                    $o_im = imagecreatefromjpeg($o_filename);   
                    break;
                case 'image/png': // png
                    $o_im = imagecreatefrompng($o_filename);
                    break;
            }
    
  2. 2 Helper Guy Posted March 9th, 2007 - 11:21 am

    Just a note,

    there is a small bug

    as things currently are, you must move the slider (even if you are just moving it to the zero position), otherwise the image will not be cropped at the correct correctly

    to fix this, add the following line into the SetupAva function

    ava_width.value = new_w;

    apart from that.. great code, very helpful! EXACTLY what i was looking for :)

  3. 3 Dave Dash Posted March 9th, 2007 - 11:44 am

    Helper Guy,

    Thanks for the help, I edited your original post to preserve underscores… this site uses markdown which likes to turn underscores into italics… I’ll need to make the editing form more clear.

    -d

  4. 4 Helper Guy Posted March 9th, 2007 - 1:36 pm

    Transparency doesn’t work properly, even with the default image you are using

    any idea how to fix that?

  5. 5 Dave Dash Posted March 9th, 2007 - 1:43 pm

    Are you using Firefox or IE?

    If you’re using Firefox, it should work.

    If you’re using IE then there is an issue with IE’s lack of support for alpha transparncies.

    Luckily there is a PNG transparency fix that’ll use some IE specific magic to make it work. Hope that helps.

  6. 6 Helper Guy Posted March 9th, 2007 - 2:22 pm

    I meant the PHP GD image stuff

    it creates an image with a black background, rather than transparent

  7. 7 Dave Dash Posted March 9th, 2007 - 2:29 pm

    Unfortunately my knowledge of GD is limited, when I’ve used this cropper in environments running PHP5 with GD2 my transparencies worked.

    Looking back at the code:

    $im = imagecreatetruecolor(200, 200) or die("Cannot Initialize new GD image stream");
    imagecolortransparent ( $im, 127 ); // set the transparency color to 127
    imagefilledrectangle( $im, 0, 0, 200, 200, 127 ); // fill the canvas with a transparent rectangle
    

    We already initialize the canvas with a 200×200 rectangle that has the same color as the transparent pixel. So the code does what I expect it to on my end, beyond that I’m not sure what to say.

  8. 8 Helper Guy Posted March 9th, 2007 - 2:36 pm

    alright thanks for your help, it behaves differently for me for some reason.. i’m looking into the imagecopypallete function

    if i get it working i’ll post the solution here

  9. 9 vince Posted March 26th, 2007 - 11:17 am

    Very Nice Job, I’ve been looking for something like this for a while. Though I can’t seem to get it to work fully, would anyone have a working version they could zip to me. It would be greatly appreciated.

    vjz@hotmail.com

  10. 10 Dave Dash Posted March 26th, 2007 - 11:40 am

    Hey Vince,

    Where are you getting stuck?

    Unfortunately I don’t have anything zippable (code that’s used in closed-source production projects) at the moment, but I’m sure it can be debugged.

  11. 11 Fred Posted April 30th, 2007 - 10:02 pm

    Hi,

    this is the very best at least the more elegant cropping solution I have ever seen on web. Far. Bravo !

    Unfortunatly my poor english and programming knowledge don’t give me the opportunity to make it working fine for my users.

    The link to dom-drag.js is dead but I could get it using Coogle search (dom-drag.js download).

    The only way I found to get mac design images was to copy them from demo. I think they are only two images (handle.png and slider_back.png), right ?

    If you could put on line a full “kit” to download with the differents files (php, JS, CSS) as someone already asked for I believe, it would be great.

    Best regards

  12. 12 Developerz Posted June 14th, 2007 - 1:10 am

    Hi Dave,

    Thanks so much for sharing this example. I just got done implementing it in C#. Let’s just say I’m pretty stoked for figure it all out.

    I did find one bug in the javascript “function setupAva”

    The problem occurs when a user uploads a landscape image (width is greater than height), and does not use the handle.onDrag . If they don’t the ava_width returns 80, which will throw off the proportion.

    So what I did to fix this bug was set the was to set: avawidth.value = neww;

    Here’s a portion of the function setupAva that shows where I’ve added “avawidth.value = neww;”

    var ratio = (starth / startw); var newh; var neww; if (ratio > 1) { neww = 80; newh = (80starth)/startw; } else { newh = 80; neww = (80startw)/starth; }

            // set the ava_width.value here in case the user does not use the handle.onDrag function, if you don't landscape images get cropped with a small height
            ava_width.value = new_w;
    
            // these need to be set after we init
            avatarImg.style.top = '100px';
            avatarImg.style.left = '100px';
            avatarImg.style.width = new_w   'px';
            avatarImg.style.height = new_h   'px';
    

    Anyway, thanks again and hopefully this helps someone.

  13. 13 dungpt Posted July 2nd, 2007 - 7:12 am

    omg, i’m crazing with it :(

    here’s my problem

    http://www.sitepoint.com/forums/showthread.php?t=475618

    i can’t create image ok with 48×48 pixels

  14. 14 Ignacio Posted August 7th, 2007 - 11:30 pm

    I passed 2 days trying to change this… I don’t want 80×80, I want 160×160, nothing happen… :(

  15. 15 Ignacio Posted August 8th, 2007 - 12:04 am

    I did it! imagecopyresampled ( $final, $im, 0, 0, 17, 17, 160, 160, 160, 160 ); 17 = Margin of the overlay, sure!

  16. 16 NP Posted August 20th, 2007 - 10:03 am

    Could you please make a archive with the full functional code? I’m really having a hard time setting this up.

  17. 17 léonie Posted August 30th, 2007 - 5:17 am

    Can you please upload the corresponding php file(s)? Or am I missing something? I really digg your script. There are a couple others around, but I like the idea of the interface.

    Just for others to be up to date of what is around and works in most browsers:

    http://199.199.212.26/demo/ http://mondaybynoon.com/examples/imagecropresize/

  18. 18 Aleksey Posted May 28th, 2008 - 6:30 pm

    Hi, thank you very much autor for this work. Very introsting. But this havn’t been working in IE6. :-( . Problem in transparency for “”. Maybe somebody solved this proble.

  19. 19 Aleksey Posted May 28th, 2008 - 6:31 pm

    Problem in transparency for “ava_overlay”

  20. 20 Steve Conley Posted June 14th, 2009 - 5:34 pm

    Because there’s some demand out there for a packaged version of this, I’ve made one! My package wraps this in a class that isn’t symfony dependent.

    http://www.tanabi.com/projects/cropper

Who's linking?

  1. 1 - aNieto2K Pingback on Sep 16th, 2006
    "[...] Hazle Crop a tus imagenes con Javascript [Demo] [...] "
  2. 2 PHPDeveloper.org Trackback on Sep 16th, 2006
    "SpinDrop.us: Cropping Images using DHTML (Prototype) and symfony... ... "
  3. 3 Ruido blanco Trackback on Sep 17th, 2006
    "De como ser vago te evita escribir dos páginas de tutorial... Desde que volví de vacaciones he estado a punto de ... "
  4. 4 香港 PHP 用家社區 | Hong Kong PHP Users Group Trackback on Sep 20th, 2006
    "教學文件:利用 DHTML 製作一個裁剪圖像的介面... 網上有很多圖像處理、相簿管理的網上應用系統,容許我們把大圖像裁剪為較小的圖像,但是用戶必須輸入裁剪框的座標和大小,對一般人來說顯然十分困難,Dave Dash 在 Spindrop 發表了一篇教... "
  5. 5 Kwerty Blog » Avatar cropper Pingback on Mar 9th, 2007
    "[...] The cropper is identical to digg.coms cropper, I found the source on some random website. [...] "
  6. 6 Simbiotica :: Cropping Images using DHTML (Prototype) and symfony at Spindrop Pingback on Oct 24th, 2007
    "[...] Read more :: http://spindrop.us/2006/09/16/cropping-images-using-dhtml-prototype-and-symfony/ [...] "
Comments are currently closed.

Further Help

If you require more hands on assistance, we do offer affordable hands on support.