Can I use graphics.RotateTransform() without these artifacts? - c#

I'm implementing a colour picker component as described in this seminal article.
As you can see, I've got the basics sorted:
One of the requirements however, is the ability to have the colour wheel rotated by an arbitrary amount. Thinking this would be easy, I some arithmetic to the mouse location -> colour value code and the following code to the bit that actually paints the wheel:
newGraphics.TranslateTransform((float)this.Radius, (float)this.Radius);
newGraphics.RotateTransform((float)this.offset);
newGraphics.TranslateTransform((float)this.Radius * -1, (float)this.Radius * -1);
Unfortunately, rotating the bitmap like this actually produces this:
Note the artefacts that appear either side of the centre.
Am I using the wrong approach? Or is there a way to get rid of these nasty rips?

Looking at the source code from that Microsoft example, I made the following change to the UpdateDisplay method by adding a matrix and setting the RotateAt method.
private void UpdateDisplay() {
// Update the gradients, and place the
// pointers correctly based on colors and
// brightness.
using (Brush selectedBrush = new SolidBrush(selectedColor)) {
using (Matrix m = new Matrix()) {
m.RotateAt(35f, centerPoint);
g.Transform = m;
// Draw the saved color wheel image.
g.DrawImage(colorImage, colorRectangle);
g.ResetTransform();
}
// Draw the "selected color" rectangle.
g.FillRectangle(selectedBrush, selectedColorRectangle);
// Draw the "brightness" rectangle.
DrawLinearGradient(fullColor);
// Draw the two pointers.
DrawColorPointer(colorPoint);
DrawBrightnessPointer(brightnessPoint);
}
}
It rotated the wheel 35 degrees (although the color selection was off now by, well, 35 degrees since I didn't mess with all the code) and it didn't produce any tearing.
Not 100% sure this is the answer (but too long for a comment), so maybe this is helpful.

Related

Set bits per pixel in WPF-Desktop program

I noticed ugly banding issues when using Gradients in WPF, and saw that a solution was to set the "bits per pixel" property to 32.
The thing is that the property seem to be Windows Phone only, ie not working on a program for desktop devices, since trying to add this string in the ApplicationManifest didn't seem to do anything.
Does anyone know if/how I can set this property?
Thank you.
My function which draws the gradients:
public LinearGradientBrush getGradient(Color c1, Color c2, double opacity)
{
LinearGradientBrush gradient = new LinearGradientBrush();
gradient.StartPoint = new Point(0, 0);
gradient.EndPoint = new Point(1, 1);
gradient.GradientStops.Add(new GradientStop(c1, 0.0));
gradient.GradientStops.Add(new GradientStop(c2, 1.0));
gradient.Opacity = opacity;
return gradient;
}
I draw the gradients off of the two most dominant colors in an AlbumCover. You can see the two colors on the top left of the window. I then call the getGradient-function with this:
getGradient(Colors[0], Colors[1], 0.5); // 0.5 is dynamic depending on the brightness of those colors. Tried with 1 opacity but it's still the same.
Here are the sample images (in PNG and uploaded without compression)
Image1
Image2
Image3
As you can see, there is banding going on. There are worse examples but I can't remember what Cover gave it.
Please notice that Image1 does not have banding on it's AlbumCover. Even though there is a gradient on it.
By doing a quick search I found some suggestions that the issue may be just a visual effect that is a result of having only 256 values for each of R, G and B channels that defines a color and the way that gradients work. If You try to cover a large area with a gradient, it'll divide it into smaller areas filled with solid colors, slightly changing between neighbouring areas. Additionally, there is an optical illusion called Mach bands that makes the borders of the areas even more visible.
Take a look at those links for more information and some suggested solutions:
how to make the brush smooth without lines in the middle
http://social.msdn.microsoft.com/Forums/vstudio/en-US/cea96578-a6b3-4b29-b813-e3643d7770ae/lineargradientbrush-can-see-individual-gradient-steps?forum=wpf
After digging around a long time I finally found the best solution:
Adding a little bit of noise to the image! This does mean I have to draw the gradient myself, but I believe the quality will be much better.
I will update this post with the algorithm itself and examples when I'm done writing.
Stay tuned I guess.

Blurred picture after applying of shader effect

I've come across strange behavior of pixel shader in WPF.
This problem is 100% reproducible, so I wrote small demo program. You can download source code here.
The root of all evil is tiny class titled MyFrameworkElement:
internal sealed class MyFrameworkElement : FrameworkElement
{
public double EndX
{
get
{
return (double)this.GetValue(MyFrameworkElement.EndXProperty);
}
set
{
this.SetValue(MyFrameworkElement.EndXProperty, value);
}
}
public static readonly DependencyProperty EndXProperty =
DependencyProperty.Register("EndX",
typeof(double),
typeof(MyFrameworkElement),
new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.AffectsRender));
protected override void OnRender(DrawingContext dc)
{
dc.DrawLine(new Pen(Brushes.Red, 2), new Point(0, 0), new Point(this.EndX, 100));
dc.DrawLine(new Pen(Brushes.Green, 3), new Point(10, 300), new Point(200, 10));
}
}
As you can see this framework element renders 2 lines: lower line has permanent coordinates but upper line depends on EndX dependency property.
So this framework element is target for pixel shader effect. For simplicity's sake I use grayscale shader effect found here. So I applied GrayscaleEffect to MyFrameworkElement. You can see result, it looks nice.
Until I increase EndX property drastically.
Small line is blurred and big line is fine!
But if I remove grayscale effect, all lines will look as they should.
Can anybody explain what's the reason of this blurring?
Or even better how can I solve this problem?
With a custom pixel shader it has to create an Intermediate Bitmap and then that texture gets sampled by the pixel shader.
You're creating a massive rendering, so your hitting some limitation in the render path.
A quick fix is to clip what you want rendered as follows:
Geometry clip = new RectangleGeometry(new Rect(0,0,this.ActualWidth, this.ActualHeight));
dc.PushClip(clip);
dc.DrawLine(new Pen(Brushes.Red, 2), new Point(0, 0), new Point(this.EndX, 100));
dc.DrawLine(new Pen(Brushes.Green, 3), new Point(200, 10), new Point(10, 300));
dc.Pop();
UPDATE:
One theory is that it's using a filter to scale the bitmap when it exceeds the maximum texture size (which can vary depending on your graphics card architecture)...so it goes through the pixel shader at a different size....then it gets scaled back to original size.
Thus the scaling filter is causing artifacts depending on the content of your bitmap (i.e. horizontal lines and vertical lines survive a scale down and up better than diagonal lines).
.NET 4 changed the default filter it uses for filtering to a lowerquality one...Bilinear, instead of Fant...maybe this impacts the quality that you get too.
http://10rem.net/blog/2010/05/16/more-on-image-resizing-in-net-4-vs-net-35sp1-bilinear-vs-fant
UPDATE2:
This kind of confirms what I was thinking above.
If you use the Windows Performance Toolkit/Suite (part of Windows SDK), then you can see the Video Memory being gobbled up in the orange graph while you increase the slider value because a bigger Intermediate Bitmap texture is being created. It keeps increasing until it hits a limit, then it flatlines...and thats when the pixelation becomes evident.
UPDATE3:
If you set the render mode to the "Software Renderer" (Tier 0) then you can see how it copes with rendering such a large visual - the artifacts start appearing at a different point....presumably because the texture size limit is larger/different to your GPUs. But the artifacts still appear because it's using a Bilinear filter internally.
Trying to use RenderOptions.SetBitmapScalingMode to up the filter to Fant doesn't seem to change the rendering quality in any way (I guess because it isn't honoured when it goes through the custom pixel shader path).
Put this in Application_Startup to see the software renderer results:
RenderOptions.ProcessRenderMode = RenderMode.SoftwareOnly;
Note that image is normally blurred in vertical direction, but is jagge in horizontal.
Since shaders are applied to raster images, not vector, the lines are rasterized into texture. Hardware usually supports textures up to 8198*8192.
In my case the "blurring", as you call it, appears at slider value of 16384. So, my virtualBox virtual graphics card supports up to 16384*16384.
Your limit may differ.
So just keep this value lower than that.
But it's strange that WPF rasterizes whole image, since only small part of it visible.
So there is also another possible reason, that lies inside shader itself, but it is compiled into binary, so i can't check it.
Update:
In my case it looks this way:
Looks like it is filtered vertically but not horizontally.
Ok, I've got this!
I decompiled the library with your grayscale effect and also decompiled WCF PresentationCore library to check why BlurEffect works perfect in the same situation.
And i found that BlurEffect implements abstract method Effect.GetRenderBounds which is absent in GrayscaleEffect. I also noticed that GrayscaleEffect is built against PresentationCore v 3.0.0 where Effect does not have GetRenderBound.
So this is an incompatibility between 3rd and 4th versions of WPF.
There are three ways to fix it:
If you have source code of GrayscaleEffect - add needed methods and compile it against 4.0.0 version of runtime.
You can switch the runtime your application use to version 3.*.
If you don't have sources of GrayscaleEffect but can't use 3rd version of runtime, write wrapper for GrayscaleEffect that inherits Effect (v4) and implements absent methods.
I tried 2nd way and the problem disappeared.
old question, but might be useful for someone having problem with blurring of image after applying Custom ShaderEffect.
Also problem OP mentioned might be releated to scale of rendered content,
I had similar problem with blurring after applying ShaderEffects from WPFShadersLibrary to video, text and any other content within normal window.
What I noticed that that image shifts down by a tiny bit, resulting in "pixel splitting", so I created two new properties for chosen ShaderEffect : XOffset and YOffset, and applied them in HLSL (see code below), then binded to Sliders in XAML :
float2 newPos;
newPos.x = uv.x + offsetX;
newPos.y = uv.y + offsetY;
Then I experimented with some arbitrary offsets and was able to re-align the picture. There is still some minimal blurring (or loss in detail) but result was noticeably better.
Problem with this solution currently, that I don't know how to predict offset either depending on resolution or window size.

