Tiling rectangles seamlessly in WPF - wpf

I want to seamlessly tile a bunch of different-colored Rectangles in WPF. That is, I want to put a bunch of rectangles edge-to-edge, and not have gaps between them.
If everything is aligned to pixels, this works fine. But I also want to support arbitrary zoom, and ideally, I don't want to use SnapsToDevicePixels (because it would compromise quality when the image is zoomed way out). But that means my Rectangles sometimes render with gaps. For example:
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Background="Black">
<Canvas SnapsToDevicePixels="False">
<Canvas.RenderTransform>
<ScaleTransform ScaleX="0.5" ScaleY="0.5"/>
</Canvas.RenderTransform>
<Rectangle Canvas.Left="25" Width="100" Height="100" Fill="#CFC"/>
<Rectangle Canvas.Left="125" Width="100" Height="100" Fill="#CCF"/>
</Canvas>
</Page>
If the ScaleTransform's ScaleX is 1, then the Rectangles fit together seamlessly. When it's 0.5, there's a dark gray streak between them. I understand why -- the combined semi-transparent edge pixels don't combine to be 100% opaque. But I would like a way to fix it.
I could always just make the Rectangles overlap, but I won't always know in advance what patterns they'll be in (this is for a game that will eventually support a map editor). Besides, this would cause artifacts around the overlap area when things were zoomed way in (unless I did bevel-cut angles on the underlapping portion, which is an awful lot of work, and still causes problems at corners).
Is there some way I can combine these Rectangles into a single combined "shape" that does render without internal gaps? I've played around with GeometryDrawing, which does exactly that, but then I don't see a way to paint each RectangleGeometry with a different-colored brush.
Are there any other ways to get shapes to tile seamlessly under an arbitrary transform, without resorting to SnapsToDevicePixels?

You might consider using guidelines (see GuidelineSet on MSDN) and overriding the Rectangles' OnRender methods so that their boundaries line up with the pixel boundaries of the device. WPF uses guidelines to determine whether and where to snap drawings.
Internally, it's exactly what SnapsToDevicePixels is using to ensure that objects line up with the device's pixels, but by placing guidelines manually you'll be able to control when the snapping behaviour is applied and when it is not (so when your image is zoomed all of the way out, you can avoid drawing guidelines, or only draw guidelines where your shapes lie next to other shapes, and rely on WPF's anti-aliasing to take care of the rest). You might be able to do it with an attached property so that you can apply it to any element, though if it's only one type of element (e.g. Rectangle) that you need this behaviour on, it's probably not worth the extra effort.
It seems like Microsoft is aware of this problem, too - WPF 4.0 is expected to feature Layout Rounding, which, like the version in Silverlight, rounds off non-integer values at the Render pass when layout rounding has been enabled.

I guess the gaps are not actual gaps but the stroke that is painted. When you scale it down than you just make the stroke smaller to a point where it is not visible anymore. I tried to paint the stroke in the color of the rectangle wich works just fine on any scale.
&ltPage xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Background="Black"&gt
&ltCanvas SnapsToDevicePixels="False"&gt
&ltCanvas.RenderTransform&gt
&ltScaleTransform ScaleX="0.5" ScaleY="0.5"/&gt
&lt/Canvas.RenderTransform&gt
&ltRectangle Canvas.Left="25" Width="100" Height="100" Fill="#CFC" Stroke="#CFC"/&gt
&ltRectangle Canvas.Left="125" Width="100" Height="100" Fill="#CCF" Stroke="#CCF"/&gt
&lt/Canvas&gt
&lt/Page&gt

Related

Maintain position for child in a Canvas with zoom and scroll - WPF

I would like to implement the following functionality.
I have a canvas with several UIElements inside it. The Canvas allows to zoom and scroll applying a scale and translate transforms.
I would like to maintain the red square always in the same position (left bottom corner of the Canvas), to behave as a floating control, so as I change the zoom or the scroll, the red square always maintains it's size and position. Something similar to google maps "Earth window":
What is the best approach to implement it?
NOTE: I tried to use the WPF adorner layer but it does not respond to mouse events, and I need to interact with the red square.
As Clemens said, put it in another layer on top. Grid can host multiple items in the same cell, so create a 1 by 1 Grid for your Canvas add content on top just like any other WPF layout. The later items appear on top (unless Z layer is specified):
<Grid>
<Canvas>
... do all my fancy drawing
</Canvas>
<Rectangle VerticalAlignment="Bottom" HorizontalAlignment="Left" Width="100" Height="100" />
</Grid>
Note, you may have trouble if the canvas has any non WPF rendering, such as video or embedded WindowsForms content. I've seen people have trouble with drawing WPF stuff on top of that.
I also believe you can set Canvas.Bottom="20" to set the position relative to the bottom edge, but I've never used it.

WPF Anti-aliasing edge of turned elements

