Creating a scrollable and zoomable image viewer in C# Part 4
In the conclusion to our series on building a scrollable and zoomable image viewer, we'll add support for zooming, auto centering, size to fit and some display optimizations and enhancements.
Getting Started
Unlike parts 2 and 3, we're actually adding quite a lot of new functionality, some of it more complicated than others.
First, we're going to remove the ShowGrid
property. This
originally was a simple on/off flag, but we want more control
this time.
We've also got a number of new properties and backing events to add:
AutoCenter
- controls if the image is automatically centered in the display area if the image isn't scrolled.SizeToFit
- if this property is set, the image will automatically zoom to the maximum size for displaying the entire image.GridDisplayMode
- this property, which replacesShowGrid
will determine how the background grid is to be drawn.InterpolationMode
- determines how the zoomed image will be rendered.Zoom
- allows you to specify the zoom level.ZoomIncrement
- specifies how much the zoom is increased or decreased using the scroll wheel.ZoomFactor
- this protected property returns the current zoom as used internally for scaling.ScaledImageWidth
andScaledImageHeight
- these protected properties return the size of the image adjusted for the current zoom.
Usually the properties are simple assignments, which compare the values before assignment and raise an event. The zoom property is slightly different as it will ensure that the new value fits within a given range before setting it.
private static readonly int MinZoom = 10;
private static readonly int MaxZoom = 3500;
[DefaultValue(100), Category("Appearance")]
public int Zoom
{
get { return _zoom; }
set
{
if (value < ImageBox.MinZoom)
value = ImageBox.MinZoom;
else if (value > ImageBox.MaxZoom)
value = ImageBox.MaxZoom;
if (_zoom != value)
{
_zoom = value;
this.OnZoomChanged(EventArgs.Empty);
}
}
}
Using the MinZoom
and MaxZoom
constants we are specifying a
minimum value of 10% and a maximum of 3500%. The values you are
assign are more or less down to your own personal preferences -
I don't have any indications of what a "best" maximum value
would be.
Setting the SizeToFit
property should disable the AutoPan
property and vice versa.
Layout Updates
Several parts of the component work from the image size, however
as these now need to account for any zoom level, all such calls
now use the ScaledImageWidth
and ScaledImageHeight
properties.
protected virtual int ScaledImageHeight
{ get { return this.Image != null ? (int)(this.Image.Size.Height * this.ZoomFactor) : 0; } }
protected virtual int ScaledImageWidth
{ get { return this.Image != null ? (int)(this.Image.Size.Width * this.ZoomFactor) : 0; } }
protected virtual double ZoomFactor
{ get { return (double)this.Zoom / 100; } }
The AdjustLayout
method which determines the appropriate
course of action when certain properties are changed has been
updated to support the size to fit functionality by calling the
new ZoomToFit
method.
protected virtual void AdjustLayout()
{
if (this.AutoSize)
this.AdjustSize();
else if (this.SizeToFit)
this.ZoomToFit();
else if (this.AutoScroll)
this.AdjustViewPort();
this.Invalidate();
}
public virtual void ZoomToFit()
{
if (this.Image != null)
{
Rectangle innerRectangle;
double zoom;
double aspectRatio;
this.AutoScrollMinSize = Size.Empty;
innerRectangle = this.GetInsideViewPort(true);
if (this.Image.Width > this.Image.Height)
{
aspectRatio = ((double)innerRectangle.Width) / ((double)this.Image.Width);
zoom = aspectRatio * 100.0;
if (innerRectangle.Height < ((this.Image.Height * zoom) / 100.0))
{
aspectRatio = ((double)innerRectangle.Height) / ((double)this.Image.Height);
zoom = aspectRatio * 100.0;
}
}
else
{
aspectRatio = ((double)innerRectangle.Height) / ((double)this.Image.Height);
zoom = aspectRatio * 100.0;
if (innerRectangle.Width < ((this.Image.Width * zoom) / 100.0))
{
aspectRatio = ((double)innerRectangle.Width) / ((double)this.Image.Width);
zoom = aspectRatio * 100.0;
}
}
this.Zoom = (int)Math.Round(Math.Floor(zoom));
}
}
Due to the additional complexity in positioning and sizing, we're also adding functions to return the different regions in use by the control.
GetImageViewPort
- returns a rectangle representing the size of the drawn image.GetInsideViewPort
- returns a rectangle representing the client area of the control, offset by the current border style, and optionally padding.GetSourceImageRegion
- returns a rectangle representing the area of the source image that will be drawn onto the control.
The sample project has been updated to be able to display the
results of the GetImageViewPort
and GetSourceImageRegion
functions.
public virtual Rectangle GetImageViewPort()
{
Rectangle viewPort;
if (this.Image != null)
{
Rectangle innerRectangle;
Point offset;
innerRectangle = this.GetInsideViewPort();
if (this.AutoCenter)
{
int x;
int y;
x = !this.HScroll ? (innerRectangle.Width - (this.ScaledImageWidth + this.Padding.Horizontal)) / 2 : 0;
y = !this.VScroll ? (innerRectangle.Height - (this.ScaledImageHeight + this.Padding.Vertical)) / 2 : 0;
offset = new Point(x, y);
}
else
offset = Point.Empty;
viewPort = new Rectangle(offset.X + innerRectangle.Left + this.Padding.Left, offset.Y + innerRectangle.Top + this.Padding.Top, innerRectangle.Width - (this.Padding.Horizontal + (offset.X * 2)), innerRectangle.Height - (this.Padding.Vertical + (offset.Y * 2)));
}
else
viewPort = Rectangle.Empty;
return viewPort;
}
public Rectangle GetInsideViewPort()
{
return this.GetInsideViewPort(false);
}
public virtual Rectangle GetInsideViewPort(bool includePadding)
{
int left;
int top;
int width;
int height;
int borderOffset;
borderOffset = this.GetBorderOffset();
left = borderOffset;
top = borderOffset;
width = this.ClientSize.Width - (borderOffset * 2);
height = this.ClientSize.Height - (borderOffset * 2);
if (includePadding)
{
left += this.Padding.Left;
top += this.Padding.Top;
width -= this.Padding.Horizontal;
height -= this.Padding.Vertical;
}
return new Rectangle(left, top, width, height);
}
public virtual Rectangle GetSourceImageRegion()
{
int sourceLeft;
int sourceTop;
int sourceWidth;
int sourceHeight;
Rectangle viewPort;
Rectangle region;
if (this.Image != null)
{
viewPort = this.GetImageViewPort();
sourceLeft = (int)(-this.AutoScrollPosition.X / this.ZoomFactor);
sourceTop = (int)(-this.AutoScrollPosition.Y / this.ZoomFactor);
sourceWidth = (int)(viewPort.Width / this.ZoomFactor);
sourceHeight = (int)(viewPort.Height / this.ZoomFactor);
region = new Rectangle(sourceLeft, sourceTop, sourceWidth, sourceHeight);
}
else
region = Rectangle.Empty;
return region;
}
Drawing the control
As with the previous versions, the control is drawn by
overriding OnPaint
, this time we are not using clip regions or
drawing the entire image even if only a portion of it is
visible.
// draw the borders
switch (this.BorderStyle)
{
case BorderStyle.FixedSingle:
ControlPaint.DrawBorder(e.Graphics, this.ClientRectangle, this.ForeColor, ButtonBorderStyle.Solid);
break;
case BorderStyle.Fixed3D:
ControlPaint.DrawBorder3D(e.Graphics, this.ClientRectangle, Border3DStyle.Sunken);
break;
}
Depending on the value of the GridDisplayMode
property, the
background tile grid will either not be displayed, will be
displayed to fill the client area of the control, or new for
this update, to only fill the area behind the image. The
remainder of the control is filled with the background color.
Rectangle innerRectangle;
innerRectangle = this.GetInsideViewPort();
// draw the background
using (SolidBrush brush = new SolidBrush(this.BackColor))
e.Graphics.FillRectangle(brush, innerRectangle);
if (_texture != null && this.GridDisplayMode != ImageBoxGridDisplayMode.None)
{
switch (this.GridDisplayMode)
{
case ImageBoxGridDisplayMode.Image:
Rectangle fillRectangle;
fillRectangle = this.GetImageViewPort();
e.Graphics.FillRectangle(_texture, fillRectangle);
if (!fillRectangle.Equals(innerRectangle))
{
fillRectangle.Inflate(1, 1);
ControlPaint.DrawBorder(e.Graphics, fillRectangle, this.ForeColor, ButtonBorderStyle.Solid);
}
break;
case ImageBoxGridDisplayMode.Client:
e.Graphics.FillRectangle(_texture, innerRectangle);
break;
}
}
Previous versions of the control drew the entire image using the
DrawImageUnscaled
method of the Graphics
object. In this
final version, we're going to be a little more intelligent and
only draw the visible area, removing the need for the previous
clip region. The InterpolationMode
is used to determine how
the image is drawn when it is zoomed in or out.
// draw the image
g.InterpolationMode = this.InterpolationMode;
g.DrawImage(this.Image, this.GetImageViewPort(), this.GetSourceImageRegion(), GraphicsUnit.Pixel);
Zooming Support
With the control now all set up and fully supporting zoom, it's time to allow the end user to be able to change the zoom.
The first step is to disable the ability to double click the control, by modifying the control styles in the constructor.
this.SetStyle(ControlStyles.StandardDoubleClick, false);
We're going to allow the zoom to be changed two ways - by either scrolling the mouse wheel, or left/right clicking the control.
By overriding OnMouseWheel
, we can be notified when the user
spins the wheel, and in which direction. We then adjust the zoom
using the value of the ZoomIncrement
property. If a modifier
key such as Shift or Control is pressed, then we'll modify the
zoom by five times the increment.
protected override void OnMouseWheel(MouseEventArgs e)
{
if (!this.SizeToFit)
{
int increment;
if (Control.ModifierKeys == Keys.None)
increment = this.ZoomIncrement;
else
increment = this.ZoomIncrement * 5;
if (e.Delta < 0)
increment = -increment;
this.Zoom += increment;
}
}
Normally, whenever we override a method, we always call it's
base implementation. However, in this case we will not; the
ScrollbableControl
that we inherit from uses the mouse wheel
to scroll the viewport and there doesn't seem to be a way to
disable this undesirable behaviour.
As we also want to allow the user to be able to click the
control with the left mouse button to zoom in, and either the
right mouse button or left button holding a modifier key to zoom
out, we'll also override OnMouseClick
.
protected override void OnMouseClick(MouseEventArgs e)
{
if (!this.IsPanning && !this.SizeToFit)
{
if (e.Button == MouseButtons.Left && Control.ModifierKeys == Keys.None)
{
if (this.Zoom >= 100)
this.Zoom = (int)Math.Round((double)(this.Zoom + 100) / 100) * 100;
else if (this.Zoom >= 75)
this.Zoom = 100;
else
this.Zoom = (int)(this.Zoom / 0.75F);
}
else if (e.Button == MouseButtons.Right || (e.Button == MouseButtons.Left && Control.ModifierKeys != Keys.None))
{
if (this.Zoom > 100 && this.Zoom <= 125)
this.Zoom = 100;
else if (this.Zoom > 100)
this.Zoom = (int)Math.Round((double)(this.Zoom - 100) / 100) * 100;
else
this.Zoom = (int)(this.Zoom * 0.75F);
}
}
base.OnMouseClick(e);
}
Unlike with the mouse wheel and it's fixed increment, we want to use a different approach with clicking. If zooming out and the percentage is more than 100, then the zoom level will be set to the current zoom level + 100, but rounded to the nearest 100, and the same in reverse for zooming in.
If the current zoom is less than 100, then the new value will +- 75% of the current zoom, or reset to 100 if the new value falls between 75 and 125.
This results in a nicer zoom experience then just using a fixed value.
Sample Project
You can download the final sample project from the link below.
What's next?
One of the really annoying issues with this control that has
plagued me during writing this series is scrolling the
component. During scrolling there is an annoying flicker as the
original contents are moved, then the new contents are drawn. At
present I don't have a solution for this, I've tried overriding
various WM_*
messages but without success. A future update to
this component will either fix this issue, or do it's own
scrollbar support without inheriting from ScrollableControl
,
although I'd like to avoid this latter solution.
If anyone knows of a solution please let us know!
Another enhancement would be intelligent use of the interpolation mode. Currently the control uses a fixed value, but some values are better when zoomed in, and some better when zoomed out. The ability for the control to automatically select the most appropriate mode would be useful.
Update History
- 2010-08-28 - First published
- 2020-11-21 - Updated formatting
Related articles you may be interested in
- Displaying multi-page tiff files using the ImageBox control and C#
- Adding drag handles to an ImageBox to allow resizing of selection regions
- ImageBox 1.1.4.0 update
- ImageBox and TabList update's - virtual mode, pixel grid, bug fixes and more!
- ImageBox update, version 1.1.0.0
- Zooming to fit a region in a ScrollableControl
- Zooming into a fixed point on a ScrollableControl
- Arcade explosion generator
- Creating an image viewer in C# Part 5: Selecting part of an image
- Extending the ImageBox component to display the contents of a PDF file using C#
- Creating a scrollable and zoomable image viewer in C# Part 3
- Creating a scrollable and zoomable image viewer in C# Part 2
- Creating a scrollable and zoomable image viewer in C# Part 1
Downloads
Filename | Description | Version | Release Date | |
---|---|---|---|---|
imageboxsample-part4.zip
|
Fourth and final in a multi part series on creating an image viewer that can be scrolled and zoomed in C#. After part three added panning, we now add zoom support via the mouse wheel and clicking with the left or right buttons, along with some additional display properties. |
28/08/2010 | Download |
Leave a Comment
While we appreciate comments from our users, please follow our posting guidelines. Have you tried the Cyotek Forums for support from Cyotek and the community?
Comments
DotNetKicks.com
#
[b]Creating a scrollable and zoomable image viewer in C#[/b] You've been kicked (a good thing) - Trackback from DotNetKicks.com - Trackback from DotNetKicks.com
DotNetShoutout
#
[b]Creating a scrollable and zoomable image viewer in C# Part 4 :: Cyotek[/b] Thank you for submitting this cool story - Trackback from DotNetShoutout - Trackback from DotNetShoutout
Roberto Beretta
#
Hi. First of all, sorry for my bad english! This control is perfect, but I have a question: there is a way to zoom using mouse scroll wheel, on the image zone under mouse pointer? (actually, zoom reset pointer to (0,0))
Thanks a lot!
Richard Moss
#
Hi Roberto,
Your English is fine, don't worry :) In regards to the zooming, yes it is possible - you'd need to adjust the AutoScrollPosition as you zoom. I'll try and update the sample with this as soon as I have some free time.
Regards; Richard Moss
Mark Malburg
#
Have you come up with a way to maintain the same image position under the mouse as the wheel is scrolled (much like Google Maps)? This would be super helpful.
Richard Moss
#
Mark,
Thanks for the question. Yes, this bug was fixed several years back. You can either install the latest binary via NuGet, or grab the source from GitHub.
Hope this helps.
Regards;
Richard Moss
Roberto Beretta
#
Hi Richard,
any news about zooming?
I'm sorry for my insistency... I tried to play alone with AutoScrollPosition parameter, but without any significative result :(
Richard Moss
#
No, I haven't got it working yet - I had it "sort" of working, but it's not good enough.
Kerwin Lumpkins
#
I'd like to use this control in a project, but I'm not clear on how to bring into a project. Info that I have found posted speaks of creating a .dll from code like yours and then importing that as a reference. Is there another method for bringing in your source code and making it so that .net finds the control so that I can begin using it?
Kerwin
Richard Moss
#
Kerwin,
There are two different approaches you could take. The first is to add ImageBox.cs, ImageBoxGridDisplayMode.cs and ImageBoxGridScale.cs directly to your solution. This will then appear in the Toolbox for your to drag onto your forms, and will have no external dependency.
The second option would be to create a new Class Library project, and add the files to this instead. You could add a reference to this new assembly to your application project. If you plan on using the control in multiple applications, this would be the better way to go, but if you only plan on using it in a single application then directly embedding it your application's source is easier to maintain.
Hope this helps.
Regards; Richard Moss
Casey
#
Can this tool be used in any other frameworks besides 4.0? I can't seem to get it to work in 3.5.
Richard Moss
#
Hello. It wasn't actually written against .NET 4, it was written against .NET 3.5 but should work equally well with just 2.0 - if you can let me know what specific issues you had I might be able to advise better.
Arsalan
#
Cuong Doan
#
Hi Richard!! At first, forgive my bad english :D. Second, i really appreciate you for developed component. I'm using this component in my project and it worked wells except one problem. Do you have any solution for zoom in the image but not losing it's focus location? I've tried to find out a way but still stuck in this. If you have any ideas about its, please share with.
Regard!!
Cuong Doan
Bryan Brannon
#
I, too, am decypering this code to figure out how to zoom into the X,Y coordinate of the cursor location. Just curious if there was any update. I am implementing a Click/Drag/Draw Rectangle to zoom into that position but am still trying to get the AutoScrollPosition to workout as expected.
Japala
#
I'm too trying to get the autoscroll to move to the mouse coordinates. It sort of works but the movement is not exact to the pixel that I had the curson on at the time of scrolling the wheel... I do get the concept but for some reason I have something missing...
inside the onmousewheel I have this....
_startScrollPosition = this.AutoScrollPosition; if (e.Delta < 0) position = new Point((-_startScrollPosition.X) - Convert.ToInt32(e.Location.X * this.ZoomFactor), (-_startScrollPosition.Y) - Convert.ToInt32(e.Location.Y * this.ZoomFactor)); else position = new Point(Convert.ToInt32((-_startScrollPosition.X + e.Location.X )* this.ZoomFactor), Convert.ToInt32((-_startScrollPosition.Y + e.Location.Y)* this.ZoomFactor)); this.UpdateScrollPosition(position);
The zooming in part is nearly there but still not exact. :( Any help?!
-Jani
Jordan
#
Hello. I wanted to ask, is there any way I can use this in WPF Forms. I get an error when I try adding this control to WPF Form. I couldnt find a way of converting it from ImageBox to UIElement. Is there any way to make it work ? If someone could help, please send me an e-mail :) Tnx in advance :)
Richard Moss
#
Hello,
No idea I'm afraid; I haven't done much with WPF. I know it does have a host for Windows Form controls but not sure if that's a suitable way of displaying this, nor do I know how much effort it would be to convert the control into a WPF control. Maybe I'll look at that for a future article!
Sorry I can't be of more assistance.
Richard Moss
Rafael Vasco
#
If anyone is seeing this, here are some enhancements:
To correctly transform check background (stop the flickering):
g.TranslateTransform(this.AutoScrollPosition.X, this.AutoScrollPosition.Y); Rectangle fillRect = this.GetImageViewport(); g.FillRectangle(tileBrush, fillRect); g.ResetTransform();
Fix PointToImage:
(...) x = (int)(((point.X) - viewport.X)/this.ZoomFactor); y = (int)(((point.Y) - viewport.Y)/this.ZoomFactor); (...)
Richard Moss
#
Hello,
Thanks for your comments. In regards to "fixing PointToImage"... I'm not entirely sure what it is you are fixing, unless you mean the fact it returns +1 for the position. This was a deliberate design decision so that hovering over 0,0 doesn't return Point.Empty. So I don't really count that as a bug, although it's probably not suitable for everyone! Saying that, as I wrote this reply I recalled that there used to be a bug in the formula but this was fixed at the start of the year when another user spotted it - that's probably in the recent part 5 source code though, rather than this older version.
I haven't tested your checkbox fix as it doesn't look like it matches my copy (which uses a different way of generating the texture) but I suspect that won't work either as it doesn't account for the controls border's, and I know they are affected by the flicker too. Another user provided me with some code some time ago which used a custom scroll control and a helping of Win32 interop to fix the scroll issue, however I didn't want to complicate these examples with code of this nature. I should probably dig it out and add them to the site for interested parties anyway!
Thanks again for the comment.
Regards; Richard Moss
Rafael Vasco
#
Oh sorry i didn't see part 5 :) I'm making an image editor so PointToImage in part 4 was wrong, when i clicked on image it painted slighty off position, just a matter of applying scalefactor to the whole expression. Anyway i'll take a look at part 5. Thanks.
Henrik Haftmann
#
I'm new in .NET programming. Regarding flicker while scrolling … In Win32 you have to code all the scrolling behaviour yourself, i.e. handle WM_HSCROLL, WM_VSCROLL, eventually call ScrollWindow(). To prevent (slight) flicker which is caused by the small delay between ScrollWindow() invocation (quite fast) and slower WM_PAINT processing, ScrollWindow() has to be avoided, ScrollDC() should be used for the double-buffer bitmap, and then the invalid regions of the bitmap has to be actualized before "blt"ing it to the control's client area. Unfortunately, the .NET standard behaviour is to use ScrollWindow() inside its WM_HSCROLL / WS_VSCROLL message handler. So the solution starts capturing these messages in your own handlers …
However, in most cases, ScrollWindow() gives closer feedback to user input than any other method, because it's fast, and scroll distances are mostly less than half a client size.
Some applications call UpdateWindow() afterwards to accelerate updating of the newly-exposed invalid area, some not. It depends on complexity of the exposed area. While UpdateWindow() is in progress, further user input cannot be handled.
I'm searching for a scrollable image viewer with a miniature of the entire picture in one of the four corners, showing a red rectangle for the area currently shown. While I'm twiddeling with a solution, flickering is even more annoying because every scroll movement shifts this "fixed part of image" out of the corner. This kind of viewer is especially useful for very large images or vector graphics.
Richard Moss
#
Henrik,
Thanks for your comments. I'm currently working on an update to the ImageBox control, and one of the things I've done is integrate a new base ScrollControl / VirtualScrollableControl that was provided to me last year - I've been able to use this as a drop-in replacement for .NET's ScrollableControl and this provides a fully flicker free experience (which is just as well fortunately as one of the enhancements I'd made to the control introduced some chronic flicker!). This control sets the WS_HSCROLL and WS_VSCROLL window styles and then handles the WM_VSCROLL and WM_HSCROLL messages. Happily this appears to be working rather nicely.
Hopefully this new update will be available soon.
Regards; Richard Moss
DirtyCode
#
first of all thank you for this great article i want to use this control in my project, but i got a little space between control's picture and the HScrollbar of cotrol when i change the InterpolationMode property to NearestNeighbor value why is that happening, and how can i fix it? regards
Richard Moss
#
Hello,
This is a known bug with due to the default Graphics.PixelOffsetMode. If you grab the latest source (the source in this article is quite old) then this is fixed.
https://github.com/cyotek/Cyotek.Windows.Forms.ImageBox http://cyotek.com/blog/imagebox-and-tablist-updates-virtual-mode-pixel-grid-bug-fixes-and-more
Regards; Richard Moss
urlreader
#
Hi, thanks for the work. It is a great control. Just curious, how can we zoom the image using the mouse point as center? so we can point the area we want to zoom, scroll to zoom and do not have to zoom and then scroll to find the point where we want to zoom? any update? thanks
I guess the picturebox always use (0,0) as center of zoom?
Richard Moss
#
Hello,
The latest source code - http://cyotek.com/blog/imagebox-and-tablist-updates-virtual-mode-pixel-grid-bug-fixes-and-more - includes a fix for this and more.
Regards; Richard Moss
MutStarburst
#
Error at line 2286 in ImageBox::GetSelectedImage() function: Parameter is not valid. => Add: Check rect before create image if (rect.Width <= 0 || rect.Height <= 0) return null;
Richard Moss
#
Hello,
Thank you for the bug report and fix. I'll make sure this goes into the next minor build of the ImageBox, due in the next couple of days. Alternatively, if you have a GitHub account, feel free to fork and make a pull request.
Regards; Richard Moss
Chris
#
I LOVE your coding style! Nobody understands how annoy it is to have brackets for if or else statements when there is only one statement inside it! But you do!
Richard Moss
#
Chris,
Thanks for the kind comment! Alas in this case I seem to have come full circle and am once again surrounding single lines with braces in recent code. Who knows though, I may change my mind again :)
Regards; Richard Moss
sudhakar
#
Your work is really amazing , i can't appreciate in single word.
Jack Cai
#
Hi there, we are using your imagebox in our project. It is very nicely coded. However, we have one problem with this control. In a high zoom value (e.g. 20x or 40x). We try to scroll down to the bottom of the image. It seems that the last row or last colume sometime is not render. It is probably due to the full pixel render. Is it an easy way to change to sub pixel render (like smooth scroll)
Richard Moss
#
Jack,
I don't think I've seen this before. Firstly, have you tried using the latest source? If so and you still have the problem, then could you please raise an issue either on the forums or GitHub along with a sample image and/or set of steps that I can use to exactly reproduce the issue?
Thanks;
Richard Moss
Naeem Shah
#
hi, how to add usercontrol on imagebox and when image zoom that control also zoom and stay same position where its first there like there is 3 shapes in image each shape contain 1 usercontrol if we zoom image usercontrol stay on that image shape area sorry for my bad english thanks