3

I am custom drawing two zoom-in images on the screen, one next to each other. Each one of them occupies half of the screen.

I have previously done it in .net 3.5 (I think) by overriding the OnPaint():

    //using System.Drawing

    /// <summary>
    /// Custom drawing
    /// </summary>
    /// <param name="e"></param>
    protected override void OnPaint(PaintEventArgs e)
    {
        e.Graphics.DrawImage(Image, DestRectangle, SrcRectangle, GraphicsUnit);
    }

The description of the DrawImage method: "Draws the specified portion of the specified Image at the specified location and with the specified size." (MSDN)

I am trying to achieve the same thing using .net 4.5. I am overriding the OnRender and using the DrawingContext object to perform my drawings. Basically this is my loop:

    //using System.Windows.Media;

    /// <summary>
    /// Overide the OnRender to have access to a lower level of drawing.
    /// </summary>
    /// <param name="drawingContext"></param>
    protected override void OnRender(DrawingContext drawingContext)
    {
        drawingContext.DrawImage(BitmapImage_Left, Window_LeftHalf);
        drawingContext.DrawImage(BitmapImage_Right, Window_RightHalf);
    }

It works just fine if I want to display the stretched picture. What I want is to display (in Window_LeftHalf and Window_RightHalf) a portion of the picture (like zoom-in). Basically what graphics.DrawImage (look above) does, but using the DrawingContext object.

I have tried to look at MSDN but I couldn't pull anything interesting out. Maybe creating a buffer that is later used by the DrawingContext? I'm almost sure there is the need for an intermediate object that holds the zoomed-in images. Any ideas?

UPDATE: I am using the mouse to navigate through the image, so performance is important. For example:

    /// <summary>
    /// Handles the mouse move events.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private static void MouseMoveEventHandler(RoutedEventArgs e)
    {
        // The size of the crop is always the same
        // but the portion of the picture different.
        crop.X += mouseDelta.X;
        crop.Y += mouseDelta.Y;
    }
jimasun
  • 604
  • 9
  • 12

2 Answers2

3

Take a look at the CroppedBitmap class. Just as you used to be able to do with e.Graphics.DrawImage(), CroppedBitmap allows you to specify just the portion of the image you're interested in.

Here's an example:

protected override void OnRender(System.Windows.Media.DrawingContext dc)
{
    int halfWidth = (int)this.Width / 2;
    int height = (int)this.Height;
    BitmapImage leftImage = new BitmapImage(new Uri(@"C:\Users\Public\Pictures\Sample Pictures\Chrysanthemum.jpg"));
    BitmapImage rightImage = new BitmapImage(new Uri(@"C:\Users\Public\Pictures\Sample Pictures\Desert.jpg"));
    CroppedBitmap leftImageCropped = new CroppedBitmap(leftImage, new Int32Rect(0, 0, halfWidth, height));
    CroppedBitmap rightImageCropped = new CroppedBitmap(rightImage, new Int32Rect(0, 0, halfWidth, height));
    dc.DrawImage(leftImageCropped, new System.Windows.Rect(0, 0, leftImageCropped.Width, height));
    dc.DrawImage(rightImageCropped, new System.Windows.Rect(halfWidth, 0, halfWidth, height));
}
cokeman19
  • 2,405
  • 1
  • 25
  • 40
  • Hey cokeman19, thank for reply. Yes! I havent tried it yet, but it looks just like the thing that I want. The only difference is that every time I need a new section of the image I need to create/ modify an CroppedBitmap object, where `e.Graphics.DrawImage()` would use the same bitmap all the time. Thanks anyway, I'll use it. I'll update my post to a more specific issue. – jimasun Dec 02 '12 at 21:23
0

EDIT 2 : ImageBrush.Viewbox. The Viewbox is a Rect with dimensions [0.0... 1.0] that let you control what used to be the SourceRect. I tested this and it works brilliantly. What I did:

In my Window:

    protected ImageBrush imgBrush = new ImageBrush(new ImageSource(new Uri("image.png")));
    protected Rect vBox = new Rect(0, 0, 1, 1);
    protected Point lastPosition = new Point(0, 0);

