Is it possible to Brush a DrawingVisual? - wpf

I am working with DrawingVisual in WPF and want to make some Effect.
Is it possible to brush a DrawingVisual with any brushes? That means:
Suppose I have a DrawingVisual:
DrawingVisual myVisual = new DrawingVisual();
myVisual.RenderOpen().....
.....
....
....
No matter how I draw, I want to make the whole visual to be in a color brush at the end.
something like: drawingcontext.drawvisual(myVisual, Brushes.Red) ??
Thank you.

A DrawingVisual is not itself a drawing object that can be filled with a Brush or outlined with a Pen. Instead it is a container for Drawing objects (and for other visuals, since it is also a ContainerVisual).
If you want to "fill" a DrawingVisual with a "background" brush, you would have to draw an appropriate Drawing, e.g. a large enough filled rectangle.

Related

HowTo Get Rendered Image from WPF Image Control

Each WPF Image Control has RenderTransform Property that sets Scale, Skew, Rotate and many more Transformation to Images. After Calling RenderTransform Property, How to get Rendered Image into the BitmapImage or RenderTargetBitmap Class or Any other class?
This is my Code:
Dim InImage As New BitmapImage(New Uri("My Image Path"))
Dim TG As New TransformGroup
TG.Children.Add(New RotateTransform(190))
Dim MyImg As New Image
MyImg.Source = InImage
MyImg.RenderTransform = TG
'Here i need get Transformed Image into a BitmapImage or RenderTargetBitmap Variable or Any other class variable.
It's a pity that TransformedBitmap does not support rotation with angle other than any multiple of 90 degs (that means we can only rotate 90, 180, 270, ...). Thinking about which objects we can put in a bitmap and apply some Transform? Well we have DrawingGroup, DrawingVisual, ImageBrush, UIElement and via DrawingContext.
Using DrawingGroup, we have to put in an ImageDrawing and apply the transform via the Transform property of the DrawingGroup. Then we have to use a DrawingImage, set the Drawing property to the DrawingGroup.
Using DrawingVisual, we have to open a DrawingContext, use the DrawImage method after pushing some transform. Then we may have to use RenderTargetBitmap by passing the DrawingVisual in the Render method.
Using UIElement (like your idea about using Image control as the medium), we have to render the image on the UIElement. So an Image control suits this best. Every UIElement has a Transform property allowing us to add whatever transform. At last we also have to use a RenderTargetBitmap by passing the UIElement in the Render method.
Using ImageBrush, we have to set the ImageSource property to a BitmapImage. Then we can use the Transform or RelativeTransform property to apply some transform. After that we have to use a DrawingImage. Create a simple GeometryDrawing using the ImageBrush as its Brush, and a RectangleGeometry as its Geometry. Finally we just need to set this GeometryDrawing to the Drawing property of the DrawingImage. The output is the DrawingImage. I would like to use this approach to write the code here:
Dim InImage As New BitmapImage(New Uri("My Image Path"))
Dim ImgBrush As New ImageBrush(InImage)
ImgBrush.Viewport = New Rect(0.1,0.1,0.8,0.8)
ImgBrush.ViewportUnits = BrushMappingMode.RelativeToBoundingBox
Dim Rotating As New RotateTransform(190)
Rotating.CenterX = 0.5
Rotating.CenterY = 0.5
ImgBrush.RelativeTransform = Rotating
Dim ImgSize As New Rect(0,0,300,400)
Dim DrawImage As New DrawingImage()
DrawImage.Drawing = New GeometryDrawing(ImgBrush, null, ImgSize)
Note about the "My Image Path". I've found out that using a Relative image would not work if you don't have any Image folder at the same level with the built exe file. If you don't want to deploy some image folder together with your application, you can add your image as a Resource. To refer to an image added as a Resource in code behind, you have to use a special kind of path:
pack://application:,,,/Your_Relative_Image_Path
Note that to be sure your image is added as a Resource, try right clicking on the image (under the Projects treeview), select Properties in the popup menu, then look into the Build Action field, it should be Resource.
Also note about the ImgBrush.Viewport, setting it appropriately will prevent the image from being cut off (that's because the transformed image is rotated). It depends on the ImgSize and how much degree you rotate.

WPF Pen DrawingVisual

I am working with WPF DrawingVisual and Pen and encountered a problem.
When I draw a DrawingVisual with Pen, say, a Rectangle as follows:
Pen StrokePen = new Pen();
StrokePen.Brush = Brushes.SkyBlue;
StrokePen.Thickness = 6;
DrawingVisual dv = new DrawingVisual
DrawingContext dc = dv.RenderOpen();
dc.DrawingRectangle(......., StrokePen, ......);
dc.Close();
I found that the half of the Stroke cover the rectangle like the following:
Therefore, if the Thickness of the Pen is too large so that it even larger than the Rectangle, the Rectangle will disappear (The whole rectangle is covered by the Stroke).
Could I adjust some setting so that the Stroke (Pen) drawn on the rectangle will not cover the rectangle (only draw beyond the sides of the rectangle)
Thank you.
You could simply draw the Rectangle twice, first with a Pen, then with a Brush:
using (DrawingContext dc = dv.RenderOpen())
{
...
dc.DrawingRectangle(null, StrokePen, ...);
dc.DrawingRectangle(FillBrush, null, ...);
....
}
In wpf border of rectangle is its internal content so no there is no way to force it to be outside of rectangle. But you can adjust size of your rectangle to compensate for Pen.Thickness.

How to change the background of a grid to an image in WP7 silverlight?

I'm trying to set a background for a grid control in WP7 silverlight, I need to do that programatically, not in the desighn.
I tried something like:
ContentPanel.Background = new BitmapImage(new Uri("Images\Backgrounds\with box\13.jpg", UriKind.Relative));
But of course, I got an error, because on the right hand we should have the type SolidColorBrush.
is there a way to do that?
Thanks..
I would firstly recommend that you use a Canvas instead of a Grid. You can do this by deleting the Grid and inserting a Canvas through the Toolbox -> Drag and Drop.
Then, you can use the code as simple as:
ImageBrush imageBrush = new ImageBrush();
imageBrush.ImageSource = new BitmapImage(new Uri("url", UriKind.Relative));
CanvasName.Background = imageBrush;
That will change the background to whatever you want.
Hope that helps.

problem with ContainerVisual.Transform

in my custom control i have a ContainerVisual object and a DrawingVisual under it.
I override ArrangeOverride and calculate the rectangle that i want to draw in based on the given size and the control's padding.
after that i set my ContainerVisual object's transform to the upper left corner of the rectangle so that the methods that render the drawing would not have to take account of the rectangle and assume that the drawing origin is at point 0,0.
this does not work, and the drawing is displaced. if instead i set transform of the DrawingVisual object it works and the rectangle is displayed the way it is supposed to be.
i thought that if i set transform on the container, it will automatically be applied to the visuals under it. is that so?
thanks for any help
EDIT: Updated the source code to show complete code.
class MyControl : Control
{
private readonly ContainerVisual container = new ContainerVisual();
private readonly DrawingVisual drawing = new DrawingVisual();
private Rect rect;
private void RenderDrawing()
{
using (var c = drawing.RenderOpen())
{
var p = new Pen(new SolidColorBrush(Colors.Black), 1);
c.DrawRectangle(null, p, new Rect(0, 0, rect.Width, rect.Height));
}
}
protected override Size ArrangeOverride(Size s)
{
var h = Math.Max(0, s.Height - Padding.Top - Padding.Bottom);
var w = Math.Max(0, s.Width - Padding.Left - Padding.Right);
var r = new Rect(Padding.Left, Padding.Top, w, h);
if (rect != r)
{
rect = r;
container.Clip = new RectangleGeometry(rect);
container.Transform = new TranslateTransform(rect.Left, rect.Top);
// replace the line above with the following line to make it work
// drawing.Transform = new TranslateTransform(rect.Left, rect.Top);
RenderDrawing();
}
return s;
}
protected override Visual GetVisualChild(int index)
{
return container;
}
protected override Size MeasureOverride(Size s)
{
return new Size();
}
protected override int VisualChildrenCount
{
get { return 1; }
}
public MyControl()
{
container.Children.Add(drawing);
AddVisualChild(container);
}
}
<Window x:Class="MyApp.MyWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:c="clr-namespace:MyApp"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<c:MyControl Padding="20" />
</Grid>
</Window>
Explanation of strange clipping behavior
Now that you have posted your full source code I was finally able to see what you were seeing. Your problem isn't in the transform at all: It is in the clip!
If you comment out the container.Clip assignment statement, you get identical results no matter whether you put the transform on container or drawing
If you uncommented container.Clip assignment statement, the clipping region is perfectly centered on when the drawing is transformed, but when the container is transformed the clipping area is offset, so that only the lower and right lines of the rectangle were visible (and not all of those)
The reason this occurs is that the geometry specified for container.Clip is part of the container, so it is affected by container.Transform but not drawing.Transform:
This can be better understood by looking at the upper-left corners of the container, drawing, rectangle, and clip area relative to the upper-left corner of the window:
When you set the transform on the drawing:
Container is at (0,0) relative to window (null transform)
Clip area is at (20,20) relative to window (null transform + RectangleGeometry)
Drawing is at (20,20) relative to window (null transform + TranslateTransform)
Rectangle is at (20,20) relative to window (null transform + TranslateTransform + 0,0)
When you set the transform on the container:
Container is at (20,20) relative to window (TranslateTransform)
Clip area is at (40,40) relative to window (TranslateTransform + RectangleGeometry)
Drawing is at (20,20) relative to window (TranslateTransform + null transform)
Rectangle is at (20,20) relative to window (TranslateTransform + null transform + 0,0)
So your problem isn't that the transform isn't happening: It is that the transform is moving the clip area too, so the clip area no longer coincides with the rectangle and you can only see two sides of the rectangle.
Answer given for original code (retained because it has some useful explanation)
In fact, the code you posted never uses "container" so all you will see is a blank screen.
In your actual code you are using "container" incorrectly, preventing the events from occurring in the correct sequence to cause its Transform to be picked up and passed to the MIL layer.
Remember that when a Visual has a Transform set, it is not the visual itself but that Visual's visual parent that actually handles that transform. For example, if you render a page to XPS using ReachFramework or do hit testing, the Transform on the outermost Visual is ignored.
Your understanding is correct: If your visual tree is built following all the rules, it doesn't matter whether your transform is on your "container" or your "drawing".
Since you are using Control anyway, I'm curious why you don't just let the normal UIElement-based layout system handle your layout needs.
First update (retained for the same reason)
Thanks for the code correction. It is as I suspected: You are building your visual tree incorrectly. If you are using AddVisualChild you also must also override GetVisualChild and VisuaChildrenCount. This is because Visual does not store a list of children: It is up to the subclass (your class) to do this. What is happening is:
When you call AddVisualChild the container's transform is null so that is what is passed down to MILCore.
Later when you change the container's transform, it uses its parent pointer (that was set in AddVisualChild) to signal that its transform data must be refreshed. This update requires part of the visual tree to be scanned using GetVisualChild and VisualChildrenCount.
Since you didn't implement these methods this part of the update fails.
You say you are "new to WPF." Are you aware that you are playing with some of WPF's most low-level and esoteric features, ones that would never be used in a most ordinary WPF applications? It is equivalent to starting to learn programming using machine language. Normally you would use templates with Path, Rectangle, etc for this purpose. Sometimes you might go lower level and use a DrawingBrush with a DrawingGroup containing GeometryDrawings, etc. But you would almost never go all the way down to DrawingVisual and RenderOpen! The only time you would do that is when you have huge drawings consisting of millions of individual items and so you want to bypass all the layout and structure overhead of the higher layers for absolute maximum performance.
Manipulating the visual tree yourself (AddVisualChild, etc) is also an advanced feature. I always recommend people new to WPF stick with UIElement and above for the first few months, using Control with templates. I recommend they use Path and other shape subclasses for their drawings, and use VisualBrushes when advanced drawing effects are needed.
Hope this helps.
the problem is with the container.Clip. it should be
container.Clip = new RectangleGeometry(new Rect(0, 0, w, h));

WPF: How to crop/clip a Drawing or a DrawingImage?

I have a function receiving a Drawing that I need to partially expose as a DrawingImage (i.e.: its position and size will be reduced/changed to fit in a target area).
How can I crop/clip a region of the original Drawing?
Or maybe it is easier to do that after the transformation to DrawingImage (how clip that DrawingImage)?
The solution was to encapsulate the original Drawing in a DrawingGroup and then apply a clipping geometry...
public DrawingGroup MyClippingFunc(Drawing OriginalDrawing, Rect ClippingArea)
{
var Group = new DrawingGroup();
Group.Children.Add(OriginalDrawing);
Group.ClipGeometry = new RectangleGeometry(ClippingArea);
return Group;
}
This is another way to do it, using the InkCanvas StrokeCollection class as an example.
using (DrawingContext drawingContext = drawingGroup.Open())
{
drawingContext.PushClip(new RectangleGeometry(yourRectangleObject));
Strokes.Draw(drawingContext);
drawingContext.Pop();
}
I am a little confused on what you are asking but maybe my answer to this similar question will help?
How can I use a PathGeometry as a mask for a BitmapSource (or any image data)?

Resources