I need Anti-alias edge of my turned rectangle more than normally.
My code is like this:
<Rectangle Margin="20,20,147,135" Fill="#FFCAD2DE" RenderOptions.EdgeMode="Unspecified">
<Rectangle.Effect>
<DropShadowEffect BlurRadius="4" ShadowDepth="2" Opacity=".5"/>
</Rectangle.Effect>
<Rectangle.RenderTransform>
<RotateTransform CenterX="0" CenterY="0" Angle="6" />
</Rectangle.RenderTransform>
</Rectangle>
I change the angle slowly programmatically...
But the result is aliased in some angles like below image (left side). I want edge of my rectangle be fully smooth in all angles like the below image (right side).
EDIT1:
I use .NET 3.5
The following steps can help
A)
First i moved my project from .NET 3.5 to .Net 4.5 without any changes in it and the result was:
It looks like very smoother
B)
Layout Rounding:
What is Layout Rounding and how to use it in WPF 4
When an object edge falls in the middle of a pixel device, the
DPI-independent graphics system can create rendering artifacts, such
as blurry or semi-transparent edges.
Previous versions of WPF included
pixel snapping to help handle this case. Silverlight 2 introduced
layout rounding, which is another way to move elements so that edges
fall on whole pixel boundaries.
WPF now supports layout rounding with
the UseLayoutRounding attached property on FrameworkElement. Drawing
objects on pixel boundaries eliminates the semi-transparent edges that
are produced by anti-aliasing, when an edge falls in the middle of a
device pixel. When you use layout rounding, the layout system creates
small variations in the column or row measurements to avoid sub-pixel
rendering.
The following code uses UseLayoutRounding attached property set on a single pixel-width line. You can see the difference that layout rounding makes when you resize the window slowly.
<StackPanel Width="150" Margin="7" Orientation="Horizontal">
<!-- Single pixel line with layout rounding turned OFF.-->
<Rectangle UseLayoutRounding="False" Width="45.6" Margin="10" Height="1" Fill="Red"/>
<!-- Single pixel line with layout rounding turned ON.-->
<Rectangle UseLayoutRounding="True" Width="45.6" Margin="10" Height="1" Fill="Red"/>
</StackPanel>
C)
SnapsToDevicePixels
Note
You should set UseLayoutRounding to true on the root
element. The layout system adds child coordinates to the parent
coordinates; therefore, if the parent coordinates are not on a pixel
boundary, the child coordinates are also not on a pixel boundary.
If UseLayoutRounding cannot be set at the root, set
SnapsToDevicePixels on the child to obtain the effect that you want.
It looks as if this isn't possible: RotateTranform causes alias edges on border control or a Rectangle.
From there:
Fortunately, the edge becomes perfectly soomth for 25+ degree rotations on my computer. If you eventually find a way to solve your problem, please share with us. It will be very beneficial for other community members having the similar questions.

WPF control origin point

After some investigation, I still can't find method to change origin of control.
So, I want just to place one square exactly in center of another square, without margins, so it will be completely independent of first square size.
Theoretically, it can be easily done with HorizontalAlignment and VerticalAlignment set to Center, since it automatically sets Margin of control to half of width and height of parent control. But it is not so simple.
Simplest way to describe problem is next picture
As you can see, margin is counted towards upper left corner. Which is what I call origin. The perfect solution is to change it to center of first square, but this is where I need help - how can I do that?
Point of origin applies when using a transform object, and attaching the transform to your control. It won't actually effect the behaviour of the margin or left, top properties. If using a transform to place your object, point of origin is very useful.
The top, left (if using cavas) and margin (if using say grid) help govern the "auto" placement by the parent control, and this in turn governs where point of origin for the control winds up being relative to the parent control. The transform object then offsets RELATIVE to where that point of origin is.
The other useful thing is that transform overrides the auto placement in the parent control, or rather, forces an offset to where the parent wants to put it, which in some cases is useful - i.e., you might have boxes listed in a grid and want them to "shake" left and right when you hover the mouse over them, their alignment stays in order to the grid, but the transform lets you bump them away from their "forced" position.
For example, attach the same transform object to 2 controls, and set their origins separate, then apply an animation to the transform object - both controls will animate off the one animation object (if you wanted to their movement in perfect sync).
Well, it was weird enough. The given behaviour can be seen only when using Image, and Center alignment. Can be solved by either wrapping Image in Grid, which will be using Center alignment, or using Stretch alignment with Image (which is much simplier).
<Grid Width="500" Height="500">
<Image Width="250" Height="250" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"/>
</Grid>
If you want to reproduce problem I've described in question, replace Stretch with Center in code above.
Probably oversimplifying here but I would just use a Grid to wrap the two items you mentioned like this example (One stretched to fit and one centered):
<Grid>
<Border HorizontalAlignment="Stretch" VerticalAlignment="Stretch" BorderBrush="Black" BorderThickness="10" Margin="4"/>
<Button HorizontalAlignment="Center" VerticalAlignment="Center" Padding="10">InnerButton</Button>
</Grid>

