urema
urema

Reputation: 731

Canvas background for retrieving color

I have a canvas with a background set to be lineargradientbrush....how do I then extract the color from this background at a particular mouse point (x,y)?

I can do this with a BitmappedImage fine...as this deals with pixels, not sure about a canvas though...

Upvotes: 2

Views: 3406

Answers (2)

RBEng
RBEng

Reputation: 11

The code posted by Ray Burns didn't work for me but it did lead me down the right path. After some research and experimentation I located the problems to be the bitmap.Render(...) implementation and the Viewbox it uses.

Note: I'm using .Net 3.5 and WPF so maybe his code works in other versions of .Net.

The comments were left here intentionally to help explain the code.

As you can see the Viewbox needs to be normalized with respect to the source Visual Height and Width.

The DrawingVisual needs to be drawn using the DrawingContext before it can be rendered.

In the RenderTargetBitmap method I tried both PixelFormats.Default and PixelFormats.Pbgra32. My testing results were the same with both of them.

Here is the code.

    public static Color GetPixelColor(Visual visual, Point pt)
    {
        Point ptDpi = getScreenDPI(visual);

        Size srcSize = VisualTreeHelper.GetDescendantBounds(visual).Size;

        //Viewbox uses values between 0 & 1 so normalize the Rect with respect to the visual's Height & Width
        Rect percentSrcRec = new Rect(pt.X / srcSize.Width, pt.Y / srcSize.Height,  
                                      1 / srcSize.Width, 1 / srcSize.Height);

        //var bmpOut = new RenderTargetBitmap(1, 1, 96d, 96d, PixelFormats.Pbgra32); //assumes 96 dpi
        var bmpOut = new RenderTargetBitmap((int)(ptDpi.X / 96d),
                                            (int)(ptDpi.Y / 96d),
                                            ptDpi.X, ptDpi.Y, PixelFormats.Default); //generalized for monitors with different dpi

        DrawingVisual dv = new DrawingVisual();
        using (DrawingContext dc = dv.RenderOpen())
        {
            dc.DrawRectangle(new VisualBrush { Visual = visual, Viewbox = percentSrcRec },
                             null, //no Pen
                             new Rect(0, 0, 1d, 1d) );
        }
        bmpOut.Render(dv);

        var bytes = new byte[4];
        int iStride = 4; // = 4 * bmpOut.Width (for 32 bit graphics with 4 bytes per pixel -- 4 * 8 bits per byte = 32)
        bmpOut.CopyPixels(bytes, iStride, 0); 

        return Color.FromArgb(bytes[0], bytes[1], bytes[2], bytes[3]);
    }

If you are interested in the getScreenDPI() function the code is:

    public static Point getScreenDPI(Visual v)
    {
        //System.Windows.SystemParameters
        PresentationSource source = PresentationSource.FromVisual( v );
        Point ptDpi;
        if (source != null)
        {
            ptDpi = new Point( 96.0 * source.CompositionTarget.TransformToDevice.M11,
                               96.0 * source.CompositionTarget.TransformToDevice.M22  );
        }
        else
            ptDpi = new Point(96d, 96d); //default value.

        return ptDpi;
    }

And the usage is similar to Ray's. I show it here for a MouseDown on a canvas.

    private void cvsTest_MouseDown(object sender, MouseButtonEventArgs e)
    {
        Point ptClicked = e.GetPosition(cvsTest);

        if (e.LeftButton.Equals(MouseButtonState.Pressed))
        {
            Color pxlColor = ImagingTools.GetPixelColor(cvsTest, ptClicked);
            MessageBox.Show("Color String = " + pxlColor.ToString());
        }
    }

FYI, ImagingTools is the class where I keep static methods related to imaging.

Upvotes: 1

Ray Burns
Ray Burns

Reputation: 62919

WPF is vector based so it doesn't really have any concept of a "pixel" except within a bitmap data structure. However you can determine the average color of a rectangular area, including a 1x1 rectangular area (which generally comes out as a single pixel on the physical screen).

Here's how to do this:

public Color GetPixelColor(Visual visual, int x, int y)
{
  return GetAverageColor(visual, new Rect(x,y,1,1));
}

public Color GetAverageColor(Visual visual, Rect area)
{
  var bitmap = new RenderTargetBitmap(1,1,96,96,PixelFormats.Pbgra32);
  bitmap.Render(
   new Rectangle
    {
      Width = 1, Height = 1,
      Fill = new VisualBrush { Visual = visual, Viewbox = area }
    });
  var bytes = new byte[4];
  bitmap.CopyPixels(bytes, 1, 0);
  return Color.FromArgb(bytes[0], bytes[1], bytes[2], bytes[3]);
}

Here is how you would use it:

Color pixelColor = GetPixelColor(canvas, x, y);

The way this code works is:

  1. It fills a 1x1 Rectangle using a VisualBrush that shows the selected area of the canvas
  2. It renders this Rectangle on to a 1-pixel bitmap
  3. It gets the pixel color from the rendered bitmap

Upvotes: 0

Related Questions