Click here to Skip to main content
Click here to Skip to main content

Dealing with Images in Content Management Systems, Part 2

, 17 Feb 2009
Rate this:
Please Sign up or sign in to vote.
Browser-based resizing and optimisation of images

Introduction

This is part 2 of my article "Dealing with images in content management systems". Please read Part 1 of the article before you start this part:

Generating the Canvas

Regardless of whether the user uploaded a new raw image or picked a thumbnail, we have now supplied the control with information about the image's width and height, and what the server ID is (either newly generated or inferred from the thumbnail).

If we were in uploadButton_Click, the new thumbnail is added in to the list of thumbnail names in the session:

SessionImages.Add(thumbnailFileName);

Then, both uploadButton_Click and btnThumb_Command call UseRaw(). This method first checks to see if the raw image has the same dimensions as the control's ImageWidth and ImageHeight, and if so, assumes the user has uploaded a web ready image. In this case, the IImageProvider's SaveRawImageAsWebImage is called, which effectively copies the raw image to the web image directory, only doing any graphics redrawing if the uploaded format differs from the specified format. The mode of the control is then set to ControlMode.Changed, and its work is done.

Usually, the image won't be the right size, so the control calls CanvasFromRaw() to generate the user's canvas on which they can make a selection:

private void CanvasFromRaw()
{
    // we're going to need to do some further work with this image
    // so let's work out a few things about it
    aspectRatio = (float)rawWidth / (float)rawHeight;

    // the image is not the right size so we need to make a canvas
    string[] clientDimensions = hiddenField.Value.Split(new char[] { ',' });
    // we'll allow the canvas to be up to 80% 
    // of the user's current browser window size:
    int clientX = Convert.ToInt32(clientDimensions[0]) * 4 / 5;
    int clientY = Convert.ToInt32(clientDimensions[1]) * 4 / 5;

    float clientRatio = (float)clientX / (float)clientY;

    // which is the dimension that should constrain the canvas size?
    if (clientRatio > aspectRatio)
    {
        // y axis constrains the canvas size
        canvasHeight = clientY;
        canvasWidth = rawWidth * clientY / rawHeight;
    }
    else
    {
        canvasWidth = clientX;
        canvasHeight = rawHeight * clientX / rawWidth;
    }

    canvasImageName = ImageProvider.CreateCanvas(
        webImageFormat, webImageQuality,
        canvasWidth, canvasHeight);
    canvas.Src = getImageSource(canvasImageName, "canvas");
    canvas.Width = canvasWidth;
    canvas.Height = canvasHeight;
    controlMode = ControlMode.Canvas;
}

This is where we make use of the viewport dimensions we captured earlier. We work out the aspect ratio of the uploaded image.

aspectRatio = (float)rawWidth / (float)rawHeight;

and that of the browser viewport:

float clientRatio = (float)clientX / (float)clientY;

Comparing them allows us to determine which dimension should constrain the canvas so that it fits in the user's browser. We also multiply the client dimensions by 4/5 so the canvas has some space around it and doesn't go right to the edge of the browser, which would be ugly and not allow any room for additional UI.

The call to IImageProvider.CreateCanvas is more straightforward than the earlier thumbnail example:

public string CreateCanvas(WebImageFormat format, 
       WebImageQuality quality, int canvasWidth, 
       int canvasHeight)
{
    purge(WebImageMaker.CanvasImageDirName);
    string canvasFileName = null;
    using (Image rawImg = Image.FromFile(getRawFilePath()))
    {
        using (Bitmap canvas = new Bitmap(
            canvasWidth, canvasHeight, PixelFormat.Format24bppRgb))
        {
            using (Graphics g = Graphics.FromImage(canvas))
            {
                setGraphicsQuality(g, quality);
                g.DrawImage(rawImg, 0, 0, canvasWidth, canvasHeight);
                string filePath = getFilePath(
                    WebImageMaker.CanvasImageDirName, format);
                canvas.Save(filePath, getGDIFormat(format));
                canvasFileName = Path.GetFileName(filePath);
            }
        }
    }
    return canvasFileName;
}

The overload of Graphics.DrawImage used here simply redraws the source image on the whole of the canvas, scaling appropriately. The calls to setGraphicsQuality, getFilePath and getGDIFormat are the same as in the thumbnail case.

Back in the control, we set the source of the canvas image using:

getImageSource(canvasImageName, "canvas");

