I have had the problem described in the question Tiling rectangles seamlessly in WPF, but am not really happy with the answers given there.
I am painting a bar chart by painting lots of rectangles right next to each other. Depending on the scale of the canvas containing them, there are small gaps visible between some of them as a result from sub-pixel rendering.
I learned from the above question how to make my rectangles fit with the screen pixels, removing that effect.
Unfortunately, my chart may display way more bars than there are pixels. Apart from the tiny gaps (which manifest as a periodic change in color saturation), this works well. If I snap each bar with the screen pixels, most of the bars vanish, though, so I am looking for another solution.
Thanks in advance!
Cause of the problem
Subpixel shapes use alpha blending within the pixel. Unfortunately there is no alpha blending algorithm that results in the rectangles blending seamlessly when abutted.
For example, if:
The background color is white
The foreground color is black, and
You have two rectangles, each covering half of a single pixel
Each rectangle will be painted as black with 50% opacity. The first converts the white pixel to gray. The second converts it to a darker gray, but not black. If these rectangles continue black in adjacent pixels you see a dark gray pixel among the black.
Two types of solutions
There are two general ways to solve this problem:
Use a single Geometry to define all your rectangles, or
Force the initial rendering to be at a high enough resolution your user won't see the problem.
How to use a single Geometry
If you just have a set of Rectangles, you can create a simple control that paints over the whole set of rectangles with a single PathGeometry containing the combined shape. To illustrate the idea, if you had two rectangles beside each other of different heights, like this:
<Rectangle Canvas.Left="0" Canvas.Top="0" Width="1.5" Height="2" Fill="Red" />
<Rectangle Canvas.Left="1.5" Canvas.Top="0" Width="1.5" Height="4" Fill="Red" />
You could render it with a single PathGeometry like this:
<Path Data="M0,0 L0,2 L1.5,2 L1.5,4 L3,4 L3,0 Z" Fill="Red" />
A practical way to implement this is to:
Paint your rectangles with a Transparent brush so they will be clickable but not visable
Add a Path control underneath the rectangles in Z order
Data binding the Data property of your Path control to your data source with a converter that constructs the geometry.
If you are using the layout system to position your rectangles, you may instead want to use an AdornerLayer by creating an Adorner for each rectangles, then when rendering the adorners compute the combined path for the first one and make the rest invisible.
The above assumes it is easy to generate the PathGeometry from the source data. For more complex scenarios, the Path control can be subclassed to search the visual tree of its parent for specified shapes and use general geometricl algorithms to compute a PathGeometry that represents the union of them with no extra edges.
If your rectangles will have multiple colors, you can use multiple Path controls one per color, or you can construct a Drawing object and show that.
Here is the structure of the code to construct a PathGeometry:
var geo = new PathGeometry();
var figure = new PathFigure();
var segment = new PolyLineSegment();
segment.Points.Add(...);
segment.Points.Add(...);
segment.Points.Add(...);
segment.Points.Add(...);
segment.Points.Add(...);
figure.Segments.Add(segment);
geo.Figures.Add(figure);
How to force the initial rendering to be at high resolution
To force rendering at higher resolution:
Internally construct your chart several times larger than you want to display it, for example by wrapping it in a ViewBox.
Use a VisualBrush or RenderTargetBitmap to force your chart to be rendered separately
Add a Rectangle painted with that VisualBrush to your UI
Note that normally WPF is clever about rendering at the actual resolution required when you use a ViewBrush, but it can be tricked by having the actual chart actually display on the screen at the larger size, but then be clipped by a parent control so you don't actually see the too-big version.
This problem doesn't exist with RenderTargetBitmap, of course, since you specify the resolution you want, but it can be tricky knowing when to re-render the bitmap. If you only re-render on data changes you can use an event, but if you want any visual change to trigger a re-render it is more difficult.
Related
I have a WriteableBitmap object which I load a .jpg image into and show it in an image control. Now I would like to be able to let the user draw on that image with a little half-transparent brush.
Right now I am handling the MouseDown and MouseMove events to draw a little half-transparent circle on every change of the mouse's coordinates over the image:
MyWriteableBitmap.FillEllipseCentered(x, y, 1, 1, myColor);
That clearly does not refresh fast enough though - leaving me with a set of dots (unless the user moves the mouse very very slow).
My temporary solution is to just draw lines from one point to another and update the two points on every MouseMove event - the curve is consistent that way but is only one-pixel wide, which doesn't look great.
What is the best approach for me to tackle that problem?
You'll get better performance and automatic conversion to splines (if you want it) by using an InkCanvas instead:
<InkCanvas>
<InkCanvas.DefaultDrawingAttributes>
<DrawingAttributes Color="Blue" Width="8" Height="8" FitToCurve="True" />
</InkCanvas.DefaultDrawingAttributes>
</InkCanvas>
You can always get it to render itself into a bitmap later if you need it:
RenderTargetBitmap bitmap = new RenderTargetBitmap((int)theCanvas.ActualWidth, (int)theCanvas.ActualHeight, 96d, 96d, PixelFormats.Default);
bitmap.Render(theCanvas);
UPDATE: this works for transparent brushes as well BTW, and also pressure-sensitive pens (if enabled). The main problem is that the performance will drop as the user draws more and more lines, but you can easily fix that by rendering to a bitmap and then setting that bitmap to be the InkCanvas background i.e. the InkCanvas shouldn't never be displaying more than 1 spline, and only when the user is drawing it.
I am drawing lines on a background image in a c# panel. The panel is anchored to the form so as the form resizes the panel resizes. The background image is set to be stretched so all you see as you resize the form is the background image.
My initial problem:
The lines drawn on the panel (via the OnPaint event) stay where they were originally drawn as the image resizes.
My current solution:
Record the location of the line and redraw it on a new bitmap by scaling the X and Y coordinates (works fine).
My new problem:
As you continually resize the window and draw lines you can't calculate the scaling factor from any point in time and apply it to all lines since the lines were originall drawn in different size images.
The two options I think I have:
After I redraw the line go through my array of lines and update the coordinate information so it now matches the current scale.
Or
In addition to storing the coordinate information of the line also store the size information of the panel at the time it was drawn so I can always calculate the scale for each line based on when it was drawn and the new panel size.
What I'm hoping for:
If you have thoughts on either of the two approaches that would be greatly appreciated....Even better would be to point me in the direction of a far better method to do this (I am fairly new to graphics processing in c#).
Can't write a comment, much as I want to. You do have a few options:
Draw your lines directly on the original Bitmap. This might not be an option for you, depending on the task.
Do it as you're doing now, keeping track of the lines' coordinates, updating them on resize, and redrawing them on Paint - if you use this, you'll be able to move and delete them, too,
Or do it by introducing a "scale factor" (float) which you update on every resize, and in your Paint event handler you draw everything using that scale factor. As you create a line, you calculate its coordinates using the scale factor BACK TO an unified coordinate system (scale factor 1) and then you don't have to modify your coordinates at all. This might be easy to debug due to that unified coordinate system. This is what I'd recommend, but it again depends on your task.
Draw to a full transparent Bitmap of the same size as your original image, use a scale factor like in the previous option. On creating a line, calculate its coordinates in the unified coordinate system, draw it on the Bitmap, and then on every Paint, draw the entire Bitmap over your original one. This, again, might not be an option if you need to delete or move your lines, or if you're tight on memory, or you don't want your lines to be blurred when you scale up, but somehow many ppl like this because it's like a "layer in Photoshop". :)
before question think about for example photoshop. When you draw a rectangle on the picture.You can move it. And when you move it works very quickly and it doeasnt make some traces on the picture.
So my question is, how to do that in c# application?
This might be useful for you
Image Processing for Dummies with C# and GDI+ Part 1 - Per Pixel Filters
Image Processing for Dummies with C# and GDI+ Part 2 - Convolution Filters
Image Processing for Dummies with C# and GDI+ Part 3 - Edge Detection Filters
Image Processing for Dummies with C# and GDI+ Part 4 - Bilinear Filters and Resizing
Image Processing for Dummies with C# and GDI+ Part 5 - Displacement filters, including swirl
Image Processing for Dummies with C# and GDI+ Part 6 - The HSL color space
When you are moving the rectangle, Photoshop doesn't put it in the image and then draw the image, instead the image is drawn without the rectangle, and the rectangle is drawn on top of that on the screen. That way when you move the rectangle it can redraw the part of the image that previously was covered by the rectangle, and draw the rectangle at the new position.
I think you're asking about selection rectangles (or other temporary shapes) on top of the document image. This effect is sometimes known as “rubber banding”, especially when drawing a line from one point to another (it stretches like a rubber band).
Traditionally, this was done by using XOR drawing -- instead of overwriting the image with the selection shape, the colors in that area are inverted. Then, to remove the selection, it suffices to invert the colors again, returning to the same original image. Today, graphics rendering is fast enough that such tricks are not usually necessary; it suffices to simply repaint that part of the window (without the rectangle).
Either way, it is important to recognize that the document image — the image the user is editing — is not the same as the window image, which is just a copy to be remade whenever necessary. In the window, the document image is drawn and then selections, guide marks, and other such controls are drawn on top of it.
I'm not familiar with C#'s GUI facilities (and I understand there is more than one GUI framework you might be using), but it's probably got the usual structure of putting many "widgets", "views", or "controls" in the window (possibly nested inside each other). You can do a straightforward selection box — though not an optimally efficient one — by just putting an appropriately sized rectangle widget (with a solid border and a transparent background) on top of an image widget. This lets your GUI framework take care of the appropriate redrawing for you and is probably a good cheap way to start.
I have polygons of various shapes and sizes. They have a solid fill and currently a solid border.
I would like to give the polygons a gradient on their edge to soften them.
So far I've tried using a Pen with a LinearGradientBrush and whilst the effect it produces is very interesting it's most definitely not what I want ;)
I've looked through the System.Drawing.Drawing2D namespace but there didn't seem to be any other classes that would be applicable for this purpose.
I've had a search around and the articles that I can find are mostly about creating borders for rectangles, which are mush easier, or are irrelevant.
So to summarize, does anyone have a way of drawing a gradient border in on a polygon using GDI+?
Perhaps a screen shot of what your previous attempt produced and a mock up of what you would like would help?
Though I suspect the issue you're running into is that the direction and offset of the gradient is consistent throughout the entire shape and does not change with the orientation of the lines of the polygon.
Have you taken a look instead at the PathGradientBrush? (Examples) If you can't achieve the effect using it with a Pen for the stroke of the shape, perhaps you could do it with two "fills" with the first (the border) being slightly larger than the second (the interior).
I think I have done exactly what you're asking for, but in my case I have used it for creating soft shadows on text.
I do the following:
Draw the text (in your case:
polygon) to a Bitmap
Apply a
softening filter on the alpha
channel only
Iterate step 2 as
many times needed to get the desired
gradient width
Finally draw the
result onto the resulting
bitmap/screen
I do NOT want the system trying to scale my drawing, I want to do it entirely on my own as any attempt to squeeze/stretch the graphics will produce ugly results. The problem is that as the image gets bigger I want to add more detail rather than have it simply scale up.
Right now I'm looking at two sets of stripes. One is black/white, the other is black/white/white. The pen width is set to 1.
When the line is drawn horizontally it's correct. The same logic drawing vertical lines appears to be doing some antialiasing, bleeding the black onto the nearby white. The black/white/white doesn't look as good as the horizontal, the black/white looks more like medium++ gray/medium-- gray.
The same code is generating the coordinates in all cases, the transform logic is simply selecting what offset to apply where as I am only supporting orientations on the cardinals. Since there's no floating point involved I can't be looking at precision issues.
How do I get the system to leave my graphics alone???
(Yeah, I realize this won't work at very high resolution and eventually I'll have to scale up the lines. Over any reasonable on-screen zoom factor this won't matter, for printer use I'll have to play with it and see where I need to scale. The basic problem is that I'm trying to shoehorn things into too few pixels without just making blobs.)
Edit: There is no scaling going on. I'm generating a bitmap the exact size of the target window. All lines are drawn at integer coordinates. The recommendation of setting SmoothingMode to None changes the situation: Now the black/white/white draws as a very clear gray/gray/white and the black/white draws as a solid gray box. Now that this is cleaned up I can see some individual vertical lines that were supposed to be black are actually doing the same thing of drawing as 2-pixel gray bars. It's like all my vertical lines are off by 1/2 pixel--yet every drawing command gets only integers.
Edit again: I've learned more about the problem. The image is being drawn correctly but trashed when displayed to the screen. (Saving it to disk and viewing it on the very same monitor shows it drawn correctly.)
You really should let the system manage it for you. You have described a certain behavior that is specific to the hardware you are using. Given different hardware, the problem may not exist at all, or it may exist horizontally but not vertically, or may only exist at much smaller or much larger resolutions, etc. etc.
The basic problem you described sounds like the vertical lines are being drawn "between" vertical stacks of pixels, which is causing the system to draw an anti-aliased line. The alternative to anti-aliasing the line is to shift it. The problem with that is the lines will "jitter" or "jerk" if the image is moved around, animated, or scaled or transformed in any other way. Generally, jerk is MUCH less desirable than anti-aliasing because it is more distracting.
You should be able to turn off anti-aliasing using the SmoothingMode enum, or you could try to handle positioning yourself. Either way, you are trading anti-aliasing for jittery, jerky rendering during any movement or transformation.
Have a look at System.Drawing.Drawing2d.SmoothingMode. Setting it to 'Default' or 'None' should turn off anti aliasing when doing line drawing. If you're talking about scaling an image without anti aliasing effects, have a look at InterpolationMode. Specifically, you might wish to set it to 'Nearest-Neighbor' which will keep your rectangular blocks perfectly crisp. Note that you will see some odd effects if you scale your image by anything other than whole numbers.
Perhaps you need to align your lines on half-pixel coordinates? A one pixel line drawn at say x = 5 would be drawn on the center of the line, which means it would go from x = 4.5 to x = 5.5. If you want it to go from x = 4 to x = 5 then you'd need to set its coordinate to x = 4.5.
GDI+ has a property: http://msdn.microsoft.com/en-us/library/system.drawing.graphics.pixeloffsetmode.aspx that allows you to control this behavior.
Sounds like you need to change your application to tell the system it is DPI aware so scaling doesn't occur. Here's an article on doing that: http://msdn.microsoft.com/en-us/library/ms701681%28VS.85%29.aspx