WPF item panel that keeps aspect ratio

I have an ItemsControl which may contain an arbitrary number of items (unknown at design time). Each of these items is basically represented as an infinitely-scalable image with a fixed aspect ratio (ie. the image will draw itself in whatever space is given to it -- it does not dictate its own size except that the aspect ratio must be preserved). The aspect ratio for each item might differ but is usually the same.
I want to:
Draw a border around each image, ideally of uniform thickness regardless of image scaling.
Draw each image as large as possible within the window, while maintaining its order, aspect ratio, and margins external to the border.
At least two of the four sides of an image must always touch the invisible boundaries of the cell it is within. The other two sides should be centred if not touching. (Assuming that some sort of uniform cell layout is used.)
Not overlap or clip any images.
Automatically re-layout as the containing window is resized.
Waste the minimum amount of non-image space.
Cope as well as possible if the aspect ratios of the items differ. (But it's ok if this increases the wasted space of other items, as long as they rescale to fit the result.)
The general consensus that I've found seems to be to wrap each image in a Stretch.Uniform Viewbox, and then put those into a UniformGrid. I've tried that approach but it doesn't appeal to me because:
Given two items, the UniformGrid always wants to create a 2x2 grid layout even when the window shape would make a 2x1 or 1x2 grid more suitable, which results in over-scaling and wasted space.
If I put the Border within the Viewbox then it scales the border thickness. If I put it outside then it distorts the aspect ratio.
Is there a better way to do this? (Note that the "image" is actually my own custom-draw FrameworkElement, so I can put custom measure/arrange code in here or in a custom container panel if it will help.)
<UniformGrid>
<Rectangle Fill="Red" Margin="4" Width="500" Height="281.25" />
<Rectangle Fill="Blue" Margin="4" Width="500" Height="281.25" />
</UniformGrid>
Here's a simple example. Put this into a window, then try resizing the window. The rectangles change size to fit the window (good), but they also change shape/aspect (bad), and they stop resizing once the window is sufficiently large (bad). Also it leaves space for an entire 2x2 grid even when the window itself is sized such that 1x2 or 2x1 would work better.
<UniformGrid>
<Viewbox Stretch="Uniform" Margin="4"><Rectangle Fill="Red" Width="500" Height="281.25" /></Viewbox>
<Viewbox Stretch="Uniform" Margin="4"><Rectangle Fill="Blue" Width="500" Height="281.25" /></Viewbox>
</UniformGrid>
This works marginally better in that the rectangles continue to stretch when the window gets large, and they no longer distort their shape, but there's still the needless second row when the window is wide or second column when the window is narrow. And I would prefer that the elements line up from the top left (like a WrapPanel) rather than centering, but that's a minor detail.
(And now try adding a Border, both inside and outside of the Viewbox, and see what I mean there.)
Actually a WrapPanel almost does what I want, except that it auto-sizes the items too large when the window gets smaller.

How can I create an opacity mask in wpf that doesn't scale?

Ok, I've created a PNG-24 with transparency. It's basically a grayscale image that uses 'colors' in between black and transparent instead of black and white. I did this so I can use this as the Opacity Mask of a colored rectangle, thus rendering the image in whatever color I want using only a single graphic.
However, for the life of me, I can't get WPF to stop anti-aliasing the da*n image!!
I've set 'SnapesToDevicePixels' on the rectangle to which the brush is applied... I've set the ImageBrush's Scale to 'None'... I've set its ViewPort and the ViewBox to absolute units and sized them exactly to the source image. But no matter what I try, WPF still insists on trying to smooth things out! This is VERY frustrating!!!
So... anyone know how to use an image as an opacity mask but not lose the pixel-precise drawing that we have done? I just want WPF to render the damn thing as we drew it, period!
I have tried to reproduce your problem. Simply like this:
<Rectangle Width="200" Height="200" Fill="Red">
<Rectangle.OpacityMask>
<ImageBrush ImageSource="/mask.png"/>
</Rectangle.OpacityMask>
</Rectangle>
mask.png contains a simple diagonal mask, like that half of rectange is visible and other half is 100% transparent.
And recrangle is rendering pixel perfect (and aliased, as you want).
I think, that you may a DPI setting, that is not native to your monitor, and WPF just can`t render images correctly.
GOT IT! It's a layout issue that for some reason, there's no easy way to change. However, there's a value you can set called UseLayoutRounding that fixes it. I just set it at the root level (for this fauxample, a grid...)
<Grid UseLayoutRounding="True">
....
</Grid>
...and BAM! Works like a charm! "Sort of" like a 'SnapsToDevicePixels' but for positioning of elements (i.e. it rounds all layout-related values like left, width, etc. whereas SnapsToDevicePixels snaps the layout to the on-screen pixels when rendering.)
M

Resources