My container for this was a WPF Rectangle called tgtRect whose Rect.Fill was imgBrush. The zoom and scroll methods were as follows:

    protected void tgtRect_MouseWheel(object sender, MouseWheelEventArgs e)
    {
        // Zoom in when Delta is positive, Zoom out when negative
        double exp = -e.Delta / Math.Abs(e.Delta);
        double val = Math.Pow(1.1, exp);
        vBox.Scale(val, val);
        imgBrush.Viewbox = vBox;
    }

    void tgtRect_MouseMove(object sender, MouseEventArgs e)
    {
        Point thisPosition = e.GetPosition(tgtRect);
        if (e.RightButton == MouseButtonState.Pressed)
        {
            double w = tgtRect.ActualWidth;
            double h = tgtRect.ActualHeight;
            Vector offset = lastPosition - thisPosition;
            offset.X /= w;
            offset.Y /= h;
            vBox.Offset(offset);
            imgBrush.Viewbox = vBox;
        }
        lastPosition = thisPosition;
    }

For your implementation:

    protected override void OnRender(DrawingContext drawingContext)
    {
        drawingContext.DrawRectangle(imgBrush, null, DesRect);
    }

You will likely need to maintain a separate imgBrush for each rectangle you want to draw to. I tried the above code (not the OnRender override) in a WPF Window with just a Rectangle whose Rectangle.Fill was this ImageBrush and the performance was very good. If you have any troubles, I think we can sort it out, but I think the ImageBrush will end up being the correct implementation. This has been a very interesting project! Thank you for your question.

END EDIT 2

You will need to define the Rect objects "Window_LeftHalf" and "Window_RightHalf" to be the actual size you want the images to be rendered. For example, if you are zoomed 200%, then the Rect.Width and Rect.Height properties will need to be 2x the size of the original ImageSource.

EDIT:

Old method was :

protected override void OnPaint(PaintEventArgs e)
{
    e.Graphics.DrawImage(Image, DestRectangle, SrcRectangle, GraphicsUnit);
}

Using 'BitmapImage.SourceRect` :

protected override void OnRender(DrawingContext drawingContext)
{
    BitmapImage_Left.SourceRect = SrcRectangleLeft;
    drawingContext.DrawImage(BitmapImage_Left, Window_LeftHalf);
    BitmapImage_Right.SourceRect = SrcRectangleRight;
    drawingContext.DrawImage(BitmapImage_Right, Window_RightHalf);
}

Your mouse functions can change the SourceRect. For example:

private static void MouseMoveEventHandler(RoutedEventArgs e)
{
    // The size of the crop is always the same
    // but the portion of the picture different.
    SrcRectangleLeft = new Int32Rect(SrcRectangleLeft.X + mouseDelta.X, 
        SrcRectangleLeft.Y + mouseDelta.Y,
        SrcRectangleLeft.Width, SrcRectangleLeft.Height);
}

Not sure how the performance will be, but should be better than mapping portions of the bitmap onto new objects each update.

Hope this has helped at all.

newb
  • 846
  • 6
  • 9
  • Hey newb, thanks for reply. I had tried it before. If I do that the images will overlap. Even if I display them in the right spot, the Window_*Half will not display the *specific* parts of the images (zoomed). – jimasun Dec 02 '12 at 21:17
  • That would make sense if you're not repositioning the target rectangles. I'm re-reading this and imagining side-by-side scrolling windows for each half of the window. Is that right? – newb Dec 02 '12 at 22:02
  • Yes, side by side, fixed "windows." But the content is always changing. So if I display 200% I can only have a fixed content (on x-axis). – jimasun Dec 03 '12 at 20:49
  • Ok. I think I might have another solution. If your BitmapImage_Left and BitmapImage_Right are indeed BitmapImage objects, you can set their BitmapImage.SourceRect property based on the position and zoom. I'll edit the original answer with an example. – newb Dec 04 '12 at 19:44
  • If the SourceRect is the area of the image that is to be considered, than I think this is the answer. I don't know how I completely missed this. I haven't checked this yet but I'll surely come back here if it didn't worked. Thanks! – jimasun Dec 05 '12 at 22:55
  • Okay, there is a little problem here. The `BitmapImage.SourceRect` property can't be changed on demand. Or at leas this is what I understood ([MSDN](http://msdn.microsoft.com/en-us/library/system.windows.media.imaging.bitmapimage.aspx) The 'Remarks' section). If there is a way than this is a valid solution, otherwise I'm going to stick with `CroppedBitmap`. – jimasun Dec 06 '12 at 13:23
  • I was just looking through a solution where I had to do something similar recently (cropping images, technically visuals, for printing a dynamic UI), and there I used Brushes. One more edit! I promise this is going to be a perfect solution! – newb Dec 06 '12 at 14:22
  • I couldn't figure it out how to change the properties on demand. I'll stick with creating new `CroppedBitmap`. It seems to pop the CPU a bit to much for what it does, but I'll just stick to it for the moment. Thanks (both) for the input. – jimasun Dec 06 '12 at 23:20