This method generates the image URL appropriately depending on whether the handler is being used or the control itself will be serving the image.

We are now in ControlMode.Canvas, and the control will render the canvas UI when the Render method is called.

Inserting Client-side Script References

We ensure that the client side UI has the correct JavaScript in the OnPreRender phase. The conditional compilation in this method has already been discussed. The end result is that usually, WebImageMaker_normal.js will be referenced. This has script to position the thumbnails selector and control its visibility (particularly when there may be multiple instances of the control on the page at any one time), as well as the setViewportDimensions function discussed earlier. When the control is in Canvas mode, WebImageMaker_canvas.js will be referenced instead. We also need to inject an additional piece of JavaScript to initialise the canvas in the browser:

Page.ClientScript.RegisterStartupScript(
    this.GetType(), this.ClientID, getInitScript(), true);

private string getInitScript()
{
    string s = @"
function init{0}()
{{
    initialise('{1}', '{2}', '{3}', '{4}', '{5}', 
               '{6}', '{7}', '{8}');
}}

window.onload = init{0};

";
    return String.Format(s, this.ClientID, 
           popupDiv.ClientID, canvas.ClientID, 
           selectionBox.ClientID, imageWidth, 
           imageHeight, targetImage.ClientID, 
           confirmSelection.ClientID, 
           lblDebugInfo.ClientID);
}

(The double "{{" are to escape a single "{" when using String.Format.)

Client Side Canvas Script

The key point to remember is that the user's selection is just a transparent DIV element with a border set to a dashed line, and absolutely positioned to appear to float over the canvas image.

The job of the canvas script is to:

  • Set all the elements up in the correct positions on the page.
  • Initialise the selection DIV in a suitable default position and size, at the correct aspectRatio if both ImageWidth and ImageHeight were specified on the control.
  • Respond to the user's mouse moving and clicking, such that:
    • When the mouse pointer is near the corners or edges of the selection, the cursor changes to a resize icon indicating a resize can be performed in the appropriate direction.
    • When the mouse pointer is inside the selection and away from the edge, the cursor changes to a move icon.
    • When the mouse pointer is near the corners or edges of the selection, clicking and dragging will have the effect of resizing the selection in an intuitive natural way.
    • When the mouse pointer is inside the selection, clicking and dragging should move the selection.
    • If the user does resize the selection and as aspectRatio is being enforced, the script should ensure that the selection is maintained at the correct aspect ratio even though the user's mouse movements would otherwise change the ratio. This constraining of the selection is familiar from image editing packages – the script should ensure that the selection behaves in an intuitive way as the user drags the edges to resize.
  • Store the position and size of the selection relative to the canvas prior to the page being resubmitted.

When the page loads, the initialise(..) method is called, with parameter values that the control wrote out in the pre-render phase described earlier.

First of all, references to all the HTML elements we need to manipulate are stored in variables for ease of use:

...
oCanvas = document.getElementById(canvasID);
oSelection = document.getElementById(selectionBoxID);
...

Then, we resize and position the popupDiv element so that it is a little wider than the canvas image and inset from the top and left of the viewport. We also store the position and size of the canvas itself for later use:

canvasRect = rectangle(oCanvas);

The rectangle function needs some explaining. It is used to represent the position and size of an element without having to continually access its style properties (e.g., oElement.style.left). It also allows us to capture a snapshot of an element that might be changing. This is particularly useful when dealing with the user's selection. Rather than continuously adjusting the size and position of the selection DIV, we use a more abstract object to represent the dimensions and location of the selection (or any other element):

// returns an object that represents
// the size and position of the element oBlock
function rectangle(oBlock)
{
    return { 
        x: parseInt(oBlock.style.left),
        y: parseInt(oBlock.style.top),
        w: parseInt(oBlock.style.width),
        h: parseInt(oBlock.style.height)
    };
}

This method returns an object that has w, h, x, and y properties. It is analogous to the System.Drawing.Rectangle we were using earlier. At the beginning of a sequence of operations on the selection, we can obtain a new rectangle object to represent a snapshot of the current selection. We can then scale, move, and constrain this representation by altering its x, y, w, and h properties, and when we're done, call setSelection with our altered rectangle to apply its new position and location to the selection DIV:

// applies the size and position information in the 
// rectangle rect to the selection div
function setSelection(rect)
{
    oSelection.style.left = rect.x + "px";
    oSelection.style.top = rect.y + "px";
    oSelection.style.width = rect.w + "px";
    oSelection.style.height = rect.h + "px";
}

