Question

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;
    }
Was it helpful?

Solution

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));
}

OTHER TIPS

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top