Segmenting Part of the image using threshold

Im trying to isolate and segment the yellow car body to change the color of it. in order to do that i need to separately identify the body from the image. And continue oration with the remaining white pixels. And im using C#, here the plan
Color d;
Color newColor = Color.YellowGreen;
for(inti =0;i<carimage.Width;i++){
for(intj =0;j<carimage.Height;j++){
d = carimage.GetPixel(i, j);
if(d.R == 255 && d.G==255 && d.B == 255)
image.SetPixel(i, j, newColor );
}
}
simple thresholding will trow the second image where car body is not separated correctly. i tried Aforge.net Fill holes image filter but no significant change has been done to the threshold image. I tried to use color filter but it i did not return a correct output due to color vary of the body. can anyone suggest and solution for this?
Original Image
Threshold Image
Instead of thresholding, you might want to look into clustering.
As a quick&dirty test, I've increased the image brightness in HSB space (using Mathematica):
brightnessAdjusted = Image[
Map[#^{1, 1, 0.2} &, ImageData[ColorConvert[img, "HSB"]], {2}],
ColorSpace -> "HSB"]
Then I've used simple K-Nearest clustering:
(clusters = ClusteringComponents[ColorConvert[brightnessAdjusted, "RGB"], 3,
Method -> "KMeans"]) // Colorize
to find clusters of similar colors in the image (there are many more, probably more suitable clustering algorithms, so you should experiment a little). Then I can just adjust the color in one of the clusters:
Image[MapThread[If[#1 == 2, #2[[{1, 3, 2}]], #2] &, {clusters, ImageData[brightnessAdjusted]}, 2]]
If you want to use thresholding, you should probably use a CIE color space, since euclidian distances in that color space are closer to human perception.
I had a similar project few years ago. I can't remember the exact details, but the idea was to shift a (not too small) sliding window over the image, and calculate the average intensity (maybe for R, G and B separately) inside the window at each position. I filled a "threshold image" with these averages, and subtracted it from the original image. There was a scaling factor somewhere, and other tuning stuff, but the point is, such an approach was way better than using a constant threshold.
If you are going to use a set of thresholds, you might be better of selecting yellow hues in the Hue Saturation Value colorspace. See the related SO question.
I=imread('test.jpg');
I=im2double(rgb2gray(I));
BW=im2bw(I,0.64);imshow(BW)
Gives me :
I got the 0.64 threshold by looking at the image's histogram. I suggest you use MATLAB to do image processing as it is much easier. Hope that helps you in colouring the image.

Custom InkCanvas (MSDN Code Sample not working properly)

I want to use custom brushes with the InkCanvas.
Their is a code snippet from MSDN. (http://msdn.microsoft.com/en-us/library/ms747347.aspx)
If i use that code and move my mouse VERY fast i get space between the brushes(ellipses):
And my question is of course how to fix this but I'm also curious why this is happening (I want to learn from it) I thought maybe i did something wrong but even if i cut/paste the example it's happening.
One little thing i noticed when reading the code was this comment in the CustomStroke class
// Draw linear gradient ellipses between
// all the StylusPoints in the Stroke
Seems to me like it should draw ellipses between the points not only at the points.
I'm using C#.NET.
Again in short:
Why is this happening
Help me fix it :)
Why this is happening
The custom InkCanvas in the example draws an ellipse at every collected StrokePoint but makes no attempt to draw lines between them. The standard InkCanvas control is implemented by drawing lines between the points it is given. This is why the custom InkCanvas implementation from the example leaves gaps and the built-in one doesn't.
How to "fix" it
The custom code could easily be extended to not leave gaps: In addition to drawing ellipses at each point, it could draw lines between each pair of points.
Code to draw connecting lines might be added before the code to draw the ellipses, like this:
// Draw connecting lines
var geo = new StreamGeometry();
using(geoContext = geo.Open())
{
geoContext.StartFigure(stylusPoints[0], false, false);
geoContext.PolyLineTo(stylusPoints.Skip(1).Cast<Point>(), true, false);
}
drawingContext.DrawGeometry(null, connectingLinePen, geo);
// Draw ellipses
for(int i = 1; i < stylusPoints.Count; i++)
{
... etc ...
This code works by constructing a polyline StreamGeometry and then drawing it to the context. Using a StreamGeometry in this context is generally more efficient than creating a PathGeometry with a Polyline or doing a bunch of DrawLine calls directly on the drawingCanvas.
Note: Using a better digitizer won't solve the underlying problem, which is that the custom InkCanvas is actually designed to only show data at the sampled points and not in between.
ReDAeR
Look this
http://msdn.microsoft.com/en-us/library/bb531278.aspx
Why this is happening: The WPF InkCanvas control has a limited number of inputs per second when using a mouse; meaning that your stylus inputs will have greater and greater distances between them as you move the mouse faster and faster. The sample itself appears to draw elipses at every stylus input point, not in-between the points.
How to solve this: use a Tablet PC - a digitizer such as on a Tablet PC has a much higher number of inputs per second so it is more difficult to encounter, or fill in the blanks - essentially estimate based on previous points, perhaps a bezier curve.

LinearGradientBrush Artifact Workaround?

The LinearGradientBrush in .net (or even in GDI+ as a whole?) seems to have a severe bug: Sometimes, it introduces artifacts. (See here or here - essentially, the first line of a linear gradient is drawn in the endcolor, i.e. a gradient from White to Black will start with a Black line and then with the proper White to Black gradient)
I wonder if anyone found a working workaround for this? This is a really annoying bug :-(
Here is a picture of the Artifacts, note that there are 2 LinearGradientBrushes:
alt text http://img142.imageshack.us/img142/7711/gradientartifactmm6.jpg
I have noticed this as well when using gradient brushes. The only effective workaround I have is to always create the gradient brush rectangle 1 pixel bigger on all edges than the area that is going to be painted with it. That protects you against the issue on all four edges. The downside is that the colors used at the edges are a fraction off those you specify, but this is better than the drawing artifact problem!
You can use the nice Inflate(int i) method on a rectangle to get the bigger version.
I would finesse Phil's answer above (this is really a comment but I don't have that privilege). The behaviour I see is contrary to the documentation, which says:
The starting line is perpendicular to the orientation line and passes through one of the corners of the rectangle. All points on the starting line are the starting color. Then ending line is perpendicular to the orientation line and passes through one of the corners of the rectangle. All points on the ending line are the ending color.
Namely you get a single pixel wrap-around in some cases. As far as I can tell (by experimentation) I only get the problem when the width or height of the rectangle is odd. So to work around the bug I find it is adequate to increase the LinearGradientBrush rectangle by 1 pixel if and only if the dimension (before expansion) is an odd number. In other words, always round the brush rectangle up the the next even number of pixels in both width and height.
So to fill a rectangle r I use something like:
Rectangle gradientRect = r;
if (r.Width % 2 == 1)
{
gradientRect.Width += 1;
}
if (r.Height % 2 == 1)
{
gradientRect.Height += 1;
}
var lgb = new LinearGradientBrush(gradientRect, startCol, endCol, angle);
graphics.FillRectangle(lgb, r);
Insane but true.
At least with WPF you could try to use GradientStops to get 100% correct colors right at the edges, even when overpainting.
I experienced artifacts too in my C++ code. What solved the problem is setting a non-default SmoothingMode for the Graphics object. Please note that all non-default smoothing modes use coordinate system, which is bound to the center of a pixel. Thus, you have to correctly convert your rectangle from GDI to GDI+ coordinates:
Gdiplus::RectF brushRect;
graphics.SetSmoothingMode( Gdiplus::SmoothingModeHighQuality );
brushRect.X = rect.left - (Gdiplus::REAL)0.5;
brushRect.Y = rect.top - (Gdiplus::REAL)0.5;
brushRect.Width = (Gdiplus::REAL)( rect.right - rect.left );
brushRect.Height = (Gdiplus::REAL)( rect.bottom - rect.top );
It seems like LinearGradientBrush works correctly only in high-quality modes.

Categories