This means we are not continuously changing the style properties of the selection element itself, which might confuse the issue when resizing.

Back in the initialise(..) function, we continue to set up the page by positioning and sizing the selection appropriately.

if(iReqdWidth > 0 && iReqdHeight > 0)
{
    bConstrain = true;
    aspectRatio = iReqdWidth / iReqdHeight; 
}

bConstrain is a global variable that is checked when the user resizes the selection to see if the selection should be "tweaked" to ensure that the correct aspectRatio is maintained. This demonstrates the use of rectangle:

var selection = rectangle(oSelection);
constrain(selection);
setSelection(selection);

We store a snapshot of the selection in the variable selection, then call constrain(..), then apply the dimensions of the rectangle back to the "real" selection.

The constrain(..) function will be described shortly.

To finish off the initialisation, we hook up some event handlers for mouse events:

// now hook up some event handling:
document.onmousemove = move;
document.onmouseup = up;
document.onmousedown = down;
oCanvas.ondrag = function(){return false;}
oSelection.ondrag = function(){return false;}    
document.ondrag = function(){return false;}

We need the mouse event handlers to be on the whole document rather than just the selection or the canvas, because in a dragging or resizing operation, the user might move the mouse outside of the canvas or even the popupDiv as part of normal use. This is especially true when an aspect ratio is being enforced and the image is being constrained as it's being resized, as the point on the selection the user first grabbed might not stay in sync with the user's mouse as the selection dimensions are constrained by the script. The final three event handlers are essential to cancel any drag events. The browser might otherwise handle these by appearing to select those parts of the page the user moves the mouse over while moving or resizing the selection. This would give a very messy user experience.

Moving the mouse

As the user moves the mouse, the move(e) function will be called. Two global variables, bMoving and bResizing, keep track of whether the script has decided the user is moving or resizing the selection. They will be set in response to a MouseDown event. Initially, both these will be false, indicating the user is just moving the mouse around. The bulk of the move function is a three condition if statement, the last of which is the "just moving the mouse around" condition. We will come back to the first two, which handle moving and resizing, in a moment.

...
else
{
    resizeXMode = "";
    resizeYMode = "";

    var targetSize = 15;
    if(p.x >= selection.x && p.x <= (selection.x + selection.w) 
        && p.y >= selection.y && p.y <= (selection.y + selection.h))
    {
        // cursor is inside the selection
        // default to move behaviour
        oSelection.style.cursor = "move";
        oCanvas.style.cursor = "move";
        bCanMove = true;
        // further checks for hotspots omitted...
...

The two global variables resizeXMode and resizeYMode keep track of what sort of resizing the user would be allowed to perform given the current mouse position. They vary independently. The resizeXMode variable can be either "E"(ast) or "W"(est), and resizeYMode can be either "N"(orth) or "S"(outh). As the user moves the mouse around the script in this else block, we will set these two variables.

They are both blanked at the start of the block, and then we check to see whether or not the mouse pointer is inside the selection. If so, we set bCanMove to true, indicating that the user would be allowed to initiate a move operation from this point. We also set the cursor to the move symbol. We then further check to see if the user is in one of eight "hotspot" areas from which a resize operation could be started, and if so, set the resizeXMode and resizeYMode variables accordingly. We also set the cursor symbol to point in the appropriate direction. The sizes of these hotspots are governed by the targetSize variable, which here is set at 15 pixels.

The rest of this condition is a lot of tedious code that finds out which of these hotspots the cursor is over, and sets the resizeXMode and resizeYMode variables, and the cursor icon, appropriately.

MouseUp and MouseDown

The down(e) and up() functions handle the mouse being pressed and released. Throughout this script, we make heavy use of the positions at which the mouse-move and mouse-down events occur. To make this easier to code against, we use a function which returns an object that represents the position at which an event occurred:

// returns an object that represents the position relative to
// popupOrigin that event e occurred.
function point(e)
{    
    if (e.pageX || e.pageY)
    {
        this.x = e.pageX;
        this.y = e.pageY;
    }
    else if (e.clientX || e.clientY)
    {
        // need to use both document.body and document.documentElement to 
        // cater for various combinations of IE 5/6 in normal and quirksmode
        this.x = e.clientX + document.body.scrollLeft 
            + document.documentElement.scrollLeft;
        this.y = e.clientY + document.body.scrollTop 
            + document.documentElement.scrollTop;
    }
    // give the point position relative to the popup
    this.x -= popupOrigin.x;
    this.y -= popupOrigin.y;
}

This can be seen in the down(e) event handler that follows. Note that the first line allows the event to be handled in a cross-browser manner as discussed on quirksmode.

function down(e)
{
    if (!e) var e = window.event;
    downPoint = new point(e);
    originalRect = rectangle(oSelection);
    
    if(bCanMove && resizeXMode == "" && resizeYMode == "")
    {
        bMoving = true;
    }
    if(resizeXMode != "" || resizeYMode != "")
    {
        bResizing = true;
    }
    return false;
}

function up()
{
    bMoving = false;
    bResizing = false;
}

When the user presses and holds the mouse button, we first of all store the position of the mouse at which the down event occurred:

downPoint = new point(e);

This will be used later to determine how far the mouse has moved. We also store the size and position of the selection as a rectangle in the originalRect variable – again, this will be used to determine how much to resize or move the selection later.

If the mouse was over the selection but not in a hotspot (resizeXMode == "" && resizeYMode == ""), then we set bMoving to true. The next time the move function is called in response to further mouse movement, we'll enter the first condition in the "if" statement. However, if the mouse pointer is over a hotspot, then we set the global variable bResizing to true. The next time the move function is called in response to user mouse movements, the second condition in its "if" statement will be called. This state of affairs continues until the user stops holding the mouse button down. The up event handler couldn't be simpler – it just sets the two globals bMoving and bResizing to false, so that further mouse movements default to the "just moving the mouse around" state discussed already.

Moving is actually the simplest state to handle:

function move(e)
{
    if(bInMove) return; // we're already processing a mousemove event
    
    bInMove = true;
    oCanvas.style.cursor = "auto";
    oSelection.style.cursor = "auto";

    bCanMove = false;
    if (!e) var e = window.event;
    var p = new point(e);    
    var selection = rectangle(oSelection);
            
    if(bMoving)    
    {        
          var dx = p.x - downPoint.x;
          var dy = p.y - downPoint.y;
          selection.x = 0 + originalRect.x + dx;
          selection.y = 0 + originalRect.y + dy;
          setSelection(selection);
          checkConfine(selection);
    }
    ...

As already mentioned, we capture a snapshot of the current selection size and position. Then we find out how far the mouse has moved since the user started moving (in down(e)), and store the difference in dx and dy. Then, all we do is set the new selection position to the old one plus the differences, and call setSelection to apply the new selection rectangle to the selection DIV. We then call checkConfine(..), which disables the OK button and displays a warning message if the selection is anywhere outside the canvas:

function checkConfine(rect)
{
    var msg = "";
    if((rect.x) < canvasRect.x) 
        msg += "Selection extends to the left of the canvas. ";
    if(rect.y < canvasRect.y) 
        msg += "Selection extends above canvas. ";
    if((rect.x + rect.w) > (canvasRect.x + canvasRect.w)) 
        msg += "Selection extends to the right of the canvas. ";
    if((rect.y + rect.h) > (canvasRect.y + canvasRect.h)) 
        msg += "Selection extends below the canvas. "; 
        
    if(msg)
    {
        oConfirmButton.disabled = true;
        oDebugInfo.innerHTML = "WARNING: " + msg;
    }
    else
    {
        oConfirmButton.disabled = false;
        oDebugInfo.innerHTML = "";
    }        
}

Resizing

The middle condition in the move function's if statement handles resizing. To change the size of the selection by moving the right hand edge to the right or the bottom edge downwards is relatively straightforward, as all you need to do is increase the width or height of the selection DIV by the amount the user has moved the mouse. To give the appearance of moving the top of the selection upwards or the left of the selection to the left is more complicated. In these cases, you want to have the bottom or right edges appear to stay where they are, so you need to simultaneously increase the size of the selection by the amount the user has moved the mouse and move it up or leftwards by the same amount. This makes the experience feel natural to the user and is what they will be expecting.

...
else if(bResizing)
{
    var dx = p.x - downPoint.x;
    var dy = p.y - downPoint.y;
    
    if(resizeXMode == "E")
    {        
        selection.w = 0 + originalRect.w + dx;
        if(selection.w < minimumSelectionSize)
        {
            selection.w = minimumSelectionSize;
            bResizing = false;
        }
    }
    if(resizeXMode == "W")
    {
        selection.w = 0 + originalRect.w - dx;
        selection.x = 0 + originalRect.x + dx;            
        if(selection.w < minimumSelectionSize)
        {
            dx = selection.w - minimumSelectionSize;
            selection.w = minimumSelectionSize;
            selection.x += dx; 
            bResizing = false;
        }            
    }
    if(resizeYMode == "S")
    {
        selection.h = 0 + originalRect.h + dy;
        if(selection.h < minimumSelectionSize)
        {
            selection.h = minimumSelectionSize;
            bResizing = false;
        }                
    }
    if(resizeYMode == "N")
    {
        selection.h = 0 + originalRect.h - dy;
        selection.y = 0 + originalRect.y + dy;
        if(selection.h < minimumSelectionSize)
        {
            dy = selection.h - minimumSelectionSize;
            selection.h = minimumSelectionSize;
            selection.y += dy; 
            bResizing = false;
        }            
    }
            
    constrain(selection);
    setSelection(selection);
    checkConfine(selection);        
}
...

Each direction is handled separately. If the user grabbed a corner hotspot then two of the four conditions will be met. Moving "W" or "N" is more complicated than moving "E" or "S" as discussed above. We also cancel the resizing operation (by setting bResizing to false) if the selection ends up too small (this script uses a default minimum selection size of 40 pixels square, which leaves enough to be able to still grab all the hotspots and move the selection around).

Once we have given the selection a new shape according to the user's mouse movements, we need to constrain it to the required aspect ratio if this is being enforced:

function constrain(rect)
{    
    if(bConstrain && rect)
    {
        var newRatio = rect.w / rect.h;
        if(newRatio > aspectRatio)
        {
            // it's too "landscapey" - 
            // keep the height the same but reduce the width 
            // in accordance with the required aspectRatio
            var correctWidth = Math.round(aspectRatio * rect.h);   
            if(correctWidth >= minimumSelectionSize)
            {
                if(resizeXMode == "W")
                {
                    var rightPos = rect.x + rect.w;                 
                    rect.x = rightPos - correctWidth;
                }
                rect.w = correctWidth;                                 
            } 
            else
            {        
                // the constrained selection will be too small
                rect.w = minimumSelectionSize;
                var newH = Math.round(minimumSelectionSize / aspectRatio);
                var dy = newH - rect.h;
                rect.h = newH;
                if(resizeYMode == "N")
                {
                    rect.y -= dy;
                }       
            }
        }
        else
        {
            // it's too "portraity" - 
            //keep the width the same but reduce the height 
            // in accordance with the required aspectRatio
            var correctHeight = Math.round(rect.w / aspectRatio);
            if(correctHeight >= minimumSelectionSize)
            {
                if(resizeYMode == "N")
                {
                    var bottomPos = rect.y + rect.h;
                    rect.y = bottomPos - correctHeight; 
                }
                rect.h = correctHeight;                
            }
            else
            {
                // the constrained selection will be too small
                rect.h = minimumSelectionSize;
                var newW = Math.round(minimumSelectionSize * aspectRatio);
                var dx = newW - rect.w;
                rect.w = newW;
                if(resizeXMode == "W")
                {
                    rect.x -= dx;
                }      
            }
        }
    }
}

First of all, we have to decide whether the current aspect ratio is wider (more landscapey) or taller more (portrait-y) than the required aspect ratio. This tells us which dimension can stay as it is, and which one needs to be altered to bring the overall shape back into proportion. Again, we have to keep the apparent position of the right or bottom edges constant if the user is resizing to the left or top. We also need to make sure that in constraining a dimension (which will always reduce the size), we're not bringing it under the minimumSelectionSize. If so, we need to set the dimension to the minimum and increase the other dimension to compensate.

There are various ways in which this constrain function could work – for example, it could work the other way round and always increase the other dimension rather than reduce it. However, this set of rules seems to produce the most intuitive results when actually manipulating the selection.

Creating the Final Web Image

Pressing the OK button causes the current selection information to be written to a hidden form field:

function storeSelectionInfo(hiddenFieldID)
{
    var field = document.getElementById(hiddenFieldID);    
    // get the selection dimensions relative to the canvas - x,y,w,h    
    var selection = rectangle(oSelection);
    field.value = (selection.x - canvasRect.x) + "," 
        + (selection.y - canvasRect.y) + "," 
        + selection.w + "," + selection.h;    
}

Then the form posts back to the server. This takes us into the OK button's server-side event handler:

void confirmSelection_Click(object sender, EventArgs e)
{
    string[] clientDimensions = hiddenField.Value.Split(new char[] { ',' });
    int x = Convert.ToInt32(clientDimensions[0]);
    int y = Convert.ToInt32(clientDimensions[1]);
    int w = Convert.ToInt32(clientDimensions[2]);
    int h = Convert.ToInt32(clientDimensions[3]);
    // now we have the x,y,w,h of the selection relative to the canvas, so
    // we have to scale the selection so that it is a selection from the 
    // original raw image:
    float scaleFactor = (float)canvasWidth / (float)rawWidth;
    Rectangle transformedSelection = new Rectangle(
        (int)(x / scaleFactor),
        (int)(y / scaleFactor),
        (int)(w / scaleFactor),
        (int)(h / scaleFactor));
    // transformedSelection now represents the user's selected crop on the 
    // raw image rather than the canvas image

    // now determine what the dimensions of the final image should be. If 
    // ImageWidth and ImageHeight were both set then we already know, but
    // if one of them was "*" then we need to work out what it should
    // proportionally be from the user's selected crop shape:
    float selectionAspectRatio = (float)w / (float)h;
    int reqdWidth = intImageWidth;
    int reqdHeight = intImageHeight;
    // these should never be both <= 0
    if (reqdWidth <= 0)
    {
        reqdWidth = (int)(selectionAspectRatio * reqdHeight);
    }
    else if (reqdHeight <= 0)
    {
        reqdHeight = (int)(reqdWidth / selectionAspectRatio);
    }

    // now we have everything we need: what area (transformedSelection) to crop 
    // out of the raw image, and what dimensions this cropped area should be
    // resized to:
    webImageName = ImageProvider.CropAndScale(
        transformedSelection,
        webImageFormat, webImageQuality,
        reqdWidth, reqdHeight);
    targetImage.Src = getImageSource(webImageName, "web");
    controlMode = ControlMode.Changed;
}

Using the coordinates from the client, we create a new System.Drawing.Rectangle called transformedSelection, which represents the user's selection on the canvas transformed to the coordinate system of the raw image. Then, if only one of reqdWidth and reqdHeight has been set as a property of the control (i.e., evaluates as a positive integer rather than "*"), we need to work out what the other dimension of the final web image should be. We use the aspect ratio of the selection to determine this. Once we have all this information, we can call the IImageProvider's CropAndScale method:

public string CropAndScale(System.Drawing.Rectangle 
       transformedSelection, WebImageFormat format, 
       WebImageQuality quality, int reqdWidth, int reqdHeight)
{
    string webFileName = null;
    Rectangle dest = new Rectangle(0, 0, reqdWidth, reqdHeight);
    using (Image rawImg = Image.FromFile(getRawFilePath()))
    {
        using (Bitmap webImage = new Bitmap(
            reqdWidth, reqdHeight, PixelFormat.Format24bppRgb))
        {
            using (Graphics g = Graphics.FromImage(webImage))
            {
                setGraphicsQuality(g, quality);
                g.DrawImage(
                    rawImg, dest, transformedSelection, GraphicsUnit.Pixel);
                string filePath = getFilePath(
                    WebImageMaker.WebImageDirName, format);
                webImage.Save(filePath, getGDIFormat(format));
                webFileName = Path.GetFileName(filePath);
            }
        }
    }
    return webFileName;
}

This is very similar to the GDI+ code we've already seen. We make a new rectangle to represent the final web image (dest), then draw the transformed selection onto it:

g.DrawImage(rawImg, dest, transformedSelection, GraphicsUnit.Pixel);

We then set the control's image to this new web image and change the controlMode to ControlMode.Changed. The developer can later query the control for the file path of the new web image by accessing its WebImagePath property.

Possible Extra Features

There are many ways this control's features could be added to. One example would be to provide a hook into the web image creation stage that allowed external code to process the image before saving, for example, adding a border or a copyright notice.

AJAX

There are a few places in this code where the user experience might be a little improved with the use of some script callbacks. However, the "elephant in the room" with this control is the requirement to get the raw file back to the server, which is likely to be the mother of all postbacks. Saving a few postbacks elsewhere seems a bit trivial after that.

Click here to see the control in action.

History

  • 22nd February, 2006: Initial post
  • 17th February, 2009: Updated demo project

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

Tom Crane
Web Developer
United Kingdom United Kingdom
Tom Crane is a software developer from London. He likes to code in C# but has been known to implement Java-based content management systems for public sector clients.
 
Most of the time he does web programming and tries to make complex tasks seem easy through friendly UI.
 


Comments and Discussions

 
GeneralMy vote of 5 Pinmemberisoint26-Oct-10 5:11 
GeneralVB.NET Version PinmemberCodePuller10-Mar-10 12:51 
QuestionSir, Can you Change WorkingDirectory="C:\wimtest\working" .. Pinmembersuninadh26-May-09 9:20 
AnswerRe: Sir, Can you Change WorkingDirectory="C:\wimtest\working" .. PinmemberScott Assenheimer29-Jun-09 12:03 
GeneralSwap an image on the server instead of upload PinmemberJanco28-May-08 0:35 
GeneralWorking with DNN PinmemberAl-Raza23-Jan-08 22:52 
GeneralImage rotation add-on Pinmemberscippyone1-Jan-07 4:49 
GeneralRe: Image rotation add-on Pinmemberxsoftdev28-Feb-07 20:12 
GeneralLooks impressive - any joy with VS2003 yet Pinmemberwhybotha6917-Dec-06 12:20 
GeneralSaving Web Image to Database, customizing control... [modified] PinmemberJeffSpicolie11-Aug-06 10:16 
AnswerRe: Saving Web Image to Database, customizing control... PinmemberJeffSpicolie14-Aug-06 9:12 
QuestionLicense for control? Pinmemberjahkesh13-Jun-06 18:51 
GeneralBest Control Ever PinmemberBoredOfCoding29-Apr-06 7:50 
GeneralTwo Problems PinmemberG Money26-Apr-06 7:13 
Firstly the canvas does not open when used in conjunction with a master page, secondly when compiling into a control library the code has to be edited slightly from;
 
[ToolboxData("<{0}:WebImageMaker1 runat=\"server\"><{0}:WebImageMaker1>")]
 
to
 
[ToolboxData("<{0}:WebImageMaker runat=\"server\"><{0}:WebImageMaker>")]
 
No big deal, but when using as a crontrol after the canvas editing, if the user accepts the image and Invalid Parameter exception is thrown even if all the properties are set correctly.
 
eg.
Parameter is not valid.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
 
Exception Details: System.ArgumentException: Parameter is not valid.
 
Stack Trace:
 
[ArgumentException: Parameter is not valid.]
System.Drawing.Bitmap..ctor(Int32 width, Int32 height, PixelFormat format) +83
Guild.WebControls.ImageProviderImpl.CropAndScale(Rectangle transformedSelection, WebImageFormat format, WebImageQuality quality, Int32 reqdWidth, Int32 reqdHeight) +120
Guild.WebControls.WebImageMaker.confirmSelection_Click(Object sender, EventArgs e) +770
System.Web.UI.WebControls.Button.OnClick(EventArgs e) +75
System.Web.UI.WebControls.Button.RaisePostBackEvent(String eventArgument) +97
System.Web.UI.WebControls.Button.System.Web.UI.IPostBackEventHandler.RaisePostBackEvent(String eventArgument) +7
System.Web.UI.Page.RaisePostBackEvent(IPostBackEventHandler sourceControl, String eventArgument) +11
System.Web.UI.Page.RaisePostBackEvent(NameValueCollection postData) +172
System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +4921
 
would be great if someone can confirm that this is actually a bug
GeneralGot my 5 PinmemberNigel Shaw15-Apr-06 13:22 
GeneralProblem when the page use Master Page PinmemberAlbert875228-Apr-06 19:07 
GeneralRe: Problem when the page use Master Page [modified] Pinmemberpoteet7-Jun-06 15:03 
GeneralRe: Problem when the page use Master Page PinmemberMedEz26-Dec-06 8:21 
General5 again PinmemberAnasMH7-Mar-06 23:17 
GeneralSuperb Piece of Coding! PinmemberAch1lles24-Feb-06 1:04 
JokeNice work! PinmemberFredrik Kalseth23-Feb-06 1:16 
Generalreally nice PinmemberFrancesco Aruta22-Feb-06 22:27 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.141223.1 | Last Updated 17 Feb 2009
Article Copyright 2006 by Tom Crane
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid